前言
目的:第一遍摘抄书中大部分内容,方便过第二、三遍时可以随时随地通过手机查看。
1.React
1、我们在JSX中看到一个组件使用了onClick,但是并没有产生直接使用onclick的HTML,而是使用事件委托的方式处理点击事件,无论有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最高层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数。
因为React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄漏也不再是一个问题。
2、试试
1 | 使用 ` create-react-app ` 命令创建项目 |
3、纯函数,指的是没有任何副作用,输出完全依赖输入的函数,两次函数调用如果输入相同,得到的结果也绝对相同。
4、web前端开发关于性能优化有一个原则:尽量减少DOM操作
。虽然DOM操作也只是一些简单的JavaScript语句,但是DOM操作会引起浏览器对网页进行重新布局,重新绘制,这就是一个比JavaScript语句执行慢很多的过程。
5、分而治之
把问题分解为多个小问题,拆分组件。
6、prop是组件的对外接口,state是组件的内部状态,对外用prop,对内用state。
7、
1 | class Counter extends React.Component{ |
(1) 如果一个组件需要定义自己的构造函数,一定要在构造函数的第一行通过super调用父类也就是React.Component的构造函数。如果在构造函数中没有调用super(props),那么组件实例被构造之后,类实例的所有成员函数就无法通过this.props访问到父组件传递过来的props值。
(2) 在Count的构造函数中还给两个成员函数绑定了当前this的执行环境,因为ES6方法创建的React组件类并不自动给我们绑定this到当前实例对象。
(3) 在构造函数中可以通过参数props获得传入prop值,在其他函数中比如render中,可以通过this.props访问传入prop的值。
8、在开发环境中定义 propTypes, 在开发过程中避免犯错,但是生产环境做propTypes检查没什么帮助,还要消耗CPU计算资源。在生产环境用插件babel-react-optimize
,生产环境安装可以去掉propTypes。
9、组件的生命周期的三个阶段:
- 装载过程(Mount),也就是把组件第一次在DOM树中渲染的过程;
- 更新过程(Update),当组件被重新渲染的过程;
- 卸载过程(Unmount),组件从DOM中删除的过程。
9.1、装载过程
- constructor
- getInitialState
- getDefaultProps
- componentWillMount
- render
- componentDidMount
【constructor】目的:
初始化state
绑定成员函数的this环境
在ES6语法下,类的每个成员函数在执行时的this并不是和类实例自动绑定的。而在构造函数中,this就是当前组件实例,为了方便调用,往往在构造函数中将这个实例的特定函数绑定this为当前函数。1
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
表示通过bind方法让当前实例中onClickIncrementButton函数被调用时,this始终指向当前组件实例。
【getInitialState、 getDefaultProps】只有在React.createClass方法创造的组件类才有作用。
【类组件名.defaultProps】在ES6中可以指定props 的初始值
【render】
函数应该是一个纯函数,完全根据this.state和this.props来决定返回的结果,而且不要产生任何副作用。
【componentDidMount】
render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被”装载“到了DOM树上。
举例:
1
2
3
4
5<App>
<Counter1></Counter1>
<Counter2></Counter2>
<Counter3></Counter3>
</App>
父组件包含三个子组件,在这4个组件中各自生命周期函数中打印文字,如下:
1 | constructor App |
可以看到,虽然componentWillMount都是紧贴自己组价的render函数之前被调用,componentDidMount可不是跟着render函数被调用,当所有三个组件的render都被调用之后,三个组件的componentDidMount才连在一起被调用。
之所以会出现上面的现象是因为render函数本身并不往DOM树上渲染或者装载内容,它只是返回一个JSX的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。所以,只有React库调用三个Counter组件的render函数之后,才能有可能完成装载,这时候才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。
componentDidMount只在浏览器端执行,在componentDidMount被调用的时候,组件已经被装载到DOM树上了,可以放心获取渲染出来的任何DOM。
【componentDidUpdate】
当props或者state被修改的时候,就会引发组件的更新过程。
【shouldComponentUpdate】
我们知道render函数应该是一个纯函数,这个纯函数的逻辑输入就是组件的props和state。所以,shouldComponentUpdate的参数就是接下来的props和state值。
【componentWillUnMount】
当React组件要从DOM树上删除掉之前,对应的componentWillUnMount函数就会被调用
10、第二章小结:React利用props来定义组件的对外接口,用state来代表内部的状态,某个数据选择用props还是state表示,取决于这个数据是对外还是对内。
2. Redux
- 唯一数据源
- 保持状态只读
- 数据改变只能通过纯函数完成
2.1.1唯一数据源
指的是应用的状态数据应该只存储在唯一的一个store上。
这个唯一Store的状态,是一个树形的对象,每个组件往往只是用树形对象上一部分的数据。
2.1.2保持状态只读
就是说不能直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。
2.1.3数据改变只能通过纯函数完成
2.2 容器组件和展示组件
拆分容器组件和展示组件,是设计React组件的一种模式,和Redux没有直接关系。
2.1 react-redux
2.1.1 connect
connect(mapStateToProps, mapDispatchToProps)(Counter)
这里有两次函数执行,第一次是connect函数,第二次是把connect函数返回的函数再次执行,最后产生的就是容器组件。
2.1.2 Provider
react-redux要求store必须包含三个函数:subscribe、dispatch、getState。
3. 模块化 React 和 Redux 应用
- 代码文件的组织结构;
- 确定模块的边界;
- Store的状态树设计。
3.1 代码文件的组织方式
Redux应用适合于“按功能组织”,也就是把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码。
拿Todo应用为例,那个应用的两个基本功能就是TodoList和Filter,所以代码文件目录如下:
1 | todoList/ |
每个基本功能对应的其实就是一个功能模块,每个功能模块对应一个目录。
在这种组织方式下,当你要修改某个功能模块代码的时候,只要关注对应的目录就行了。
3.2 确定模块的边界
在理想的情况下,我们应该通过增加代码就能增加系统的功能,而不是通过对现有代码的修改来增加功能。
不同功能模块之间的依赖关系应该简单而且清晰,也就是所谓的保持模块之间低耦合性;一个模块应该把自己的功能封装的很好,让外界不要太依赖与自己内部的结构,这样不会因为内部的变化而影响外部模块的功能,就是所谓高内聚性。
React组件本身应该具有低耦合性和高内聚性的特点,不过,在Redux的游乐场中,React组件扮演的就是一个视图的角色,还有reducer、actions这些角色参与这个游戏。对于整个Redux应用而言,整体由模块构成,但是模块不再是React组件,而是由React组件加上相关reducer和actions构成的一个小整体。
可以预期每个模块之间会有依赖关系,比如filter模块想要todoList的action构造函数和视图,那么我们希望对方如何导入?一种写法是像下面的代码这样:
1 | import * as actions from '../todoList/actions'; |
todoList和filter中的文件名几乎一样,但是这毕竟是模块内部的事情,不应该假设所有模块都按照这样的文件夹名命名。
现在我们既然把一个目录看做一个模块,那我们要做的就是明确这个模块对外的接口,而这个接口应该实现把内部封装起来。todoList和filter模块目录下的index.js就是我们的模块接口。
比如,在todoList/index.js中,代码如下:
1 | import * as actions from '../todoList/actions'; |
如果filter中的组件想要使用todoList中的功能,应该导入todoList这个目录,如下:
1 | import {actions, reducer, views} from '../todoList'; |
3.3 状态树的设计
- 一个模块控制一个状态节点
- 避免冗余数据
- 树形结构扁平
3.4 辅助开发工具
- React Devtools:可以检视React组件的树形结构。
- Redux Devtools:可以检视Redux数据流,可以将Store状态跳跃到任何一个历史状态,也就是所谓的“时间旅行”功能。
- React Perf:可以发现React组件的渲染问题。
redux-immutable-state-invariant辅助包:每个reducer函数都必须是一个纯函数,不能修改传入的参数state和action,这个包可以在开发环境中使用,当不小心在reducer中修改了参数state,会给以错误警告。