React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React渲染机制

React渲染机制及相关优化方案

作者:大橘为重¨

这篇文章主要介绍了react中的渲染机制以及相关的优化方案,内容包括react渲染步骤、concurrent机制以及产生作用的机会,简单模拟实现 concurrent mode,基于作业调度优先级的思路进行项目优化的两个hooks,感兴趣的小伙伴跟着小编一起来看看吧

一、react渲染步骤

二、concurrent机制以及产生作用的机会

注:React 的并发模式(Concurrency Mode)是一种用于处理大型和复杂应用程序的特性,旨在提高应用程序的性能和响应能力。解决react中状态更新就会触发该组件及该组件下所有子组件无脑更新而引发的性能问题;同时提供部分控制作业调度优先级的能力给开发者使用

补充:concurrent mode 主要工作在渲染流程的 Compute Phase 及 Render Phase,因为它们是纯粹的 JS 计算意味着可以被拆分,而 commit 阶段由于带有 DOM 更新,不可能 DOM 变更到一半中断,因此必须一次性执行完成

1. 优先级调度:

concurrent mode 通过对任务进行优先级划分,React 可以根据优先级动态地分配和重新分配任务。基于此React 可以更好地响应用户交互和其他高优先级的任务,同时提供了 “useDeferredValue” 、“useTransition” 两个hooks用于调度作业任务的优先级。

2. 递增式渲染:

1)concurrent mode 下的渲染是逐步进行的,React 将大量需要重新渲染的组件的工作基于时间片的理念划分为多个小片段工作,在浏览器的每一帧的空闲时间中去执行这些渲染工作,而不是一下子全部直接执行,这样有效的避免了掉帧情况的出现。

2)这里也就说明了为什么React官方说 componentWillMount 可能被调用多次的原因,正是因为低优先级任务的 render 阶段可能被重复的中断和重新执行,而 componentWillMount 就包含在 render 阶段中。

注意:工作拆分的最小单元应该是一个fiber节点,当某个fiber节点本身的计算就十分巨大时依然会导致卡帧,不过我们可以通过调整工作的优先级使得用户的体验是平滑的

三、简单模拟实现 concurrent mode 的递增式渲染

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="index.js"></script>
</head>
<body>
    <div id="root"></div>
    <script>
        // 调用render提供挂载容器 "root"
        render(document.getElementById('root'))
    </script>
</body>
</html>

index.js

// 页面需要渲染的组件
function Counter() {
    return {
        type: 'span',
        value: 'hello world',
        next: {
            type: 'p',
            value: 'hello LiHua'
        }
    }
}
const CounterElementDescriptors = {
    type: 'Function',
    fn: Counter
}
// 记录当前工作
let presentWork = null
// 记录根元素
let rootElementDescriptor = null    
// 记录挂载容器 
let elementsContainer = null    
// 处理单元任务
function performUnitOfWork(deadline) {
    // 判断当前是否还有待执行任务
    if (presentWork == null) return commitRoot(rootElementDescriptor)
    // 当前帧超时,调用 requestIdleCallback 把任务推到下一帧空闲时间执行
    if (deadline.didTimeout) return requestIdleCallback(executeWorkLoop)
    // 若是组件则处理依赖关系、若是元素则生成真实dom
    if (presentWork.type === "Function") {
        rootElementDescriptor = presentWork
        const firstChildren = presentWork.fn()
        firstChildren.parent = presentWork
        presentWork.children = firstChildren
        presentWork = firstChildren
        performUnitOfWork(deadline)
    } else {
        const dom = document.createElement(presentWork.type)
        dom.innerHTML = presentWork.value
        presentWork.dom = dom
        presentWork = presentWork.next
        performUnitOfWork(deadline)
    }
}
// 控制循环执行工作
function executeWorkLoop(deadline) {
    performUnitOfWork(deadline)
}
// 提供render函数,用于获取挂载容器和开始渲染计算工作
function render(element) {
    elementsContainer = element
    presentWork = CounterElementDescriptors
    requestIdleCallback(executeWorkLoop)
}
// 模拟commit阶段
function commitRoot(rootElement) {
    let renderCHildrenElements = rootElement.children
    do {
        elementsContainer.appendChild(renderCHildrenElements.dom)
        renderCHildrenElements = renderCHildrenElements.next
    }while(renderCHildrenElements)
}

四、与优先级调度有关的两个hooks

1. useTransition

官方解释:useTransition 是一个让你在不阻塞 UI 的情况下来更新状态的 React Hook。

