文章目录
  1. 1. React是什么
    1. 1.1. Library 库
  2. 2. 为什么要用react
    1. 2.1. 组件化
      1. 2.1.1. 生命周期
    2. 2.2. 性能
    3. 2.3. 多端可移植
  3. 3. 如何使用react?
    1. 3.1. React Web
      1. 3.1.1. React JSX
      2. 3.1.2. React Router
      3. 3.1.3. React Flux
        1. 3.1.3.1. React Redux
    2. 3.2. React Native
      1. 3.2.1. 目录结构
      2. 3.2.2. 底层架构
        1. 3.2.2.1. android
          1. 3.2.2.1.1. mReactRootView
          2. 3.2.2.1.2. mReactInstanceManager
        2. 3.2.2.2. node-server
      3. 3.2.3. 代码复用

#React 杂谈
@auther 【homker】 @date 【2015 11 30】

最近接触React蛮多的,把一些东西记录下来,为了后面更好的学习。

React是什么

reactjs.org的官网上,是这么说的哈:

A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

那么,很简单喽,这个,就是一个帮助开发者更好的处理用户界面的东西。

这里注意两个点:

  • 这是一个用户界面的库
  • 这是一个库

Library 库

作为一个库,区别于angular,同时也区别于vue,虽然这里有一点问题还待商榷的是,vue好像没有说自己是一个框架还是一个库,但是,我个人更偏向于把它看成是前者。那么好了,如果,认为是一个库,那么作为一个库,他只是提供一个辅助工具,来帮助你解决问题,而是不是提出一个完整的解决方案,所以,你会发现,现在react是很自由的,你可以选择任何你喜欢的东西和它搭配在一起工作,他们也能工作的很好。同时,问题也来了,因为他是一个库,所以,有很多东西就变的很不确定。

为什么要用react

使用react的原因有很多,鄙人愚钝,觉得其中比较明显的原因如下:

组件化

react中非常明确的提出了组件(component)的概念,这个组件的概念,不单单指的是web component还包括了native component。而组件化的同时,也自然而然的带来了组件的生命周期。并且,结构代码的语义化在组件化的同时得到了更高层次的实现。

一个react的组件应该看起来是酱的:

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

var React= require('react');

var App = React.createClass({

getInitStates(){
return {}
},
componentWillMount(){
//do some thing
},
componentWillUpdate(){
//do some thing
},
render(){
return (
<div>hello word<span>this.props.user</span></div>
)

}

})

其中App,就是组件啦,在ES6中,你可以定义为一个class,就像酱:

1
2
3
4
5
6
7
8
9
10

import React,{Component} from 'react';

const App extends Component{
render(){
return (
<div>hello word<span>this.props.user</span></div>
)

}
}

个人认为:
组件化的本质其实是对view层的封装。将繁重的view层的变化操作从数据流变化操作中脱离出来,实现数据层和view层的分离。也就是说,在整个代码的组织结构中,数据层将不再处理样式或者说显示上的不同,而是,只要关心数据本身的变化,然后告诉react,变化后的数据集(Date set),剩下的view层变化由react来完成。这样导致了,react希望的数据流每次都是完整的,而不是一个数据片段。这也导致了,react希望使用不可修改类型的数据格式来保证每次的数据是的。同时,对于数据层而言,自然就希望view层的所有变化应该是可预见的。我提供怎么样的数据,你就给我显示出来。

生命周期

然后,在其内部定义了生命周期,共有四个。如图:
生命周期图
从生命周期的名字,很清楚的就知道啦,执行顺序如图所示。
生命周期图

组件的生命周期还要注意两点:

1
2
3
ReactDom.render(<MyComponent />, body);
ReactDom.unmountComponentAtNode(body);
ReactDom.render(<MyComponent />, body);

上面的代码是 ReactDom将组件挂载在真实的DOM上的。当然还有移除的方法。要注意的地方是,这里挂载的时候,DOM必须是已经存在着的,不然并不可以啊。也就是说,如果你挂载在动态的DOM的时候,一定要确保DOM是已经生成的,并且真真正正的存在着。
对于React中,生命周期具体的管理方法可以参考这篇文章:React LifeStyle

