一只小强

天道酬勤 厚德载物


  • 首页

  • 标签24

  • 分类8

  • 归档30

  • 公益

  • 搜索

JavaScript 中的树型数据结构

发表于 2021-11-14 | 分类于 JavaScript

JavaScript 中的树型数据结构

实现和遍历技术

作者:Anish Kumar 译者:同学小强 来源:stackfull

Tree 是一种有趣的数据结构,它在各个领域都有广泛的应用,例如:

  • DOM 是一种树型数据结构
  • 我们操作系统中的目录和文件可以表示为树
  • 家族层次结构可以表示为一棵树

树有很多变体(如堆、 BST 等) ,可用于解决与调度、图像处理、数据库等相关的问题。许多复杂的问题可能看起来和树没有关系,但是实际上可以表示为一个问题。我们还将讨论这些问题(在本系列后面的部分中) ,看看树是如何使看似复杂的问题更容易理解和解决的。

引言

为二叉树实现一个

1
2
3
4
5
6
7
8
9
10
11

```js
function Node(value){
this.value = value
this.left = null
this.right = null
}
// usage
const root = new Node(2)
root.left = new Node(1)
root.right = new Node(3)

因此,这几行代码将为我们创建一个二叉树,它看起来像这样:

1
2
3
4
5
6
           2  
/ \
/ \
1 3
/ \ / \
null null null null

这很简单。现在,我们如何使用这个呢?

遍历

让我们从试图遍历这些连接的树节点(或整颗树)开始。就像我们可以迭代一个数组一样,如果我们也可以“迭代”树节点就更好了。然而,树并不是像数组那样的线性数据结构,因此遍历这些数据结构的方法不止一种。我们可以将遍历方法大致分为以下几类:

  • 广度优先遍历
  • 深度优先遍历

广度优先搜索/遍历(BFS)

在这种方法中,我们逐层遍历树。我们将从根开始,然后覆盖所有的子级,以及覆盖所有的二级子级,以此类推。例如,对于上面的树,遍历会得到如下结果:

1
2, 1, 3

下面是一个略微复杂的树的例子,使得这个更容易理解:

要实现这种形式的遍历,我们可以使用一个队列(先进先出)数据结构。下面是整个算法的样子:

  • 初始化一个包含 root 的队列
  • 从队列中删除第一项
  • 将弹出项的左右子项推入队列
  • 重复步骤2和3,直到队列为空

下面是这个算法实现后的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function walkBFS(root){
if(root === null) return

const queue = [root]
while(queue.length){
const item = queue.shift()
// do something
console.log(item)

if(item.left) queue.push(item.left)
if(item.right) queue.push(item.right)
}
}

我们可以稍微修改上面的算法来返回一个二维数组,其中每个内部数组代表一个包含元素的层级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function walkBFS(root){
if(root === null) return

const queue = [root], ans = []

while(queue.length){
const len = queue.length, level = []
for(let i = 0; i < len; i++){
const item = queue.shift()
level.push(item)
if(item.left) queue.push(item.left)
if(item.right) queue.push(item.right)
}
ans.push(level)
}
return ans
}

深度优先搜索/遍历(DFS)

在 DFS 中,我们取一个节点并继续探索它的子节点,直到深度到达完全耗尽。这可以通过以下方法之一来实现:

1
2
3
root node -> left node -> right node // pre-order traversal
left node -> root node -> right node // in-order traversal
left node -> right node -> root node // post-order traversal

所有这些遍历技术都可以迭代和递归方式实现,让我们进入实现细节:

前序遍历

下面是一颗树的前序遍历的样子:

1
root node -> left node -> right node

诀窍:
我们可以使用这个简单的技巧手动地找出任何树的前序遍历: 从根节点开始遍历整个树,保持自己在左边。

实现:
让我们深入研究这种遍历的实际实现。

相当直观。
1
2
3
4
5
6
7
8
9
10
11
12

```js
function walkPreOrder(root){
if(root === null) return

// do something here
console.log(root.val)

// recurse through child nodes
if(root.left) walkPreOrder(root.left)
if(root.right) walkPreOrder(root.right)
}

前序遍历的

BFS 非常相似,不同之处在于我们使用```堆栈```而不是```队列```,并且我们首先将右边的子元素放入堆栈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

```js
function walkPreOrder(root){
if(root === null) return

const stack = [root]
while(stack.length){
const item = stack.pop()

// do something
console.log(item)

// Left child is pushed after right one, since we want to print left child first hence it must be above right child in the stack
if(item.right) stack.push(item.right)
if(item.left) stack.push(item.left)
}
}

中序遍历

下面是一颗树的中序遍历的样子:

1
left node -> root node -> right node

诀窍:
我们可以使用这个简单的技巧手动地找出任何树的中序遍历: 在树的底部水平放置一个平面镜像,并对所有节点进行投影。

实现:

递归:

1
2
3
4
5
6
7
8
9
10
function walkInOrder(root){
if(root === null) return

if(root.left) walkInOrder(root.left)

// do something here
console.log(root.val)

if(root.right) walkInOrder(root.right)
}

迭代: 这个算法起初可能看起来有点神秘。但它相当直观的。让我们这样来看: 在中序遍历中,最左边的子节点首先被打印,然后是根节点,然后是右节点。所以我们首先想到的是:

1
2
3
4
5
6
7
8
9
10
11
let curr = root

while(curr){
while(curr.left){
curr = curr.left // get to leftmost child
}

console.log(curr) // print it

curr = curr.right // now move to right child
}

在上述方法中,我们无法回溯,即返回到最左侧节点的父节点,所以我们需要一个堆栈来记录它们。因此,我们修订后的方法可能看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
const stack = []
let curr = root

while(stack.length || curr){
while(curr){
stack.push(curr) // keep recording the trail, to backtrack
curr = curr.left // get to leftmost child
}
const leftMost = stack.pop()
console.log(leftMost) // print it

curr = leftMost.right // now move to right child
}

现在我们可以使用上面的方法来制定最终的迭代算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function walkInOrder(root){
if(root === null) return

const stack = []
let current = root

while(stack.length || current){
while(current){
stack.push(current)
current = current.left
}
const last = stack.pop()

// do something
console.log(last)

current = last.right
}
}

后序遍历

下面是一颗树的后序遍历的样子:

1
left node -> right node -> root node

诀窍:

对于任何树的快速手动后序遍历:一个接一个地提取所有最左边的叶节点。

实现:

让我们深入研究这种遍历的实际实现。

递归:

1
2
3
4
5
6
7
8
9
10
function walkPostOrder(root){
if(root === null) return

if(root.left) walkPostOrder(root.left)
if(root.right) walkPostOrder(root.right)

// do something here
console.log(root.val)

}

迭代:我们已经有了用于前序遍历的迭代算法。 我们可以用那个吗? 由于后序遍历似乎只是前序遍历的逆序。 让我们来看看:

1
2
3
4
5
6
7
8
// PreOrder:
root -> left -> right

// Reverse of PreOrder:
right -> left -> root

// But PostOrder is:
left -> right -> root

这里有一个细微的区别。但是我们可以通过稍微修改前序算法,然后对其进行逆序,从而得到后序结果。总体算法如下:

1
2
3
4
5
// record result using 
root -> right -> left

// reverse result
left -> right -> root
  • 使用与上面的迭代前序算法类似的方法,使用临时

    1
    + 唯一的例外是我们使用 ```root-> right-> left``` 而不是 ```root-> left-> right

  • 将遍历序列记录在一个数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    + ```结果```的逆序给出了后序遍历

    ```js
    function walkPostOrder(root){
    if(root === null) return []

    const tempStack = [root], result = []

    while(tempStack.length){
    const last = tempStack.pop()

    result.push(last)

    if(last.left) tempStack.push(last.left)
    if(last.right) tempStack.push(last.right)
    }

    return result.reverse()
    }

额外:JavaScript 提示

如果我们可以通过以下方式遍历树该多好:

1
2
3
for(let node of walkPreOrder(tree) ){
console.log(node)
}

看起来真的很好,而且很容易阅读,不是吗? 我们所要做的就是使用一个

函数,它会返回一个迭代器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

以下是我们如何修改上面的 ```walkPreOrder``` 函数,使其按照上面共享的示例运行:

```js
function* walkPreOrder(root){
if(root === null) return

const stack = [root]
while(stack.length){
const item = stack.pop()
yield item
if(item.right) stack.push(item.right)
if(item.left) stack.push(item.left)
}
}