import { useTransition } from "react";
function TabContainer() {
    // isPending 标志,告诉你是否存在待处理的低优先级工作。
    // startTransition 函数 允许你将该部分的状态更新标记为低优先级。
    const [isPending, startTransition] = useTransition();
    function handle() {
        startTransition(() => {
            // 低优先级的状态更新工作
            {......}
        });
    }
    return (
        {......}
    )
}

2. useDeferredValue

官方解释:useDeferredValue 是一个 React Hook,可以让你延迟更新 UI 的某些部分。

import { useDeferredValue, useState, } from "react";
function TabContainer() {
    const [query, setQuery] = useState('');
    // 定义的 deferredQuery 获取的是query的延迟版本
    const deferredQuery = useDeferredValue(query);
    function handle(data) {
        setQuery(data)
    }
    return (
    	<>
    		<List listData={deferredQuery} />
    		{ ......}
    	</>
    )
}

3. useTransition 与 useDeferredValue 的区别

4. 应用场景

1)长列表渲染:当渲染大量列表项时,可以对列表项的渲染任务调节为低优先级异步任务,以保证用户界面的响应性能。

2)大型表单处理:对于包含大量输入字段的表单,可以使用合理使用对于hooks将表单提交和验证等任务进行优化调节,以避免阻塞用户界面。

3)图片懒加载:当页面中包含大量图片时,可以使用 useTransition 将图片的加载划分为多个低优先级异步任务,在渲染期间逐步加载图片,以减少对用户界面的阻塞。

4)异步数据加载:当页面中的数据需要从后端异步加载时,可以使用 useTransition 将数据的加载划分为多个异步任务,以保证用户界面的响应性能。

五、一个小例子

基础代码,未作优化处理:

import React, { useCallback, useState } from 'react'
const index: React.FC = () => {
    const [list, setList] = useState<any[]>([])
    const handleSearch = useCallback((value: string) => {
            const newList = []
            for (let i = 0; i < 5000; i++) {
                newList.push(value + '-' + i)
            }
            setList(newList)
    }, [])
    return (
        <>
            <input onChange={(e) => handleSearch(e.target.value)} type='text' />
            <div>
                {list.map(item => <div key={item}>数据项:{item}</div>)}
            </div>
        </>
    )
}
export default index

当我们进行持续的输入时是十分的卡顿的,效果如下:

1. 下面使用 useTransition 进行优化

代码修改如下:

import React, { useCallback, useState, useTransition } from 'react'
const index: React.FC = () => {
    const [list, setList] = useState<any[]>([])
    const [isPending, startTransition] = useTransition()
    const handleSearch = useCallback((value: string) => {
        startTransition(() => {
            const newList = []
            for (let i = 0; i < 5000; i++) {
                newList.push(value + '-' + i)
            }
            setList(newList)
        })
    }, [])
    return (
        <>
            <input onChange={(e) => handleSearch(e.target.value)} type='text' />
            <div>
                {isPending? '加载中。。。' : list.map(item => <div key={item}>数据项:{item}</div>)}
            </div>
        </>
    )
}
export default index

优化后效果如下:

2. 使用 useDeferredValue 进行优化

代码修改如下:

import React, { memo, useDeferredValue, useState } from 'react'
const Item = ({ text }: any) => {
    return (
        <div>
            数据项:{text}
        </div>
    )
}
const List = memo(({ inputValue }: { inputValue: string }) => {
    let items = [];
    for (let i = 0; i < 5000; i++) {
        items.push(<Item key={i} text={inputValue + '-' + i} />);
    }
    return (
        <>
            {items}
        </>
    );
})
const index: React.FC = () => {
    const [inputValue, setInputValue] = useState('')
    const deferredInputValue = useDeferredValue(inputValue)
    return (
        <>
            <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} type='text' />
            <List inputValue={deferredInputValue} />
        </>
    )
}
export default index

优化后效果如下:

补充:为什么VUE不需要设计 Concurrent Mode

1)在vue中,响应式系统通过 proxy 实现对 render函数 的依赖收集和触发更新,基于追踪组件依赖的响应式数据的变化,可以更为精准的实现组件的更新,大大避免了不必要的渲染和更新操作,规避了react中状态更新就会触发组件及该组件下所有子组件无脑更新的问题。

2)同时vue的异步更新策略也有助于提高性能和响应能力。Vue会在下一个事件循环周期中批量更新组件,这样可以避免频繁的DOM操作和重复渲染,提高渲染效率。

3)但vue中暂时没有 useTransition 和 useDeferredValue 类似的功能操作,无法调度控制作业的优先级

以上就是React渲染机制及相关优化方案的详细内容,更多关于React渲染机制的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文