react暇谈(2)——react组件更新的原理

主旨

此次本文旨在讨论:vue

  • react 组件更新的原理

术语定义

  • 组件更新:组件进行了重渲染,其渲染阶段至少通过了 render 函数和 tree diff 阶段。

react 组件更新的原理

首先,咱们先阐述下 react 组件更新的策略:一个组件发生了更新,其子组件也会更新,在不进行优化的状况下,这种更新的传递会持续到组件树的叶子组件,即便某个子组件先后props并无发生改变react

而后,让咱们肯定下什么能够引发组件更新(针对react 16):api

  • useState/useReducer hook
  • 类组件 this.setState
  • 父组件更新(props 变化)

1. useState/useReducer hook

因为 useReducer 的 api 过于繁琐,为了叙述的方便,这里使用 useState 举例子。数组

这一节中,我使用 setState 指代 useState hook 返回的 setState 函数,而 this.setState 指代类组件的 setState 函数。promise

关于 useState hook 须要注意的几点:异步

  • hooks 给了函数组件和类组件同样保存状态的能力,天然而然的,既然能够保存状态就应该能够更改状态,useState 返回的 setState 能够用于改变相关的状态,自此函数组件和类组件同样,一样具备了主动发起更新的能力。函数

  • react 官方认为 useState hook 应该用于更加细粒度的值,因此 useState 和基本的值类型数据配合良好,可是若是牵扯 object 类型,就须要做出一些额外的考虑:优化

    • useState hook 的 setState 和 this.setState 的行为并不同,其中很重要的一点,对于 this.setState,会对先后的 state 进行合并,因此当咱们 this.setState 时,只要写上更改的数据便可,而 useState hook 的 setState 则是替换。因此,当你使用 useState 来保存 object 时,你可能会须要写上大量的 ... 运算符。
  • setState 和 this.setState 阻止更新的行为不同。不一样于 this.setState(null),setState(null) 依旧会触发更新,而且会把 state 赋值成 null。当你使用 setState 时,react 会将先后的 state 进行 === 比较,若是同样,则不会触发更新。因此若是你使用 useState 保存 object,即便新的 object 中的值没有发生变化,可是因为生成了新的引用,依旧会触发更新。这也是为何尽可能不要使用 useState 来储存 object,对于复杂的本地状态,建议使用 useReducer 来管理。this

2. 类组件 this.setState

关于 this.setState 须要的注意的是:spa

  • 设置空对象 this.setState({}) 依旧会触发更新

  • this.setState(null) 不会触发更新

  • this.setState 同步异步问题:

    • 在事件处理函数,或生命周期函数,react 会对 this.setState 做 batching 优化,此时 this.setState 是异步的。

    • 其余时候,例如 setTimeout 或 promise 中,this.setState 是同步的。

3. 父组件更新(props 变化)

这是我想咬文嚼字的一节,在阅读 react 相关文章时,常常会看到 props变化,致使组件发生了更新,这听起来似乎 react 在更新时会对比先后的 props,只有发生了变化时才会更新组件。又或者有过 vue 经验的新人,可能认为 react 对 props 对象进行了监听,因此发生变化时才会更新,然而,以上两种解释都是错误的。

function Box(props) {
    const { count } = props;
    return <div>{count}</div>;
}

function Counter() {
    const [count, setCount] = useState(0);

    const [useLessCount, setUseLessCount] = useState(0);

    return (
        <div> <Box count={count} /> <button onClick={() => setCount(prev => prev + 1)}>INC</button> <button onClick={() => setUseLessCount(prev => prev + 1)}>useLess</button> </div> ); } 复制代码

当 INC 按钮被点击时,Box 组件显示的数值加了 1,让咱们走一遍这个 更新 的流程,看看 props 变化是否致使了组件 更新

流程:

  • count = 0
  • INC 按钮被点击
  • Counter 发生更新,React 执行 Counter 函数生成虚拟DOM
  • Counter 函数执行中,count 经过 useState 拿到改变后的新值,此时 count = 1
  • React 经过 createElement 建立 Box 组件的虚拟DOM,注意此时的 Box 的 props 中 count 被赋值为 1
  • React 以更新后的的 props 调用 Box 函数,生成其虚拟 DOM
  • Box 组件的虚拟 DOM 进行 tree diff,发现 count 值改变
  • 最后 commit 到真实 DOM 上

纵观整个流程,react 一直遵循着一个简单的策略 组件更新,则更新其全部的子组件,且不断递归的运用这个策略,直到叶子节点。和 props 自己没有任何关系。也就是说,子组件的更新是因为父组件更新引发的,而非 props 的变化

另外一个更直接的证据是,点击 useLess 按钮,一样会致使 Box 组件更新,即便 count 的值并无发生改变。

props 变化,致使组件发生了更新,犯了一个典型的因果谬误,A 先发生,而后 B 发生了,因此 A 致使了 B 发生。

这句话的主要问题在于,它会给初学者创建一种错误的心智模型,认为其中有什么 fancy 的 magic 发生,然而本质上只是遵循一个简单更新策略的天然而然的结果。

最后,任什么时候候,props 变化,致使组件发生了更新 这句话都是错误的,可是对于本小节中的例子来讲:props 变化,致使组件内容发生了变化 是正确的。

既然都谈了这么多,我就多说一点,人在交流时,假若不对一些模棱两可的词语的意思做出规定,很是容易变成你们自说自话,我不一样意你的,你不一样意个人,谁也说服了不了谁。我本觉得这是技术讨论的常识,然而却发现知乎上天天都在发生这样的对话,不多发现充满价值的讨论。

相关文章
相关标签/搜索