性能

react最早的时候,是为了解决在web上,html处理中,高度变化的DOM的性能问题。为了能够让html的DOM处理变的更加合理和高效,facebook的前端团队推出了他们自己的解决方案,react。

嗯,首先说下我们传统的解决方案:
比如,我们要修改页面中的一个span标签中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>a sample of DOM</title>
</head>
<body>
<div id = "container">
<span class="demo-span"></span>
</div>
<script type="application/javascript">
var demoSpan = document.querySelector('.demo-span');
demoSpan.innerText = 'some thing you want';
</script>

</body>
</html>

嗯,很简单。选中它,然后修改它。

需要我们注意的一点,我们在这里的修改是实实在在的DOM。那么我们修改之后,浏览器会对应修改他的DOM tree 和render Tree,重新更新render Layer。

这个一个span,如果我们要改的是一个大型的ul呢?

那么这个时候,你会发现你的每一次修改的背后,都需要付出非常大得修改成本,虽然,你可能只是要修改一个span。

如同所知,所有的HTML最后的显示结构可以认为是一个树,那么,每一次修改的本质,其实把一颗树变成另一颗树。嗯,判断哪一个节点要修改的过程我们叫做diff DOM,那么一个diff DOM的算法,其实就是把一个树变成另一个树的算法咯。这个算法,根据我们数据结构老师教给我们的,最优算法实现的时间复杂度是O( n^3 ),好像很复杂的样子哈。。。

react通过两个假设,魔术般的把整个的diff DOM算法的复杂度从O( n^3 ) 变成了 O( n )。(从算法复杂度的角度考虑,react在处理超大规模DOM节点修改上面应该会有着惊人的表现)。react 提出的两个假设是:

  • 两个相同组件产生类似的DOM结构,两个不同的组件产生不同的DOM结构。
  • 对于同一层次的一组子节点,他们可以通过唯一的id进行区分。

嗯,第一点,简直简单暴力。这个假设前提,导致react不会再去对比一个复杂的子DOM树,而是在组件级别做DOM diff。我把这种行为叫做component diff

第二个假设,告诉了我们react在component diff的时候的具体做法,他是按照层次进行diff的,也就是说,react在进行component diff的时候并没有遍历树,而是按照树状结构做层次对比。哈哈哈,是时候拿出着些图了:

component diff 图

对于长列表而言,
长列表 diff图

通过如上的两个假设,基本解决了component级别的diff问题。

当然在具体的界面更新逻辑中还有批量化处理选择性子树渲染的优化,简单的说,就是在组件树种,你调用了setState,那么该组件就会被标记为dirty,在一个事件循环中,所有被标记为dirty的组件将会调用其render()函数。当然,你也可以通过调用生命周期当中的componentWillUpdate()来控制其是否需要更新,藉此来提高性能。

多端可移植

react可以很自然的运行在server端,web端,native端。基本上能够实现一套代码四处运行,这种星辰大海的梦想,当然必须要选择的咯,怎么能拒绝呢?

如何使用react?

好了,上面记述了下react的what和why,下面来how~!首先,我们分两个大得方向:

  • web
  • native

React Web

在web端,我们可以选择的技术栈其实很多的。但是呢,我个人比较倾向于React+Redux+webpack来实现。但是,中间还会带有很多其他的东西。首先,我们要理解是JSX

React JSX

在react渲染的时候,支持一种很像XML的语言,叫JSX,官方的定义是:

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。React 可以用来做简单的 JSX 句法转换。

实际上,jsx是允许你使用XML的语法来定义你的react渲染结构和布局。
比如说酱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';
import Head from '../components/head.jsx'
import Body from '../components/body.jsx'

class App extends React.Componets {
render () {
return (
<div>
<Head/>
<Body/>
</div>
)
}
}

通过JSX的格式的引入,使得react的组件的组合变得非常容易。但是需要注意的一点是,定义组件的时候,首字母要大写,然后,每一次render必须要有一个根节点。

在使用JSX的时候,我们时刻的记住一点,JSX只是一个XML的解释性语言,所以,本质就是字符串。