推荐理由

本文(配有多图)介绍了树结构在 JavaScript 语言里面如何遍历,写得浅显易懂,解释了广度优先、深度优先等多种方法的实现,翻译难免有出入,欢迎斧正!

原文:https://stackfull.dev/tree-data-structure-in-javascript

不用递归生成无限层级的树

发表于 2021-07-08 | 分类于 JavaScript

不用递归生成无限层级的树

偶然间,在技术群里聊到生成无限层级树的老话题,故此记录下,n年前一次生成无限层级树的解决方案

业务场景

处理国家行政区域的树,省市区,最小颗粒到医院,后端回包平铺数据大小1M多,前端处理数据后再渲染,卡顿明显

后端返回的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[
{
"id": 1,
"name": "中华人民共和国",
"parentId": 0,
},
{
"id": 1001,
"name": "浙江省",
"parentId": 1,
},
{
"id": 2001,
"name": "杭州市",
"parentId": 1001,
},
{
"id": 3001,
"name": "西湖区",
"parentId": 2001,
},
{
"id": 4001,
"name": "杭州市第一人民医院",
"parentId": 3001,
},
// 其他略
]

第一版:递归处理树

常规处理方式

1
// 略,网上一抓一把

第二版:非递归处理树

改进版处理方式

1
2
3
4
5
6
7
8
const buildTree = (itemArray, { id = 'id', parentId = 'parentId', children = 'children', topLevelId = '0' } = {}) => {
return itemArray.filter((item) => {
// 挂载子级
item[children] = itemArray.filter((child) => String(item[id]) === String(child[parentId]));
// 返回顶层数据
return String(item[parentId]) === topLevelId;
});
};

时间复杂度:O(n^2)

第三版:非递归处理树

1
2
3
4
5
6
7
8
9
10
11
import { groupBy } from 'lodash';

const buildTree = (itemArray, { id = 'id', parentId = 'parentId', children = 'children', topLevelId = '0' } = {}) => {
const parentObj = groupBy(itemArray, parentId)
return itemArray.filter((item) => {
// 挂载子级
item[children] = parentObj[item[id]];
// 返回顶层数据
return String(item[parentId]) === topLevelId;
});
};

时间复杂度:O(2n)

最终版:非递归处理树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const buildTree = (itemArray, { id = 'id', parentId = 'parentId', children = 'children', topLevelId = '0' } = {}) => {
const parentMap = new Map(); // 临时存储所有父级
const topLevelResult = []; // 存储顶层结果
for(let item of itemArray) {
if(!parentMap.has(item[id])) {
item[children] = []
} else {
item[children] = parentMap.get(item[id])[children];
}

parentMap.set(item.id, item)

if(!parentMap.has(item[parentId])) {
parentMap.set(item[parentId], {
[children]: []
});
}
parentMap.get(item[parentId])[children].push(item)
if (String(item[parentId]) === String(topLevelId)) {
topLevelResult.push(item)
}
}
return topLevelResult;
}

时间复杂度:O(n)

基于qiankun微前端实战+部署粗略笔记(跳过原理)

发表于 2021-04-28 | 分类于 Reactjs

基于qiankun微前端实战+部署粗略笔记(跳过原理)

因业务需要,以下文字纯个人qiankun实战学习笔记,不谈原理只记操作过程,内容难免有纰漏部分,敬请不吝赐教批评指正。

目标场景

预备知识点

  • 已对qiankun微前端有了初步认识;
  • 熟悉react、vuejs;
  • 了解github、docker、jenkins、nginx;

技术栈

基座

  • 使用create-react-app初始化项目;
  • 安装

    "^2.4.0"```;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    + 代码地址:[react-app-qiankun-main](https://github.com/niexq/react-app-qiankun-main);
    + 独立仓库,独立部署,独立域名:https://qiankun.xiaoqiang.tech;

    #### react子应用
    + 使用[create-react-app](https://github.com/facebook/create-react-app)初始化项目;
    + 安装```"react-app-rewired": "^2.1.8"```、```"react-router-dom": "^5.2.0"```;
    + 代码地址:[react-app-qiankun-sub](https://github.com/niexq/react-app-qiankun-sub);
    + 独立仓库,独立部署,独立域名:https://react.xiaoqiang.tech;

    #### vue子应用
    + 使用[vue-cli](https://github.com/vuejs/vue-cli)初始化项目,对应```"vue": "^3.0.0"```;
    + 安装```"vue-router": "^4.0.0-beta.11"```;
    + 代码地址:[vue-cli-qiankun-sub](https://github.com/niexq/vue-cli-qiankun-sub);
    + 独立仓库,独立部署,独立域名:https://vue.xiaoqiang.tech;

    ### 快速上手

    #### 基座

    + 1.初始化项目
    ```bash
    npm init react-app react-app-qiankun-main

  • 2.安装

    1
    2
    ```bash
    yarn add qiankun # 或者 npm i qiankun -S

  • 3.目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    react-app-qiankun-main
    ├── .env.local // 本地环境
    ├── .env.development.local // 测试环境
    ├── .env.production.local // 生产环境
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │ ├── favicon.ico
    │ ├── index.html
    │ └── manifest.json
    └── src
    ├── components
    │ └── Loading.jsx
    ├── store
    │ └── store.js // 主应用的全局状态
    ├── apps.js // 子应用配置
    ├── App.css
    ├── App.js // 基座布局,挂载子应用
    ├── App.test.js
    ├── index.css
    ├── index.js // 主应用中注册微应用
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js

基座(开撸代码)

  • 新增3个.env文件,主要配置不同环境的对应的域名

    • .env/.env.development.local(此处暂未区分本地和测试的域名,所有环境变量值都保持一致)

      1
      2
      3
      REACT_APP_SUB_REACT=//localhost:2233/react
      REACT_APP_SUB_VUE=//localhost:3344/vue
      PORT=1122
    • .env.production.local (生产环境)

      1
      2
      REACT_APP_SUB_REACT = //qiankun.xiaoqiang.tech/react
      REACT_APP_SUB_VUE = //qiankun.xiaoqiang.tech/vue
  • 修改index.html挂载dom的默认id,防止与子应用id冲突

    1
    2
    // 默认root => main-root
    <div id="main-root"></div>
  • 新增store/store.js,配置主应用的全局状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { initGlobalState } from 'qiankun';

    const initialState = {
    user: {
    name: 'qiankun'
    }
    };

    const actions = initGlobalState(initialState);

    actions.onGlobalStateChange((state, prev) => {
    for(const key in state) {
    initialState[key] = state[key];
    }
    })

    // 非官方api,https://github.com/umijs/qiankun/pull/729
    actions.getGlobalState = (key) => {
    return key ? initialState[key] : initialState;
    }

    export default actions;
  • 修改src/App.js,主要完成基座页面布局及增加挂载子应用的dom(id=”subapp-viewport”)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function App(props) {
    // ...省略,详细可见源码
    return (
    <>
    <div className="mainapp">
    {/* 标题栏 */}
    <header className="mainapp-header">
    <ul className="mainapp-header-sidemenu">
    {/* 侧边栏 省略,详细可见源码 */}
    </ul>
    </header>
    <div className="mainapp-main">
    {/* 子应用 */}
    <main id="subapp-viewport"></main>
    </div>
    </div>
    </>
    );
    }
  • 增加apps.js,子应用的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import store from './store/store'
    const microApps = [
    {
    name: 'react',
    entry: process.env.REACT_APP_SUB_REACT,
    activeRule: '/react',
    container: '#subapp-viewport',
    },
    {
    name: 'vue',
    entry: process.env.REACT_APP_SUB_VUE,
    activeRule: '/vue',
    container: '#subapp-viewport',
    },
    ]

    const apps = microApps.map(item => {
    return {
    ...item,
    props: {
    routerBase: item.activeRule,
    getGlobalState: store.getGlobalState,
    }
    }
    })

    export default apps
  • 修改src/index.js,主应用中注册微(子)应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import { registerMicroApps, start, setDefaultMountApp } from 'qiankun';
    import apps from './apps'

    function render({ appContent, loading }) {
    const container = document.getElementById('main-root');
    ReactDOM.render(
    <React.StrictMode>
    <App loading={loading} content={appContent} />
    </React.StrictMode>,
    container,
    )
    }

    const loader = loading => render({ loading });

    render({ loading: true });

    const microApps = apps.map((app => ({
    ...app,
    loader,
    })))
    registerMicroApps(microApps, {
    beforeLoad: app => {
    console.log('before load app.name=====>>>>>', app.name)
    },
    beforeMount: [
    app => {
    console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
    }
    ],
    afterMount: [
    app => {
    console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
    }
    ],
    afterUnmount: [
    app => {
    console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
    }
    ]
    })

    setDefaultMountApp('/react')

    start();
  • 本地启动

    1
    npm start

react子应用

  • 1.初始化项目

    1
    npm init react-app react-app-qiankun-sub
  • 2.安装

    1
    2
    3
    ```bash
    npm i react-app-rewired --save-dev
    npm i react-router-dom --save

  • 3.目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    react-app-qiankun-sub
    ├── .env // 本地环境
    ├── config-overrides.js // 覆盖create-react-app的webpack配置
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │ ├── favicon.ico
    │ ├── index.html
    │ └── manifest.json
    └── src
    ├── components
    │ └── LibVersion.jsx
    ├── pages
    │ └── Home.jsx
    ├── public-path.js // __webpack_public_path__
    ├── App.css
    ├── App.js // 子应用布局
    ├── App.test.js
    ├── index.css
    ├── index.js // 子应用入口,挂载dom导出相应的生命周期钩子
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js

