1. React 初识
1.1 React 设计思路
- DOM 操作的性能消耗非常大,而 React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM,数据更新后,重新计算新的 Virtual DOM,并和上一次进行对比,只对发生变化的部分做更新,并且还提供了 shouldComponentUpdate 这样的生命周期,可以让开发者手动减少 Virtual DOM 的对比过程,提高性能。
- 另外,React 还通过抽象的组件化,使得开发者可以开发出很多公用组件,通过函数式方法去减少代码冗余。
1.2 JSX
JSX 是一种将 HTML 语法直接加入到 JavaScript 代码中的语法。
dangerouslySetInnerHTML :
React 会将所有要显示到 DOM 的字符串转义,防止 XSS;
必要时候,可使用 dangerouslySetInnerHTML,可避免 React 转义字符
1.3 Virtual DOM
-
Virtual DOM 就相当于是 React 的一个虚拟空间,React 的所有工作几乎都是基于 Virtual DOM 完成的。
-
React 通过 JSX 创建虚拟元素,而这创建的虚拟元素最终会被编译成调用 React 的 createElement 方法,这个方法会返回虚拟元素的实例。
1.4 React 组件的构建方法
- React.createClass —— 最传统、兼容性最好的方法
- ES6 classes —— class Button extends Component
- 无状态组件 —— 只传入 props、context 作为参数无 state,
前两种方式在调用组件时都会创建新的实例,第三种则不会,所以合适情况下可以尽可能的用第三种方法以实现优化。
1.5 React 数据流
React 中有两种数据概念:props 和 state。
1.5.1 props —— properties(属性)
props 是父组件作为属性传递给子组件的数据,props 的改变会引起子组件的重新渲染,React 就是通过 props 来实现数据的单向流动。
- static defaultProps —— props 默认配置
- static propTypes —— 规范 props 值得类型与必须状态,开发环境下,传入的值不匹配就会在控制台里警告
- children(内置prop)—— 代表组件的子组件集合,根据传入子组件的数量决定是否是数组类型。React.Children提供了 map、forEach、count 等方法用来处理子组件。
1.5.2 state —— 组件内部状态
state 用来维护组件内部的数据状态,想要更新组件时,可以使用 setState 方法来让组件重新渲染。
setState(updateState, callback) —— 异步,一个周期内所有的 setState 方法会合并操作
updateState —— 需要更新的 state 对象;
callback —— 更新完 state 后要执行的回调函数。
setState 通过一个队列机制实现 state 更新,执行 setState 时,会将需要更新的 state 合并后放入状态队列,而不是立即更新,队列机制可以高效地批量更新 state。
注: setState 大部分情况下是异步的,但使用原生事件添加的事件处理函数,或 setTimeout/setInterval 产生的异步回调里,setState 会立即执行。
1.6 React 生命周期(React 16)