React Router

这里,我们还要提到一个很酷炫的东西叫做react router。react router为单页面应用(SPA)提供了一个很好的路由表处理机制。通过react router来进行路由分发,能够很方便的去实现单页面中的页面切换。就像酱:

1
2
3
4
5
6
7
8
//router
let routes = (
<Route path="/" component={App}>
<Route path="main" component={Main} onEnter={authCheck}/>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
</Route>
);

其中App,Main,Login,Logout都是单屏组件。通过history来进行状态的切换和更新。就像酱

1
this.props.history.replaceState(null,location.state.nextPathname)

额,这里的history很明显的是从父组件继承下来的,所以,你要修改一下,你的根组件的render函数。就像酱:

1
ReactDOM.render(<Router history={history}>{routes}</Router>,document.getElementById('container'))

但是这里有一个问题,根路由。所谓根路由就是在最顶端的那个组件,同时一个路由,这个组件的任务就是分发路由。照理来说,这个设计是没有问题的,但是,有个很坑的地方,就是这个router是在数据流顶端的,乃至于事件流顶端。如果我想在事件流当中去修改这个东西的时候,我们会发现,没有办法去修改。因为,它并不在我们的事件流范围内。所以,redux提出了一个新的redux-router来封装router。

这里引用redux-router中的一段话。

This library allows you to keep your router state inside your Redux store. So getting the current pathname, query, and params is as easy as selecting any other part of your application state.

来说明其作用。

在使用的时候,我们的Root节点就变成下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//router
let routes = (
<Route path="/" component={App}>
<Route path="main" component={Main} onEnter={authCheck}/>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
</Route>
);



const Root = React.createClass({
render(){
return (
<Provider store={store}>
<ReduxRouter>{routes}</ReduxRouter>
</Provider>
)

}
});

其中,最外层的Provider封装是redux加上去的。

React Flux

好了,现在我们要来说react提出的另一个很有创意的东西。flux。一个事件流/数据流模型。

Flux is the application architecture that Facebook uses for building client-side web applications. It complements React’s composable view components by utilizing a unidirectional data flow. It’s more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.

之所以说很有创意,是因为flux提出了一个单循环的事件流/数据流模型。所谓的单循环,说明了事件和数据的本身是有生命性,每一次新发起得事件也好,数据也罢,都是区别于上次事件和数据的本身。说的好像很抽象,我们先看一个图,对,奏是flux的官方说明图。
flux事件流图

图中,有四个东西

  • Action 动作
  • Dispatcher 分发者
  • Store 数据商店/数据仓库
  • View 视图

这四个东西,代表了四个处理工位,每一个工位都只做属于自己的那一件事,那就是加工和处理流水线上的数据,并把加工好的数据扔给下一个工位。工业化的数据处理方式,要求每一次处理数据的时候,拿到的都是一个完整的独立的数据集,而不应该是一个残次品,或者说是数据片段。

为什么要强调完整独立的呢,因为,javascript语言本身的一些特性。我们知道,在javascript当中,数据类型分成两大种,基础类型和引用类型。基础类型只有6个,number,boolen,Object,null,undefined,string。其他的都是引用数据类型。所谓引用数据类型,就是他们都是Object原型链上的表达。换句话说,他们都是一个指针,指向了对应的Object类型的数据。所以,在javascript中,简单的赋值绝大多数是在拷贝指针,所有基于指针的修改是联动的,但是在react的数据流中,要求将数据的修改独立出来,也就是要求,我们要使用的是深拷贝,而不是简单的赋值。这个很容易犯错。

简单的说一下各个部分是干嘛的。

Action是动作,定义了一个完整的动作过程。其动作本身应该是可以理解的。比如说,在todo列表中添加一条新的数据。这个叫动作。而鼠标点击了添加按钮叫做点击事件。二者要加以区分。

Dispatcher分发者,作用是把不同的Action分发给不同的store,这个在不同的flux的实践中有着不同的考量,在reflux中,干脆就取消了,没了。。。因为他们觉得太复杂了。但是在flux官方本身,却是很是推崇,他们提供的flux中Dispatcher的实现建议。

