最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 为什么「不变的虚拟 DOM」可以避免组件重新 Render

    正文概述 掘金(MoonBall)   2021-04-29   547

    TL;DR

    本文包括:

    1. 使用「不变的虚拟 DOM」避免组件 Render 的两个例子
    2. 什么是「不变的虚拟 DOM」
    3. React 调和阶段伪代码
    4. 「不变的虚拟 DOM」避免组件 Render 的原因

    例一

    代码如下,Parent 组件每秒更新状态后,Child 组件会重新 Render 吗?线上 Demo 请戳这里。

    function Child() {
      return <div>子组件</div>
    }
    
    // 该 Hook 用于每秒更新状态
    function useUpdateStateEverySecond() {
      const [, setState] = useState(0)
      useEffect(() => {
        const timer = window.setInterval(() => {
          setState(v => v + 1)
        }, 1000)
        return () => clearInterval(timer)
      }, [setState])
    }
    
    function Parent({ children: child }) {
      useUpdateStateEverySecond()
    
      return (
        <div>
          父组件
          {child}
        </div>
      )
    }
    
    function App() {
      return (
        <Parent>
          <Child />
        </Parent>
      )
    }
    

    乍一看肯定会认为 Child 组件会重新 Render。但实际上:每秒更新状态后,虽然 Parent 组件会再次 Render,但 Child 组件不会。

    例二

    代码如下,通过 useMemo 实现「不变的虚拟 DOM」。线上 Demo 请戳这里。

    function Child() {
      return <div>子组件</div>
    }
    
    function Parent() {
      useUpdateStateEverySecond()
    
      const child = useMemo(() => <Child />, [])
    
      return (
        <div>
          父组件
          {child}
        </div>
      )
    }
    

    例三(优化前)

    为了与前面两例做对比,将优化前的组件写法也列在这里,代码如下。线上 Demo 请戳这里。

    function Child() {
      return <div>子组件</div>
    }
    
    function Parent() {
      useUpdateStateEverySecond()
    
      return (
        <div>
          父组件
          <Child />
        </div>
      )
    }
    

    该例子中,每秒更新状态后,Parent 组件和 Child 组件都会再次 Render。


    尽管例一和例二的代码组织方式和优化思路不同,但对 React 来说,它们对应的底层原理是相同的。

    为了将原理讲清楚,我们需要先从 JSX 和虚拟 DOM 说起,了解什么是「不变的虚拟 DOM」。再结合 React 调和阶段的伪代码,将优化背后的原理陈诉清楚。

    不变的虚拟 DOM

    JSX 是一种描述虚拟 DOM 的语法。我们编写的 JSX 代码 <Child /> 最终会被转换成 React.createElement(Child, {}, null)。参考 React Without JSX 了解更多转换 JSX 后的代码。

    在例一和例二中,Parent 函数的返回值可以用 createElement 表达为:

    function Parent() {
      return createElement(div, {}, ["父组件", child])
    }
    

    而例三中,Parent 函数的返回值为:

    function Parent() {
      return createElement(div, {}, ["父组件", createElement(Child, {}, null)])
    }
    

    两份代码的区别非常明显。优化前,Parent 函数执行时,会重新生成 Child 组件对应的虚拟 DOM。而优化后 Child 组件对应的虚拟 DOM 并没有改变,它始终是 child。

    React 调和阶段(简化版)

    为了说明优化背后原理,将 React 调和阶段简化为以下伪代码:

    /**
     * current 表示状态更新前的虚拟 DOM
     * next 表示状态更新后的虚拟 DOM
     */
    function reconcile(current, next) {
      let needRender = false
      if (current.props !== next.props) {
        // 虚拟 DOM 的 Props 是新的,则需要执行 Render 过程
        needRender = true
      } else if (current.stateHasChanged) {
        // 该虚拟 DOM 的状态发生了更新,则需要执行 Render 过程
        needRender = true
      } else if (current.descendantStateHasChanged) {
        // 该虚拟 DOM 的子孙存在状态更新,不会执行该组件的 Render 过程
        // 但是将递归对子孙虚拟 DOM 执行调和阶段
        reconcile(current.child, current.child)
      }
    
      if (needRender) {
        // current.func 表示虚拟 DOM 对应的组件函数,比如:例子中 Parent、Child
        nextChild = current.func(current.props)
        reconcile(current.child, nextChild)
      }
    }
    
    // 调和阶段的起点为:
    reconcile(currentRoot, currentRoot)
    

    水到渠成

    在例三中,更新 Parent 组件状态后,Child 组件对应的虚拟 DOM 是新生成的。因此,current.props 将不等于 next.props,所以 Child 组件将重新 Render。

    在例二中,更新 Parent 组件状态后,Parent 组件重新 Render。但由于 child 使用了 useMemo 进行缓存,所以 Child 组件对应的虚拟 DOM 不变,则 current.props 等于 next.props。又由于该虚拟 DOM 没有状态更新,所以不需要重新 Render。

    在例一中,更新 Parent 组件状态后,Parent 组件重新 Render。但由于 App 组件不会重新 Render,所以 Parent 组件收到的 children 属性值不变。因此,对于 Child 组件对应的虚拟 DOM,current.props 等于 next.props。又由于该虚拟 DOM 没有状态更新,所以不需要重新 Render。

    总结

    尽管以上三个例子对应的 JSX 代码相同,它们都是:

    <Parent>
      <Child />
    </Parent>
    

    但在例三(优化前)中,每次状态更新都会重新生成 <Child /> 对应的虚拟 DOM。而在优化后的例一和例二中,<Child /> 对应的虚拟 DOM 始终不变。

    在调和阶段,如果虚拟 DOM 不变(即 props 引用值相等),且没有状态更新,那么就跳过该虚拟 DOM 的调和阶段。

    以上便是可使用不变的虚拟 DOM 进行性能优化的原因。

    推荐更多 React 文章

    1. React 性能优化 | 包括原理、技巧、Demo、工具使用
    2. 聊聊 useSWR,为开发提效 - 包括 useSWR 设计思想、优缺点和最佳实践
    3. React 为什么使用 Lane 技术方案
    4. React Scheduler 为什么使用 MessageChannel 实现

    原创不易,别忘了点赞鼓励哦 ❤️


    起源地下载网 » 为什么「不变的虚拟 DOM」可以避免组件重新 Render

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元