react子应用(开撸代码)

  • 新增1个.env文件,主要配置本地环境

    此处PORT需要和基座

    1
    2
    ```js
    PORT=2233

  • 修改index.html挂载dom的默认id,防止与基座及其他子应用id冲突

    1
    2
    // 默认root => sub-react-root
    <div id="sub-react-root"></div>
  • 新增src/public-path.js,webpack_public_path

    1
    2
    3
    4
    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  • 修改src/App.js,主要完成子应用页面布局(略,见源码)

  • 修改src/index.js,微(子)应用导出相应的生命周期钩子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import './public-path';
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';

    function getSubRootContainer(container) {
    return container ? container.querySelector('#sub-react-root') : document.querySelector('#sub-react-root');
    }

    function render(props) {
    const { container } = props;
    ReactDOM.render(
    <React.StrictMode>
    <App store={{...props}} />
    </React.StrictMode>,
    getSubRootContainer(container),
    );
    }

    function storeTest(props) {
    props.onGlobalStateChange((value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true);
    props.setGlobalState({
    ignore: props.name,
    user: {
    name: props.name,
    },
    });
    }

    if (!window.__POWERED_BY_QIANKUN__) {
    render({});
    }

    export async function bootstrap() {
    console.log('react app bootstraped');
    }

    export async function mount(props) {
    console.log('props from main framework', props);
    storeTest(props);
    render(props);
    }

    export async function unmount(props) {
    const { container } = props;
    ReactDOM.unmountComponentAtNode(getSubRootContainer(container));
    }
  • 增加config-overrides.js,覆盖create-react-app的webpack配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const { name } = require('./package');
    module.exports = {
    webpack: config => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    return config;
    },
    devServer: (configFunction) => {
    return (proxy, allowedHost) => {
    const config = configFunction(proxy, allowedHost);
    config.historyApiFallback = true;
    config.open = false;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;
    config.headers = {
    'Access-Control-Allow-Origin': '*',
    };
    return config;
    }
    }
    }
  • 修改package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "scripts": {
    - "start": "react-scripts start",
    + "start": "react-app-rewired start",
    - "build": "react-scripts build",
    + "build": "react-app-rewired build",
    - "test": "react-scripts test",
    + "test": "react-app-rewired test",
    "eject": "react-scripts eject"
    },
  • 本地启动

    1
    npm start

vue子应用

  • 1.初始化项目

    1
    2
    npm install -g @vue/cli-service-global
    vue create vue-cli-qiankun-sub
  • 2.安装

    1
    2
    ```bash
    npm i vue-router --save

  • 3.目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    vue-cli-qiankun-sub
    ├── .env // 本地环境
    ├── vue.config.js // vue可选的配置文件
    ├── babel.config.js
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │ ├── favicon.ico
    │ ├── index.html
    │ └── manifest.json
    └── src
    ├── components
    │ └── HelloWorld.vue
    ├── router
    │ └── index.js
    ├── views
    │ └── Home.vue
    ├── public-path.js // __webpack_public_path__
    ├── App.vue // 子应用布局
    └── main.js // 子应用入口,挂载dom导出相应的生命周期钩子