Store数据仓库,但是在flux中,我比较倾向于翻译成数据商店。Action的本意是一个完整的动作,比如说在总得点赞数中加1。那么dispatcher就把这个Action派送到点赞数这个数据商店,然后从点赞数商店中获取完整的点赞数组,或者对象。并把数据发给view

view就是react啦,他就会把数据显示出来,奏事酱。

个人认为,四个东西代表了数据处理的四个步骤。数据往何去(dispatcher),数据从何来(Store),数据该如何修改(Action),数据该如何显示(view)。

tips:还有一个问题。如何判定事件的发生?

对于每一个动作(Action)的发生,我们认为其都是新的Action,为了标示其的发生,我们用State来标识。也就是说,每发生一次Action就会有对应的State的改变。那么我们可以认为,对于数据流,事件流的管理,可以认为是对各个组件的状态集合的管理。

React Redux

redux是flux思想的一种实现。他和react没有必然的联系。你可以把它用在任何处理状态管理的地方。都是可以的。

其官网中是酱介绍自己的。

Redux is a predictable state container for JavaScript apps.

简单的说,redux是javascript的状态容器,提供了可预测的状态管理。
但是redux对flux的具体实现有自己的区别。首先,redux提出了自己的三条基本哲学:

  • Single source of truth (单一数据源)
  • State is read-only (state是只读的)
  • Mutations are written as pure functions (使用纯函数来执行修改)

三条哲学的背后,隐藏了作者对于flux个性化的理解。

redux的流程图

抽象成图就是酱的。

嗯,很清晰的一点:就是单循环数据流。主要有这么几个部分组成:

  • store
  • view
  • action
  • reducer
  • middleware

嗯,这里和flux一致的东西就不累述了,我们着重的说一下redux的革新的地方。在redux中,store不再是一个程序体,而是被抽象成为了一个类似于女皇的存在,他统领和分发所有的Action。就像酱:
redux流程图
在redux中,只有一个store,store中拥有dispatch方法,通过dispatch方法来调度和触发所有的Action。在程序体中,它通过连接到组件实体,把dispatch函数注入到props当中去,就像酱:

1
2
3
4
//连接
export default connect(mapState2Props)(Main);
//调用
this.props.dispatch(commmitComments(user, message));

其中,commitComments就是Action。

所谓的reducer是一个纯函数,他是用来处理Action的。一个reducer看起来就像这样:

1
2
3
4
5
6
7
8
9
10
	function getAllMessage(state = {}, action) {
switch (action.type) {
case SHOW_ALL:
return Object.assign({},Messages(state,action),{
//TODO 添加新状态
});
default:
return state;
}
}

很明显,这是一个很简单的纯函数。他的任务就是,维护一个state树,根据Action来修改这个state树。而state树,又会通过mapstate2props函数映射给props。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mapState2Props(_state) {

const {user,commitMessage,getAllMessage,reciveMessage} = _state;
let _items = getAllMessage.messages ? [...getAllMessage.messages] : [];
_items = _items.concat(commitMessage.messages||[]).concat(reciveMessage.messages||[]).sort(function(pre,nex){
return (pre.date - nex.date)||-1 ;
});
console.log('items',_items);
return {
user,
items:[..._items]
}

}

然后,view层就能重新渲染新的数据啦。

在新一个Action发出时,到reducer之前,我们可以通过一个middleware来改变一些事情。比如通过使用高阶函数(high-order function)来进行更复杂的reducer处理。

tips:需要注意的是:
在redux的实践中,大量的使用了函数化编程的思维,什么高阶函数啊,复合函数啊,柯理函数呀。
这里顺带举几个例子,帮自己回忆一下。

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
	//复合
var a =functionx{
return 'say:'+x
}

var b = function(x){
return x
}

var c = function(a,b){
return function(x){
return a(b(x))
}
}
//c('hello') --> say hello
//柯理化

var a = function(x){
return function(y){
return x+y;
}
}

var b = a(1);
var c = b(1);
c===2;//true

React Native