1.6.1 挂载阶段
- constructor:组件实例化
- static getDerivedStateFromProps:返回值作为 setState 的参数,只能返回 object 或 null,返回 null 则不更新 state
- render:渲染
- componentDidMount:完成挂载
1.6.2 更新阶段
- static getDerivedStateFromProps:同上挂载阶段
- shouldComponentUpdate:判断是否需要重新渲染(重新对比Virtual DOM,渲染)
- render:渲染
- getSnapshotBeforeUpdate:获取 render 之前的 DOM 数据
- componentDidUpdate:渲染完成
1.6.3 卸载阶段
- componentWillUnmount:卸载完成,可用于重置数据,卸载事件等
1.6.4 捕获异常
- componentDidCatch:用于补货组件的异常
1.7 refs —— 用来获取一个组件实例或 DOM 元素,便于对其进行操作。
使用方法:
- 回调函数
class Demo extends React.Component {
render() {
return <div ref={ref => (this.testRef = ref)} />
}
}
- createRef
class Demo extends React.Component {
constructor(props) {
this.testRef = React.createRef()
}
render() {
return <div ref={this.testRef} />
}
}
以上两种方法均可以使 this.testRef 指向 div 这个 DOM 元素,可以对其进行 DOM 操作。
注:
createRef 是 React 16.3 新提供的方法,refs 也支持字符串方式,但后面可能会被废弃,此处不再赘述。
2. React 深入使用
2.1 React 事件系统
2.1.1 React 合成事件
React 基于 Virtual DOM 实现了一个 SyntheticEvent(合成事件)层,我们定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,他结局了兼容性问题,与原生浏览器时间一样拥有同样的接口,支持事件冒泡,并且会在组件卸载时自动移除事件。
React 底层,主要对合成事件做了两件事:事件委派和自动绑定。
- 事件委派: 把所有事件绑定到结构的最外层,使用一个统一事件监听器,组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象。这样简化了事件处理和回收机制,很大地提升了效率。
- 自动绑定: React 组件中会自动绑定 this 为当前组件,且对这种引用进行缓存,以达到 CPU 和内存的最优化。不能自动绑定的则需要手动进行绑定。
2.1.2 原生事件
对于无法使用合成事件的场景,还是需要使用原生事件,比如需要绑定在 body 上的事件、window 的 resize 事件,但在 React 中使用原生事件,一定要在组件卸载时手动移除,否则很可能出现内存泄漏的问题。
2.2 受控组件 & 非受控组件
- 受控组件: 组件值受 state 或 props 值控制,且组件值改变时会改变对应的状态值。
- 非受控组件: 组件值不受 state 或 props 控制。
2.3 组件间通信
- 父组件向子组件通信: 父组件通过 props 来向子组件传递数据
- 子组件向父组件通信: 回调
- 跨级组件通信: context(全局变量,适用于全局信息:界面主题、用户信息等)
- 没有嵌套关系的组建通信: 可以使用 Node 的 events 模块来实现。
emitter.on, emitter.emit, emit.removeListener
2.4 高阶组件
接受 React 组件作为参数,输出一个新的 React 组件
实现高阶组件的方法:
- 属性代理:高阶组件通过被包裹的 React 组件来操作 props(常用)
- 反向继承:高阶组件继承于被包裹的 React 组件
给高阶组件传参:
//定义高阶组件时
import React, { Component } from 'react'
export default (WrappedComponent, title = 'hello') => class HOC extends Component {
render() {
return (
<div>
<h2>{title}</h2>
<WrappedComponent {...this.props} />
</div>
);
}
};
//调用时:
import React, { Component } from 'react'
class Demo extends Component {
...
}
export default HOC(DEMO, 'test')
2.5 diff 算法
diff 会计算出 Virtual DOM 真正变化的部分,然后只针对变化的部分进行原生 DOM 操作,而非重新渲染整个页面,因此,diff 算法的改进优化是 React 性能的基础和保障。
React diff 算法的三个策略
- DOM 节点跨层级的移动操作可以忽略不计
- 拥有相同类的两个组件会生成相似的树结构,不同类的两个组件会生成不同的树结构
- 对于同一层级的一组子节点,他们可以通过唯一 id 进行区分
上面三个策略分别对 tree diff、component diff、element diff 进行算法优化。
2.5.1 tree diff
对树进行分层,只对相同层级的 DOM 节点进行比较
2.5.2 component diff
组件间的比较策略:
- 同一类型的组件,按照原策略继续比较 Virtual DOM
- 不同类型的组件,直接替换整个组件下的所有子节点
- 同一类型的组件,用户可以通过 shouldComponentUpdate 来告诉 React 是否需要进行 diff 算法分析,手动优化
2.5.3 element diff
当节点处于同一层级时,diff 提供了插入、移动、删除三种节点操作。
2.6 React 性能优化
- 尽可能多地使用纯函数组件 --function Component
- 也可使用 React.memo 对函数组件进行优化
React.memo(funComponent, isRenderFun)
funComponent 为纯函数组件,isRenderFun 用来确定是否需要刷新
- 在场景允许的情况下,PureComponent 也可以实现性能优化
PureComponent 自动为我们实现了 shouldComponentUpdate 函数,不过函数内部对 props 和 state 做的是浅比较,存在一定风险,要根据具体使用场景来判断是否使用
- 设置合适的key
key 的原则是独一无二,且能不用遍历或随机值就不用,因为 diff 时,需要通过 key 来进行比价区分,key 设置的越合适,diff 效率就越高。
3. Redux
- React 是单向数据流,那么当数据状态很复杂,涉及到多层级数据传递等情况的时候,逻辑处理上也会很复杂,Redux 就是来解决这个问题的。
- Redux 提供了一个状态管理机制,将整个应用的所有数据同一存储到 store 中,并灵活地将数据分发给需要的组件。
3.1 React 基本使用
- createStore(reducers, initailState):
Redux 的 createStore 方法会根据 reducer及初始化状态 生成 store
- action:
当需要进行数据改变时,会 dispatch 一个 action,相当于指令,每个 action 都有自己的 type,根据不同的 type 去执行对应的 reducer
- reducer(previousState, action):
每一个 reducer 都是纯函数,只有 reducer 可以改变数据,reducer 根据之前的状态和 action 生成新的 state,store 则会进行更新,并更新渲染视图。
3.2 Redux 与 React 结合(react-redux)
- Provider
Provider 接受一个 store 参数,将应用的顶层组件包裹在其中。
- connect
connect(mapStateToProps, mapDispatchToProps)(Demo) —— 使组件可以获取 store 中的数据
mapStateToProps: 把 store 中的数据映射到组件的 props中;
mapDispatchToProps: 把 dispatch 方法也映射到组件的 props 中,供组件使用。
3.3 React 中间件
react store改变时的正常流程:view --> action --> reducer --> store --> view, 中间件则是想在这个流程中插入一些其他的操作,比如 redux-thunk 就是在 action 前执行一些操作,而 redux-saga 则是在 acion 后进行一些操作。