React 知识梳理

403 阅读8分钟

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 后进行一些操作。