做这个之前,首先,你要能翻墙。。。最好是能有台mac。。。
额,科学上网的原因并不是因为镜像,而是因为google比百度靠谱多了。特别是在搜索上面。。。有台Mac的原因嘛,听说facebook的程序员们都用mac,所以并不知道react-native在windows上的兼容性到底如何。。。哈哈哈,玩笑话啦。主要是在mac上,移动端友好一点。

嗯,因为只有andorid,并且相对于ios更熟悉android开发,所以下文都是以andorid的开发为例。自己接触react-native的开发时间不长,有很多地方都不是很懂,所以如有错误,请诸位多多包涵。

目录结构

45CBFC3D-97E7-42A5-BBF3-DC44F6190B2B.png-49.9kB

对于目录结构而言,我们能够很清晰的看到,有几个很熟悉的目录,andorid,ios,分别是为了兼容两个不同平台的个平台文件。node_modules,这个文件夹,大家都很清楚啦。其他的文件,大家一看也都明白,不多说。

底层架构

嗯,首先,看张图。这张图来自腾讯QQ空间的终端团队。
react-native图片
看起来很简单的一张图,其实还是蛮复杂的。这里有三层,一层是java,一层c++,一层js。嗯,java层和js层其实都很好理解。组件和其的生命周期是在js层管理的,java层是andorid的底层通信。c++层是构建了一个Bridge。用来做js的编译,同时保持java和js之前的通信。

但是,只看这张图,肯定会有疑问,为什么要徒徒加C++这一层,js直接和java通信岂不是更好,更直接!这个嘛,是执行效率的问题吧。

就像我们知道的那样,运行一个React-Native项目的时候,需要在根目录运行

1
react-native run-andorid

而执行,这个命令,我们会发现实际上执行android编译安装到指定虚拟机的命令,同时也运行了一个start Server的命令。这个server就是react-native packager server。也就是说,react-native的运行分成了两部分。

  • android (Native)
  • node-server (react)

我们分开来分析。首先讲andorid(Native)。

android

首先,要知道andorid的application的运行机制才行。对于一个传统的application而言,最重要的东西就是activity啦。打开React-Native的andorid 的目录文档。一看文件结构。很是熟悉呀。
1368B751-C35D-4F79-A068-F6A8888F9D39.png-91.2kB
这个很明显是一个典型的android项目的文件结构。嗯,这个就很熟悉啦,直接打开其activity来看看他都干了些什么。

在其onCreate函数中,有如下代码:

1
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mReactRootView = new ReactRootView(this);

    mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(getApplication())
            .setBundleAssetName("index.android.bundle")
            .setJSMainModuleName("index.android")
            .addPackage(new MainReactPackage())
            .setUseDeveloperSupport(BuildConfig.DEBUG)
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build();

    mReactRootView.startReactApplication(mReactInstanceManager, "RX", null);

    setContentView(mReactRootView);
}

所谓万变不离其宗。andorid上所有的变化都必须要遵循andorid的底层接口和运行时。无论你是java也好,js也罢,亦或是世界上最好的语言php(php for andorid)。其实都是如此。很容易看见,在activity中,新建了一个mReactRootView,通过,mReactInstanceManager 来引入一个index.andorid.bundle来渲染这个mReacRootView,实现react和android 的整合。

嗯,这里提到了两个重要的组件。

  • 一个是mReactRootView
  • 一个是mReactInstanceManager
mReactRootView

嗯,从类的继承关系上来看,mReactRootView 继承自SizeMonitoringFrameLayout,而这个SizeMonitoringFrameLayout又很明显是继承自FragmeLayout,而FragmeLayout是android中非常常见的一种布局。同时也是最简单的布局,它只是定义了一个界面,没有对界面做任何布局上得限制。
至于SizeMonitoringFragmeLayout是个什么东西,从名字上就能很清楚的看出来。提供一个大小改变的接口,来提供改变大小的能力。
而,rootView本身是处理了一些触摸事件的传递和本省的一些生命周期的管理。

mReactInstanceManager

这个组件比较复杂了,他完成了对index.android.bundle文件的获取。而这个index.android.bundle的获取是可以通过网络获取的,其实在debug的时候就是这样的,虚拟机会发出对本地的index.andorid.bundle的请求。就像这样。