vue子应用(开撸代码)

  • 新增1个.env文件,主要配置本地环境

    此处PORT需要和基座

    1
    2
    ```js
    PORT=3344

  • 修改index.html挂载dom的默认id,防止与基座及其他子应用id冲突

    1
    2
    // 默认root => sub-vue-root
    <div id="sub-vue-roott"></div>
  • 新增src/public-path.js,webpack_public_path

    1
    2
    3
    4
    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  • 修改src/App.vue,主要完成子应用页面布局(略,见源码)

  • 修改src/mian.js,微(子)应用导出相应的生命周期钩子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    import './public-path';
    import { createApp } from 'vue';
    import { createRouter, createWebHistory } from 'vue-router';
    import App from './App.vue';
    import routes from './router';
    import store from './store';

    let router = null;
    let instance = null;
    let history = null;


    function render(props = {}) {
    const { container } = props;
    history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue' : '/');
    router = createRouter({
    history,
    routes,
    });

    instance = createApp(App);
    instance.use(router);
    instance.use(store);
    instance.mount(container ? container.querySelector('#sub-vue-root') : '#sub-vue-root');
    }

    if (!window.__POWERED_BY_QIANKUN__) {
    render();
    }

    export async function bootstrap() {
    console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
    }

    function storeTest(props) {
    props.onGlobalStateChange &&
    props.onGlobalStateChange(
    (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
    true,
    );
    props.setGlobalState &&
    props.setGlobalState({
    ignore: props.name,
    user: {
    name: props.name,
    },
    });
    }

    export async function mount(props) {
    storeTest(props);
    render(props);
    instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
    instance.config.globalProperties.$setGlobalState = props.setGlobalState;
    }

    export async function unmount() {
    instance.unmount();
    instance._container.innerHTML = '';
    instance = null;
    router = null;
    history.destroy();
    }
  • 增加vue.config.js配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    const path = require('path');
    const { name } = require('./package');

    function resolve(dir) {
    return path.join(__dirname, dir);
    }

    const port = process.env.PORT;

    module.exports = {
    outputDir: 'dist',
    assetsDir: 'static',
    filenameHashing: true,
    devServer: {
    hot: true,
    disableHostCheck: true,
    port,
    overlay: {
    warnings: false,
    errors: true,
    },
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    // 自定义webpack配置
    configureWebpack: {
    resolve: {
    alias: {
    '@': resolve('src'),
    },
    },
    output: {
    // 把子应用打包成 umd 库格式
    library: `${name}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${name}`,
    },
    },
    };
  • 修改package.json

    1
    2
    3
    "scripts": {
    + "start": "vue-cli-service serve",
    },
  • 本地启动

    1
    npm start

预览

以上操作完后,可以直接通过基座预览,子应用也可独立预览

基座预览

1
http://localhost:1122/

react子应用预览

1
http://localhost:2233/

vue子应用预览

1
http://localhost:3344/

部署

备选方案

  • 1.单域名部署;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 基座:https://qiankun.xiaoqiang.tech
    // react子应用:https://qiankun.xiaoqiang.tech/react
    // vue子应用:https://qiankun.xiaoqiang.tech/vue
    // 编译后服务器存储目录
    react-app-qiankun
    ├── main
    │ └── index.html
    ├── react
    │ └── index.html
    └── vue
    └── index.html
  • 2.多域名独立部署;(当篇笔记选择了多域名部署)

    1
    2
    3
    4
    // 基座:https://qiankun.xiaoqiang.tech
    // 编译后服务器项目独立存储目录
    react-app-qiankun-main
    └── index.html
1
2
3
4
// react子应用:https://react.xiaoqiang.tech
// 编译后服务器项目独立存储目录
react-app-qiankun-sub
└── index.html
1
2
3
4
// vue子应用:https://vue.xiaoqiang.tech
// 编译后服务器项目独立存储目录
react-app-qiankun-main
└── index.html

部署(以下只初略记录部署过程,过于简陋)

  • 前提:已购云服务器,并已安装docker、nginx、jenkins、3个独立域名及ssl证书

  • 本地编码,github存储代码,分别新建3个公开代码库

    1
    2
    3
    // 基座:react-app-qiankun-main 存储到 https://github.com/niexq/react-app-qiankun-main
    // react子应用:react-app-qiankun-sub 存储到 https://github.com/niexq/react-app-qiankun-sub
    // vue子应用:vue-cli-qiankun-sub 存储到 https://github.com/niexq/vue-cli-qiankun-sub
  • github,jenkins持续集成

1
2
3
4
5
6
7
8
9
// 详细配置步骤略
// github webHooks设置
// jenkins构建部分执行shell
BUILD_ID=dontKillMe
cd /var/jenkins_home/workspace/react-app-qiankun-main
npm install
npm run build
rm -rf /srv/www/react-app-qiankun-main
cp -rf /var/jenkins_home/workspace/react-app-qiankun-main/build /srv/www/react-app-qiankun-main/
  • 使用nginx代理
    nginx.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104

    user root;
    worker_processes 1;

    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;


    events {
    worker_connections 1024;
    }


    http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    #tcp_nopush on;

    keepalive_timeout 65;

    #gzip on;

    #include /etc/nginx/conf.d/*.conf;

    server {
    listen 443;
    server_name qiankun.xiaoqiang.tech; # 你的域名
    ssl on;
    root www/react-app-qiankun-main; # 前台文件存放文件夹,可改成别的
    index index.html index.htm; # 上面配置的文件夹里面的index.html
    ssl_certificate cert/5543142_qiankun.xiaoqiang.tech.pem; #将domain name.pem替换成您证书的文件名。
    ssl_certificate_key cert/5543142_qiankun.xiaoqiang.tech.key; #将domain name.key替换成您证书的密钥文件名。
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
    # 用于配合 browserHistory使用
    try_files $uri $uri/ /index.html;
    # root /srv/www/react-app-qiankun-main;
    # index index.html index.htm;
    }
    }

    server {
    listen 443;
    server_name react.xiaoqiang.tech; # 你的域名
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    if ($request_method = 'OPTIONS') {
    return 204;
    }
    ssl on;
    root www/react-app-qiankun-sub; # 前台文件存放文件夹,可改成别的
    index index.html index.htm; # 上面配置的文件夹里面的index.html
    ssl_certificate cert/4325684_react.xiaoqiang.tech.pem; #将domain name.pem替换成您证书的文件名。
    ssl_certificate_key cert/4325684_react.xiaoqiang.tech.key; #将domain name.key替换成您证书的密钥文件名。
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
    # 用于配合 browserHistory使用
    try_files $uri $uri/ /index.html;
    # root /srv/www/react-app-qiankun-sub;
    # index index.html index.htm;
    }
    }

    server {
    listen 443;
    server_name vue.xiaoqiang.tech; # 你的域名
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    if ($request_method = 'OPTIONS') {
    return 204;
    }
    ssl on;
    root www/vue-cli-qiankun-sub; # 前台文件存放文件夹,可改成别的
    index index.html index.htm; # 上面配置的文件夹里面的index.html
    ssl_certificate cert/5556275_vue.xiaoqiang.tech.pem; #将domain name.pem替换成您证书的文件名。
    ssl_certificate_key cert/5556275_vue.xiaoqiang.tech.key; #将domain name.key替换成您证书的密钥文件名。
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
    # 用于配合 browserHistory使用
    try_files $uri $uri/ /index.html;
    # root /srv/www/vue-cli-qiankun-sub;
    # index index.html index.htm;
    }
    }
    }

docker运行nginx命令,重点关注

挂载目录```
1
2
```bash
docker run --name nginx -p 80:80 -p 443:443 -v /root/nginx/config/nginx.conf:/etc/nginx/nginx.conf -v /root/nginx/cert:/etc/nginx/cert -v /root/nginx/logs:/var/log/nginx -v /srv/www/react-app-qiankun-main:/etc/nginx/www/react-app-qiankun-main -v /srv/www/react-app-qiankun-sub:/etc/nginx/www/react-app-qiankun-sub -v /srv/www/vue-cli-qiankun-sub:/etc/nginx/www/vue-cli-qiankun-sub --restart=always -d nginx:stable

总结

没有总结,遇到的问题太多,笔记总结的太杂,后期再整理分享

线上预览地址:https://qiankun.xiaoqiang.tech

子应用线上也可独立预览

react子应用预览:https://react.xiaoqiang.tech

vue子应用预览:https://vue.xiaoqiang.tech

参考链接

qiankun

qiankun-example

qiankun 微前端方案实践及总结

将React APP部署到GitHub Pages

发表于 2021-03-03 | 分类于 Reactjs

将React APP部署到GitHub Pages

参考react-gh-pages以及create-react-app-Deployment-GitHub Pages

react-gh-pages

步骤

前提:本地已安装nodejs,已有github账户

  • 1.在GitHub上创建一个空的repository
  • 2.使用create-react-app创建新的React应用
  • 3.重要,添加homepage到package.json
  • 4.重要,安装gh-pages并添加deploy到package.json的scripts中
  • 5.重要,将GitHub存储库添加为本地git存储库中的remote
  • 6.重要,通过运行npm run deploy部署到GitHub Pages
  • 7.对于项目页面,请确保项目设置使用gh-pages
  • 8.可选,配置域
  • 9.可选,将本地源代码提交推送到GitHub的master分支
  • 10.可选,故障排除

1.在GitHub上创建一个空的repository

  • 输入自定义的Repository name
  • 其他为空,不用初始化README.md,.gitignore,license,保持repository干净无文件

2.使用create-react-app创建新的React应用

1
2
3
4
5
6
7
8
// npx
npx create-react-app react-gh-pages

// or npm
npm init react-app react-gh-pages

// or yarn
yarn create react-app react-gh-pages

3.重要,添加homepage到package.json

打开项目,然后为项目package.json添加一个homepage字段:

1
2
3
4
5
6
7
8

"homepage": "https://myusername.github.io/react-gh-pages",

// 或GitHub用户页面
"homepage": "https://myusername.github.io",

// 或自定义域页面:
"homepage": "https://mywebsite.com",

Create React App使用该homepage字段来确定所构建的HTML文件中的根URL。

4.重要,安装gh-pages并添加deploy到package.json的scripts中

进入项目,安装gh-pages

1
2
cd react-gh-pages
npm install gh-pages --save-dev

添加以下deploy脚本到项目的package.json中:

1
2
3
4
5
  "scripts": {
+ "predeploy": "npm run build",
+ "deploy": "gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",

该predeploy脚本将自动运行在deploy运行之前。

(可忽略)如果要部署到GitHub用户页面而不是项目页面,则需要进行其他修改:

调整package.json脚本以将部署推送到master:

1
2
3
4
  "scripts": {
"predeploy": "npm run build",
- "deploy": "gh-pages -d build",
+ "deploy": "gh-pages -b master -d build",

5.重要,将GitHub存储库添加为本地git存储库中的remote

1
git remote add origin https://github.com/myusername/react-gh-pages.git

6.重要,通过运行npm run deploy部署到GitHub Pages

1
npm run deploy

7.对于项目页面,请确保项目设置使用gh-pages

确保将GitHub项目设置中的GitHub Pages选项设置为使用gh-pages分支:

gh-pages-settings
gh-pages-settings

8.可选,配置域

可以通过新增CNAME文件到public/目录来使用GitHub Pages配置自定义域。

CNAME文件应如下所示:

1
mywebsite.com

9.可选,将本地源代码提交推送到GitHub的master分支

1
2
3
git add .
git commit -m "feat: Create a React app and publish it to GitHub Pages"
git push origin master

10.可选,故障排除

  • 如果在部署时遇到/dev/tty: No such a device or address错误或类似错误,请尝试以下操作:

    • 创建一个新的个人访问令牌
    • git remote set-url origin https://:@github.com//
    • 再运行npm run deploy
  • 如果在部署时获得Cannot read property ‘email’ of null,请尝试以下操作:

    • git config –global user.name ‘<your_name>’
    • git config –global user.email ‘<your_email>’
    • 再运行npm run deploy

最后

如果操作了第7步配置了自定义域名域名,可访问:https://mywebsite.com/react-gh-pages

如未配置域名,访问:https://myusername.github.io/react-gh-pages

也可通过GitHub-settings-GitHub Pages查看:

website

在CentOS7系统使用Docker构建Jenkins

发表于 2021-02-08 | 分类于 Docker

在CentOS7系统使用Docker构建Jenkins

详情可参照官方安装连接,以下只记录一些关键步骤

前提

假设docker已安装的前提下,如需了解CentOS7系统安装Docker,可参照笔记ecs操作部分步骤-安装docker

准备

  • 需要了解docker基本命令操作
  • 登录ecs服务器,创建jenkins挂载目录
  • 登录dockerhub,选择官方版本及阅读说明文档;也可使用docker search -s 100 ‘jenkins’直接搜索jenkins镜像start大于100的

设置卷位置

在设置其他所有内容之前,请配置一个新的环境变量$GITLAB_HOME ,该变量指向配置,日志和数据文件将驻留的目录。确保目录存在并且已授予适当的权限。

对于Linux用户,将路径设置为/srv/gitlab:

1
export JENKINS_HOME=/srv/jenkins

对于macOS用户,请使用用户$HOME/gitlab目录:

1
export JENKINS_HOME=/srv/jenkins

额外加个nginx环境变量,方便jenkins shell构建使用

1
export NGINX_WWW=/srv/www

Linux,macOS查看环境变量

1
env

安装

1
2
3
4
5
// 1.从docker镜像仓库拉取最新的jenkins(一般选择lts,长期支持的版本,会定时发布系统更新)(https://hub.docker.com/r/jenkins/jenkins)
docker pull jenkins/jenkins:lts

// 2.查看已安装的docker jenkins
docker images | grep "jenkins"

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 官方配置
docker run \
-u root \
--rm \ // 关闭时自动删除Docker容器
-d \ // 在后台运行容器(即“分离”模式)并输出容器ID
-p 7199:8080 \ // 容器的端口映射
-p 50000:50000 \ // 主站进行通信,容器的端口映射
--name jenkins // 容器名称
-v $JENKINS_HOME:/var/jenkins_home \ // 磁盘挂载
-v $NGINX_WWW:/srv/www \ // 磁盘挂载,重要,此挂载可以达到在容器内部cp数据到host
-v /var/run/docker.sock:/var/run/docker.sock \ // jenkins容器与Docker守护进程通信
docker.io/jenkins/jenkins:lts // 安装的镜像包名 REPOSITORY:TAG

// 简化版
docker run --name jenkins -u root -d -p 7199:8080 -p 50000:50000 -v $JENKINS_HOME:/var/jenkins_home -v $NGINX_WWW:/srv/www docker.io/jenkins/jenkins:lts

// 查看运行的容器
docker container ls
// 或
docker ps

访问

1
2
3
4
5
// 本地访问(忽略)
http://loalhost:7199

// 局域网访问(注意:可查看域名配置-设置白名单笔记,配置安全组规则,允许7199入方向)
http://服务器ip:7199

其他,安装jenkins后设置向导

  • 自动跳转输入管理员密码,解锁Jenkins
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 首屏提示
    Please wait while Jenkins is getting ready to work ...
    Your browser will reload automatically when Jenkins is ready.

    // 自动跳转设置管理员密码页面
    解锁 Jenkins
    为了确保管理员安全地安装 Jenkins,密码已写入到日志中(不知道在哪里?)该文件在服务器上:
    /var/jenkins_home/secrets/initialAdminPassword
    请从本地复制密码并粘贴到下面。
    管理员密码

管理员密码页面

  • 查看登录密码

    • 如果您以分离模式在Docker中运行Jenkins,则可以从Docker日志(above) 访问Jenkins控制台日志

    • Jenkins控制台日志显示可以获取密码的位置(在Jenkins主目录中)。 必须在新Jenkins安装中的安装向导中输入此密码才能访问Jenkins的主UI。 如果您在设置向导中跳过了后续的用户创建步骤, 则此密码还可用作默认admininstrator帐户的密码(使用用户名“admin”)

1
2
3
4
5
// 直接在挂载的目录下查看,返回密码
cat /jenkins/secrets/initialAdminPassword
// 进入容器内部获取密码
docker exec -it "容器ID" bash
cat /var/jenkins_home/secrets/initialAdminPassword
  • 直接粘贴到管理员密码输入框中即可(一般为一串加密的hash值)
  • 新手入门-可选择安装推荐的插件(也可选择自定义插件)
  • 创建第一个管理员用户
  • 实例配置

    1
    2
    3
    Jenkins URL: http://服务器ip:7199
    Jenkins URL 用于给各种Jenkins资源提供绝对路径链接的根地址。 这意味着对于很多Jenkins特色是需要正确设置的,例如:邮件通知、PR状态更新以及提供给构建步骤的BUILD_URL环境变量。
    推荐的默认值显示在尚未保存,如果可能的话这是根据当前请求生成的。 最佳实践是要设置这个值,用户可能会需要用到。这将会避免在分享或者查看链接时的困惑。
  • Jenkins已就绪-开始使用Jenkins

在CentOS7系统使用Docker构建Jenkins

发表于 2021-02-08 | 分类于 Docker

在CentOS7系统使用Docker构建gitlab

详情可参照官方安装连接,以下只记录一些关键步骤

前提

假设docker已安装的前提下,如需了解CentOS7系统安装Docker,可参照笔记ecs操作部分步骤-阿里ECS安装docker或docker官方安装

准备

  • 需要了解docker基本命令操作
  • 登录ecs服务器,创建jenkins挂载目录
  • 登录dockerhub,选择官方社区版本及阅读说明文档;也可使用docker search -s 100 ‘gitlab’直接搜索gitlab镜像start大于100的

设置卷位置

在设置其他所有内容之前,请配置一个新的环境变量$GITLAB_HOME ,该变量指向配置,日志和数据文件将驻留的目录。确保目录存在并且已授予适当的权限。

对于Linux用户,将路径设置为/srv/gitlab:

1
export GITLAB_HOME=/srv/gitlab

对于macOS用户,请使用用户$HOME/gitlab目录:

1
export GITLAB_HOME=$HOME/gitlab

Linux,macOS查看环境变量

1
env

GitLab容器使用主机安装的卷来存储持久数据:

当前位置 容器位置 用法
$GITLAB_HOME/data /var/opt/gitlab 用于存储应用程序数据。
$GITLAB_HOME/logs /var/log/gitlab 用于存储日志。
$GITLAB_HOME/config /etc/gitlab 用于存储GitLab配置文件。

安装

GitLab Docker镜像可以多种方式运行:

  • 使用Docker引擎
  • 使用Docker Compose
  • 使用Docker群模式
1
2
3
4
5
// 1.从docker镜像仓库拉取最新的gitlab(一般选择lts,长期支持的版本,会定时发布系统更新)(https://hub.docker.com/r/gitlab/gitlab-ce)
docker pull gitlab/gitlab-ce

// 2.查看已安装的docker gitlab
docker images | grep "gitlab"

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 官方配置
docker run --detach \
--hostname gitlab.example.com \ // 域名
--publish 443:443 \ // 将容器内443端口映射至宿主机6003端口,这是访问gitlab的端口
--publish 80:80 \ // 将容器内80端口映射至宿主机80端口,这是访问gitlab的端口
--publish 22:22 \ // 将容器内22端口映射至宿主机22端口,这是访问ssh的端口
--name gitlab \ // 容器名称
--restart always \ // 容器自启动
--volume $GITLAB_HOME/config:/etc/gitlab \ // 见上"设置卷位置录"
--volume $GITLAB_HOME/logs:/var/log/gitlab \ // 见上"设置卷位置录"
--volume $GITLAB_HOME/data:/var/opt/gitlab \ // 见上"设置卷位置录"
gitlab/gitlab-ce:latest // 安装的镜像包名 REPOSITORY:TAG(也可写镜像ID)


// 简化版
docker run --detach --hostname bot.xiaoqiang.tech --publish 7143:443 --publish 7280:80 --publish 7322:22 --name gitlab --restart always --volume $GITLAB_HOME/config:/etc/gitlab --volume $GITLAB_HOME/logs:/var/log/gitlab --volume $GITLAB_HOME/data:/var/opt/gitlab gitlab/gitlab-ce:latest


// 查看运行的容器
docker container ls
// 或
docker ps

修改gitlab配置

输入正在运行的容器:

1
docker exec -it gitlab /bin/bash

打开gitlab.rb配置文件

1
2
3
4
5
6
7
8
9
10
11
// 打开文件
vim /etc/gitlab/gitlab.rb

// 此文件是全注释的,所以直接在首行添加如下配置

# gitlab访问地址,可以写域名(默认端口为80)
external_url 'http://192.168.52.128:9980'
# ssh主机ip
gitlab_rails['gitlab_ssh_host'] = '192.168.52.128'
# ssh连接端口
gitlab_rails['gitlab_shell_ssh_port'] = 7322

/etc/gitlab/gitlab.rb用您的编辑器打开并设置external_url:

1
2
3
4
5
6
7
# For HTTP
external_url "http://gitlab.example.com:8929"

or

# For HTTPS (notice the https)
external_url "https://gitlab.example.com:8929"

此URL中指定的端口必须与Docker发布到主机的端口匹配。此外,如果未显式设置NGINX侦听端口 nginx[‘listen_port’],它将从中拉出external_url。有关更多信息,请参见NGINX文档。

设置gitlab_shell_ssh_port:

1
gitlab_rails['gitlab_shell_ssh_port'] = 7322

重启修改后的配置

1
gitlab-ctl reconfigure

最后,重启gitlab

1
gitlab-ctl restart

访问

按照上面的示例,您将能够从Web浏览器下访问GitLab,:8929并在port下使用SSH进行推送2289。

1
2
3
4
5
// 本地访问(忽略)
http://loalhost:8929

// 局域网访问(注意:可查看域名配置-设置白名单笔记,配置安全组规则,允许8929入方向)
http://服务器ip:8929

其他,安装gitlab后设置后期补充

如何将文件从Docker容器复制到主机?

发表于 2021-02-08 | 分类于 Docker

如何将文件从Docker容器复制到主机?

详细说明及使用可参考官方docker cp文档

说明

在容器和本地文件系统之间复制文件/文件夹

用法

1
2
$ docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
$ docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH

例如,docker正在运行了一个jenkins,CONTAINER ID为3a3d34f3a3c0,接下来以此容器做展示:

CONTAINER ID IMAGE 其他略 NAMES
3a3d34f3a3c0 docker.io/jenkins/jenkins:lts 其他略 jenkins

注:以下命令中使用 CONTAINER ID 的地方也可用 NAMES 代替

容器到主机

将文件或文件夹从Docker容器复制到本地文件系统。

重要:此种有两种情况需要区分,第一种你在主机,第二种你已经进入了docker jenkins容器中

  • 第一种:在主机
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 将jenkins容器中 /var/jenkins_home/project1 目录拷贝到主机的 /srv/jenkins
    $ docker cp 3a3d34f3a3c0:/var/jenkins_home/project1 /srv/jenkins

    # 将jenkins容器中 /var/jenkins_home/project1 目录中 README.md 文件拷贝到主机的 /srv/jenkins
    $ docker cp 3a3d34f3a3c0:/var/jenkins_home/project1/README.md /srv/jenkins

    # 将jenkins容器中 /var/jenkins_home/project1/build 目录中所有文件拷贝到主机的 /srv/jenkins/www
    $ docker cp 3a3d34f3a3c0:/var/jenkins_home/project1/build/. /srv/jenkins/www

    # 将jenkins容器中 /var/jenkins_home/project1/build 目录中 README.md 文件拷贝到当前本地工作目录
    $ docker cp 3a3d34f3a3c0:/var/jenkins_home/project1/build/README.md .

如果容器中不存在复制的目录或文件,则报错

  • 第二种:已经进入了docker jenkins容器中(docker exec -it “3a3d34f3a3c0” bash)

将jenkins容器中/var/jenkins_home/workspace/project2/build中所有文件拷贝到主机的/srv/www中

前置环境变量:

1
2
$ export JENKINS_HOME=/srv/jenkins
$ export NGINX_WWW=/srv/www

需要借助docker run –volume,docker jenkins启动命令需要改成:

1
$ docker run --name jenkins -u root -d -p 7199:8080 -p 50000:50000 -v $JENKINS_HOME:/var/jenkins_home -v $NGINX_WWW:/srv/www docker.io/jenkins/jenkins:lts

重要:额外增加了一个参数

$NGINX_WWW:/srv/www``` 挂载目录
1
2
3
4