1
localhost:8081/index.android.bundle?platform=android&dev=true

当然,你可以把它打包到本地,一起发布。放在asset目录下面就好了。
具体的操作方法,可以参考这篇文章
获取到相应的bundle文件之后,将会把文件中的代码交给javascript core去执行和调用底层的接口。

react-native官方文档中也提到了,我们可以请求如下的链接来在chrome中调试我们的react-native代码。

1
localhost:8081/debugger-ui

嗯,那么简单的说,在andorid端,react-native会像其他的app一样,遵循andorid app的一切。这样的话,大家甚至可以在一个已经开发好的app中,通过activity的嵌入来动态加载react-native。

node-server

说了半天,发现好像,这个东西就是围绕着index.andorid.bundle来跑的。说来说去,都和这货分不开干系。这货到底是个什么!

我们发现。在react-native的调试过程中,它自己拉起了一个node-server来执行js的编译和babel的解析。是的,react-native天然支持ES6,甚至ES7的一部分特性。因为,代码被编译过啊!在node-server中,会把我们在index.andorid.js中的代码和各种依赖打包编译成一个index.andorid.bundle。

鄙人能力有限,并不能完全解释清楚其中的奥妙,大家可以参考下面两篇,来加深理解。
一篇是QQ空间团队的React Native For Android 架构初探
一篇是淘宝前端团队的使用 JS 构建跨平台的原生应用(二):React Native for Android 调试技术剖析

代码复用

react给我们带来的一点是,代码可以在多端复用。这个地方,其实有一点要清晰的是,并不是说react的代码可以一行不改,直接放入到react-native中去运行。因为,你会发现,react-native会有各自不同的组件,而且,react-native的andorid端和ios端的组件也各有各的。。。

那是不是react的开发和一般开发就没有区别了嘛?
并不是,毕竟redux(flux)部分的代码是可以复用的。但是,ui层的代码,你还是要乖乖的重写。但是,你可以很容易的根据文档,用以前的思维去写这些东西。除了事件系统有一点不一样以外,其他的都是一样的。虽然没有完全实现,一次编写四处运行,但是,照着这个方向发展下去,抹去各端差异是时间早晚的事,在不久的将来,一定会实现,一处编写,四处运行的。

###思考

react给我带来的到底是什么?私以为,除了如何高效的处理DOM,如何去组建化。还有一点是,react带来的数据和ui分流的思想。ui层的处理其实就是像盖房子,房子盖好了,它是可预见的,实实在在的,你可以真真切切的知道它就是这样的。而数据就好像是房子中的电线,水管,煤气。在建房子的时候,埋入到房子当中,是可预见的。而用户在房子当中使用这些的时候,并不会因外,使用了电,使用了水,就改变了房子本身的结构。也就是说,数据层和ui层分离之后,数据层把要显示的数据完完整整的丢给ui层去处理,自己不去干扰。而事件的处理则升格成为动作来处理。事件本身由ui层来屏蔽,在数据层,只保留要处理的动作(Action),比如,添加一条XXXX评论.而不是,处理,button click。通过如此,数据层的代码可以实现单独优化和多段无差别复用,而ui层的代码则可以更加专注于ui层本身。

文章目录
  1. 1. React是什么
    1. 1.1. Library 库
  2. 2. 为什么要用react
    1. 2.1. 组件化
      1. 2.1.1. 生命周期
    2. 2.2. 性能
    3. 2.3. 多端可移植
  3. 3. 如何使用react?
    1. 3.1. React Web
      1. 3.1.1. React JSX
      2. 3.1.2. React Router
      3. 3.1.3. React Flux
        1. 3.1.3.1. React Redux
    2. 3.2. React Native
      1. 3.2.1. 目录结构
      2. 3.2.2. 底层架构
        1. 3.2.2.1. android
          1. 3.2.2.1.1. mReactRootView
          2. 3.2.2.1.2. mReactInstanceManager
        2. 3.2.2.2. node-server
      3. 3.2.3. 代码复用