最后,直接执行即可
```js
$ cp -rf /var/jenkins_home/workspace/project2/build/. /srv/www/

主机到容器

将文件或文件夹从本地文件系统复制到Docker容器,其工作原理相同

1
2
3
4
5
6
7
8
9
10
11
# 将主机的 /srv/jenkins 拷贝到jenkins容器中 /var/jenkins_home/project1 目录
$ docker cp /srv/jenkins 3a3d34f3a3c0:/var/jenkins_home/project1

# 将主机的 /srv/jenkins 目录中 README.md 拷贝到jenkins容器中 /var/jenkins_home/project1文件
$ docker cp /srv/jenkins/README.md 3a3d34f3a3c0:/var/jenkins_home/project1

# 将主机的 /srv/jenkins/www 中所有文件拷贝到jenkins容器中 /var/jenkins_home/project1/build 目录
$ docker cp /srv/jenkins/www/. 3a3d34f3a3c0:/var/jenkins_home/project1/build

# 将当前本地工作目录拷贝到jenkins容器中 /var/jenkins_home/project1/build 目录中
$ docker cp . 3a3d34f3a3c0:/var/jenkins_home/project1/build

Element.scrollIntoView()让当前的元素滚动到浏览器窗口的可视区域内

发表于 2019-07-29 | 分类于 JavaScript

Element.scrollIntoView()

让当前的元素滚动到浏览器窗口的可视区域内

语法

1
2
3
element.scrollIntoView();                       // 等同于element.scrollIntoView(true) 
element.scrollIntoView(alignToTop); // Boolean型参数
element.scrollIntoView(scrollIntoViewOptions); // Object型参数

参数

alignToTop(一个Boolean值)

  • 如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。相应的 scrollIntoViewOptions: {block: “start”, inline: “nearest”}。这是这个参数的默认值。
  • 如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。相应的scrollIntoViewOptions: {block: “end”, inline: “nearest”}。

scrollIntoViewOptions(可选)(一个带有选项的object)

  • behavior(可选)
    • 定义缓动动画, “auto”, “instant”, 或 “smooth” 之一。默认为 “auto”。
  • block(可选)
    • “start”, “center”, “end”, 或 “nearest”之一。默认为 “start”。
  • inline(可选)
    • “start”, “center”, “end”, 或 “nearest”之一。默认为 “nearest”。
1
2
3
4
{
behavior: "auto" | "instant" | "smooth",
block: "start" | "end",
}

浏览器兼容性
浏览器兼容性预览图

demo

1
2
3
4
5
6
var element = document.getElementById("box");

element.scrollIntoView();
element.scrollIntoView(false);
element.scrollIntoView({block: "end"});
element.scrollIntoView({behavior: "instant", block: "end", inline: "nearest"});

注意

取决于其它元素的布局情况,此元素可能不会完全滚动到顶端或底端。

公司真实项目实践

表格编辑功能,当用户选择左固定(或右固定)时,表格字段自动布局左边(或右边),滚动条滚动到对应位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 部分代码实现
// 左固定Checkbox点击事件
handleLeftCheckbox = (row, checked) => {
row.fixed = checked ? 'left' : '';
this.setState({
tableConfigs: this.state.tableConfigs,
}, () => {
this.scrollIntoViewData(row);
})
}


// 当前设置元素滚动到浏览器窗口可视区域(getElementById可换成ref方式)
scrollIntoViewData = (rowData) => {
if (rowData.fixed) { // 此属性代表当前字段已设置左固定(或右固定)[左固定'left'\右固定'right']
document.getElementById(`${rowData.key}_${rowData.keyName}`).scrollIntoView({
block: 'end',
inline: 'end',
behavior: 'smooth'
});
}
}

实现效果截图

实现效果截图预览图

有关更多详细信息,请参阅:

本篇参阅

react table rowspan小记

发表于 2019-06-26 | 分类于 JavaScript

react table rowspan小记

理解表格跨行实现,重点掌握下rowspan

定义和用法

rowspan 属性规定单元格可横跨的行数。

1
<td rowspan="value">

react代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// react 组件部分代码
getTableData = () => {
const data = [
{
name: '上衣',
children: [
{ name: '红色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '白色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '黑色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '绿色', childChildren: ['L', 'XL', 'XXL'] },
]
},
{
name: '牛仔裤',
children: [
{ name: '红色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '白色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '黑色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '绿色', childChildren: ['L', 'XL', 'XXL'] },
]
},
{
name: '鞋子',
children: [
{ name: '红色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '白色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '黑色', childChildren: ['L', 'XL', 'XXL'] },
{ name: '绿色', childChildren: ['L', 'XL', 'XXL'] },
]
},
];
const rowData = [];
data.forEach((firstItem, firstIndex) => {
const firstRowSpan = firstItem.children.reduce((total, curItem) => total + curItem.childChildren.length, 0); // 重点: 第一列"名称"的rowspan值,取决于最小单元的行数
firstItem.children.forEach((secondItem, secondIndex) => {
const secondRowSpan = secondItem.childChildren.length; // 重点: 第二列"颜色"的rowspan值,取决于它子集的行数(以此类推)
secondItem.childChildren.forEach((thirdItem, thirdIndex) => {
const tdData = [];
if (secondIndex === 0 && thirdIndex === 0) { // 重点:判断第一列"名称"是否需要插入对应dom
tdData.push(<td className={styles.td} rowSpan={firstRowSpan}>{firstItem.name}</td>)
}
if (thirdIndex === 0) { // 重点:判断第二列"颜色"是否需要插入对应dom
tdData.push(<td className={styles.td} rowSpan={secondRowSpan}>{secondItem.name}</td>)
}
rowData.push((
<tr key={`row${firstIndex}${secondIndex}${thirdIndex}`}>
{
tdData
}
<td className={styles.td}>{thirdItem}</td>
</tr>
))
})
})
})
return rowData;
}
render() {
return (
<table className={styles.table}>
<thead>
<tr>
<th className={styles.th}>名称</th>
<th className={styles.th}>颜色</th>
<th className={styles.th}>规格</th>
</tr>
</thead>
<tbody>
{
this.getTableData()
}
</tbody>
</table>
)
}


// css

table{
border: 1px solid black !important;
border-bottom: 0px;
border-right: 0px;
border-collapse: collapse;
width: 100%;
height: 100%;
th{
text-align: center;
padding: 6px 0;
border: 1px solid black !important;
font-size: 8px;
color: #4D4D4D;
letter-spacing: 0.11px;
line-height: unset!important;
}
td{
padding:6px 0;
border: 1px solid black !important;
font-size: 8px;
color: #4D4D4D;
letter-spacing: 0.11px;
line-height: unset!important;
}
}

个人理解图(自绘,欢迎批评指正)

操作效果图

跨行期望效果图






































tabl






















































名称 颜色 规格
上衣 红色 L
XL
XXL
白色 L
XL
XXL
黑色 L
XL
XXL
绿色 L
XL
XXL
牛仔裤 红色 L
XL
XXL
白色 L
XL
XXL
黑色 L
XL
XXL
绿色 L
XL
XXL

Flow:Facebook 的 JavaScript 静态类型检查器

发表于 2019-05-29 | 分类于 JavaScript

Flow:Facebook 的 JavaScript 静态类型检查器

Flow是JavaScript代码的静态类型检查器。 它可以帮助您提高工作效率。 让您的代码更快,更智能,更自信,更大规模。

Flow通过静态类型注释检查代码是否存在错误。 这些类型允许您告诉Flow您希望代码如何工作,Flow将确保它以这种方式工作。

github地址:flow

flow文档地址:flow


1.从demo开始认识flow

2.安装,配置

3.flow总结及使用


1.从demo开始认识flow

1.1 出入参静态类型注释

1
2
3
4
5
6
// @flow
function square(n: number): number {
return n * n;
}

square("2"); // Error!

报错信息:

Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ common/globFile.js:26:8

Cannot call square with ‘2’ bound to n because string [1] is incompatible with number [2].

1.2.运算结果类型检查

因为Flow很好地理解JavaScript,所以它不需要很多这些类型。 你应该只需要做很少的工作来描述你的Flow代码,它将推断其余部分。 在很多时候,Flow可以完全理解您的代码而不需要任何类型

1
2
3
4
5
6
// @flow
function square(n) {
return n * n; // Error!
}

square("2");

报错信息:
Cannot perform arithmetic operation because string [1] is not a number.

2.安装

2.1 安装编译器

官方推荐babel或flow-remove-types

1
npm install --save-dev @babel/cli @babel/preset-flow

项目增加babel.config.js文件

1
2
3
4
5
6
7
module.exports = function() {
return {
presets: [
"@babel/preset-flow"
]
}
}

package.json中添加scripts

1
2
3
4
5
6
7
8
9
10
{
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/preset-flow": "^7.0.0",
},
"scripts": {
"build": "babel src/ -d lib/",
"prepublish": "npm run build"
}
}

2.2 安装flow

1
npm install --save-dev flow-bin

package.json中添加scripts

1
2
3
4
5
6
7
8
{
"devDependencies": {
"flow-bin": "^0.99.0"
},
"scripts": {
"flow": "flow"
}
}

生成flowconfig配置文件

1
npm run flow init

运行flow

1
npm run flow

3.flow总结及使用

  • 3.1 使用flow init初始化项目
  • 3.2 使用flow启动Flow后台进程flow status
  • 3.3 使用// @flow确定Flow将监视哪些文件
  • 3.4 编写flow代码
  • 3.5 检查代码是否存在类型错误
  • 3.6 如何在代码中添加类型注释

3.1 使用flow init 初始化项目

生成类似INI格式,项目.flowconfig配置文件

3.1.1 .flowconfig由6个部分组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
; 忽略匹配文件
[ignore]
<PROJECT_ROOT>/__tests__/.*
<PROJECT_ROOT>/lib/.*

; 包含指定的文件或目录
[include]
<PROJECT_ROOT>/src/.*

; 在类型检查代码时包含指定的库定义
[libs]

; lint
[lints]
all=warn
untyped-type-import=error
sketchy-null-bool=off

; 选项
[options]
all=true
esproposal.decorators=ignore
experimental.const_params=true
module.file_ext=.bar
module.use_strict=true

; 严格
[strict]
nonstrict-import
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import


; none
; 在声明模式下,代码没有进行类型检查,会检查文件内容
[declarations]
<PROJECT_ROOT>/third_party/.*

; 不检查文件内容,不匹配指定正则表达式的类型文件,丢弃类型并将模块视为任何模块
[untyped]
<PROJECT_ROOT>/third_party/.*

; 指定flow使用的版本
[version]
0.98.1

3.1.2 # or ; or 💩 are ignored

1
2
3
4
5
6
# This is a comment
# This is a comment
; This is a comment
; This is a comment
💩 This is a comment
💩 This is a comment

3.1.3 .flowconfig放置位置

.flowconfig的位置非常重要。Flow将包含.flowconfig的目录视为项目根目录。 默认情况下,Flow包含项目根目录下的所有源代码

3.2 使用flow启动flow后台进程

vscode推荐安装Flow Language Support

1
2
flow status // 启动flow后台进程
flow stop // 终止flow后台进程

webpack热加载,使用flow-webpack-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use strict';

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const FlowWebpackPlugin = require('flow-webpack-plugin');

module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './example/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
devServer: {
hot: true,
disableHostCheck: true,
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({ template: 'example/index.html' }),
new FlowWebpackPlugin({
flowArgs: ['check']
})
],
};

3.3 使用// @flow确定Flow将监视哪些文件

Flow后台进程使用此标志收集所有文件,并使用所有这些文件中提供的类型信息来确保一致性和无错误编程

使用JavaScript注释的形式,注释@flow

1
2
3
4
5
// @flow

或

/* @flow */

忽略//@flow,检查所有文件

1
flow check --all

3.4 编写flow代码

Flow后台进程将会捕获此错误

1
2
3
4
5
6
7
8
// @flow

function foo(x: ?number): string {
if (x) {
return x;
}
return "default string";
}

3.5 检查代码是否存在类型错误

1
2
# equivalent to `flow status`
flow

运行flow检查

1
2
3
4
5
6
7
8
// @flow

function foo(x: ?number): string {
if (x) {
return x; // Cannot return `x` because number [1] is incompatible with string [2].
}
return "default string";
}

3.6 如何在代码中添加类型注释

类型注释符号

1
2
3
|       // 或
& // 且
? // 可选

类型注释中包括的类型

1
2
3
4
5
6
7
8
9
10
boolean                                 // true or new Boolean(false)
string // "hello" or new String("world")
number // 3.14 or new Number(42)
null // null
undefined (void in Flow types) // undefined
Array (其中T用来描述数组中值的类型) // Array<T>
Object // {}
Function // function
class // class
Symbols (not yet supported in Flow) // Symbol("foo")

小写

1
2
3
4
5
6
// @flow
function method(x: number, y: string, z: boolean) {
// ...
}

method(3.14, "hello", true);

大写

1
2
3
4
5
6
// @flow
function method(x: Number, y: String, z: Boolean) {
// ...
}

method(new Number(42), new String("world"), new Boolean(false));

boolean

1
2
3
4
5
6
7
8
// @flow
function acceptsBoolean(value: boolean) {
// ...
}

acceptsBoolean(true); // Works!
acceptsBoolean(false); // Works!
acceptsBoolean("foo"); // Error!

JavaScript可以隐式地将其他类型的值转换为布尔值

1
2
if (42) {} // 42 => true
if ("") {} // "" => false

非布尔值需要显式转换为布尔类型

1
2
3
4
5
6
7
8
// @flow
function acceptsBoolean(value: boolean) {
// ...
}

acceptsBoolean(0); // Error!
acceptsBoolean(Boolean(0)); // Works!
acceptsBoolean(!!0); // Works!

string

1
2
3
4
5
6
7
// @flow
function acceptsString(value: string) {
// ...
}

acceptsString("foo"); // Works!
acceptsString(false); // Error!

JavaScript可以隐式地将其他类型的值转换为字符

1
2
"foo" + 42; // "foo42"
"foo" + {}; // "foo[object Object]"

Flow连接到字符串时只接受字符串和数字。

1
2
3
4
5
// @flow
"foo" + "foo"; // Works!
"foo" + 42; // Works!
"foo" + {}; // Error!
"foo" + []; // Error!

必须明确并将其他类型转换为字符串

1
2
3
4
// @flow
"foo" + String({}); // Works!
"foo" + [].toString(); // Works!
"" + JSON.stringify({}) // Works!

number

1
2
3
4
5
6
7
8
9
10
// @flow
function acceptsNumber(value: number) {
// ...
}

acceptsNumber(42); // Works!
acceptsNumber(3.14); // Works!
acceptsNumber(NaN); // Works!
acceptsNumber(Infinity); // Works!
acceptsNumber("foo"); // Error!

null and void

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
function acceptsNull(value: null) {
/* ... */
}

function acceptsUndefined(value: void) {
/* ... */
}

acceptsNull(null); // Works!
acceptsNull(undefined); // Error!
acceptsUndefined(null); // Error!
acceptsUndefined(undefined); // Works!

Array

1
2
3
4
let arr: Array<number> = [1, 2, 3];
let arr1: Array<boolean> = [true, false, true];
let arr2: Array<string> = ["A", "B", "C"];
let arr3: Array<mixed> = [1, true, "three"]

Object

1
2
3
4
5
6
7
8
9
10
11
// @flow
var obj1: { foo: boolean } = { foo: true };
var obj2: {
foo: number,
bar: boolean,
baz: string,
} = {
foo: 1,
bar: true,
baz: 'three',
};

Function

1
2
3
4
5
6
7
8
// @flow
function concat(a: string, b: string): string {
return a + b;
}

concat("foo", "bar"); // Works!
// $ExpectError
concat(true, false); // Error!

箭头Function

1
2
3
4
5
6
7
let method = (str, bool, ...nums) => {
// ...
};

let method = (str: string, bool?: boolean, ...nums: Array<number>): void => {
// ...
};

回调Function

1
2
3
function method(callback: (error: Error | null, value: string | null) => void) {
// ...
}

class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// @flow
class MyClass<A, B, C> {
constructor(arg1: A, arg2: B, arg3: C) {
// ...
}
}

var val: MyClass<number, boolean, string> = new MyClass(1, true, 'three');


class Foo {
serialize() { return '[Foo]'; }
}

class Bar {
serialize() { return '[Bar]'; }
}

// $ExpectError
const foo: Foo = new Bar(); // Error!

Maybe Types

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
function acceptsMaybeString(value: ?string) {
// ...
}

acceptsMaybeString("bar"); // Works!
acceptsMaybeString(undefined); // Works!
acceptsMaybeString(null); // Works!
acceptsMaybeString(); // Works!
acceptsMaybeString(12345); // Error!

// value: string null or undefined

对象属性可选

1
2
3
4
5
6
7
8
9
// @flow
function acceptsObject(value: { foo?: string }) {
// ...
}

acceptsObject({ foo: "bar" }); // Works!
acceptsObject({ foo: undefined }); // Works!
acceptsObject({ foo: null }); // Error!问号放在string前不报错
acceptsObject({}); // Works!

函数参数可选

1
2
3
4
5
6
7
8
9
// @flow
function acceptsOptionalString(value?: string) {
// ...
}

acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!问号放在string前不报错
acceptsOptionalString(); // Works!

带默认值的函数参数

1
2
3
4
5
6
7
8
9
// @flow
function acceptsOptionalString(value: string = "foo") {
// ...
}

acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!
acceptsOptionalString(); // Works!

使用字面文字作为类型

1
2
3
4
5
6
7
8
9
10
// @flow
function acceptsTwo(value: 2) {
// ...
}

acceptsTwo(2); // Works!
// $ExpectError
acceptsTwo(3); // Error!
// $ExpectError
acceptsTwo("2"); // Error!

Union Types

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
function getColor(name: "success" | "warning" | "danger") {
switch (name) {
case "success" : return "green";
case "warning" : return "yellow";
case "danger" : return "red";
}
}

getColor("success"); // Works!
getColor("danger"); // Works!
// $ExpectError
getColor("error"); // Error!

Mixed Types

1
2
3
function stringifyBasicValue(value: string | number) {
return '' + value;
}

A type based on another type

1
2
3
function identity<T>(value: T): T {
return value;
}

An arbitrary type that could be anything

1
2
3
function getTypeOf(value: mixed): string {
return typeof value;
}

Any Types

1
2
3
4
5
6
7
8
// @flow
function add(one: any, two: any): number {
return one + two;
}

add(1, 2); // Works.
add("1", "2"); // Works.
add({}, []); // Works.

变量类型
将类型添加到变量声明
const let var

1
2
3
4
5
6
7
8
// @flow
const foo /* : number */ = 1;
const bar: number = 2;

var fooVar /* : number */ = 1;
let fooLet /* : number */ = 1;
var barVar: number = 2;
let barLet: number = 2;

let

1
2
3
4
let foo: number = 1;
foo = 2; // Works!
// $ExpectError
foo = "3"; // Error!

重新分配变量

1
2
3
4
5
6
let foo = 42;

if (Math.random()) foo = true;
if (Math.random()) foo = "hello";

let isOneOf: number | boolean | string = foo; // Works!

重新分配变量确定变量类型

1
2
3
4
5
6
7
8
9
// @flow
let foo = 42;
let isNumber: number = foo; // Works!

foo = true;
let isBoolean: boolean = foo; // Works!

foo = "hello";
let isString: string = foo; // Works!

react

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import * as React from 'react';

type Props = {
foo: number,
bar?: string,
};

type State = {
count: number,
};


class MyComponent extends React.Component<Props> {
state = {
count: 0,
};
render() {
this.props.doesNotExist; // Error! You did not define a `doesNotExist` prop.

return <div>{this.props.bar}</div>;
}
}

<MyComponent foo={42} />;

123
niexq

niexq

30 日志
8 分类
24 标签
GitHub E-Mail StackOverflow
友情链接
  • hexo
  • ruanyifeng
  • w3school
  • runoob
浙ICP备20011079号 © 2021 niexq
0%