react dom的diff理解及性能优化

diff的三大过程

当某个值变化时,他从根组件寻找
(key,state,props,context)
当父组件稳定时,react会跳过子组件的props的对比
只有当当前组件值改变时,从他开始,所有的子孙节点都会对比props
props是全等比较,所以,都会触发重新渲染

(比如把组件A移动到父组件的某个兄弟节点位置,
那么我diff的过程中,如何寻找这个组件呢,就必须跨层级递归寻找
非常耗性能)

所以,React假设,没有跨层级的移动组件
(因为实际开发中跨层级的移动组件确实极其的少)
所以diff就可以只比较同级的节点,性能从O n^3 变O n

在 React 15 中是递归处理虚拟 DOM 的,
React 16 则是变成了可以中断的循环过程,
	Scheduler 调度器 —— 收集变化 调度任务的优先级,高优任务优先进入 Reconciler
	Reconciler 协调器 —— 负责找出变化的组件 diff算法  可中断
	Renderer 渲染器 —— 负责将变化的组件渲染到页面上


Fiber 架构的核心即是"可中断"、"可恢复"、"优先级"
作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,
保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。
作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作

Fiber 把一个渲染任务分解为多个渲染任务,
而不是一次性完成,把每一个分割得很细的任务视作一个"执行单元"

React 16 则是变成了可以中断的循环过程,

每次循环都会调用shouldYield判断当前是否有剩余时间。
requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态 去中断
然后	新一轮的调度开始

useMemo

https://juejin.cn/post/7253980320357269561?searchId=20231209154011153FF01F950F1F4D3A93#heading-2

 const childFucntion = useCallback(() => {
    action()
 }, [a, b])
作用:用于优化渲染性能。

useMemo 会接收一个箭头函数包裹的回调函数和依赖项数组,然后返回回调函数的计算结果。
当依赖项数组中的某个值发生变化时,useMemo 会重新计算回调函数。

如果依赖项没有发生变化,useMemo 会返回上一次计算的结果,
这样可以避免不必要的计算。

如下,只有在a或者b发生改变的时候,value的值才会重新计算。



react 面试题整理

setstate是同步还是异步

setState自动批处理

原生事件和setTimeout 中都是同步的

合成事件和钩子函数中 是异步的

JSX

JSX JavaScriptXML的简写
HTMLJS结合的语法
JSX是react的语法糖,它允许在html中写JS,它不能被浏览器直接识别,
需要通过webpack、babel之类的编译工具转换为JS执行

函数组件与类组件的区别

函数组件以前被叫做无状态组件,就是因为函数组件内部不能保存state
类组件需要声明constructor,函数组件不需要
类组件需要手动绑定this,函数组件不需要
类组件有生命周期钩子,函数组件没有
类组件可以定义并维护自己的state,属于有状态组件,函数组件是无状态组件
类组件需要继承class,函数组件不需要
类组件使用的是面向对象的方法,封装:组件属性和方法都封装在组件内部 
继承:通过extends React.Component继承;
函数组件使用的是函数式编程思想

render的高级

render props
组件允许通过属性传入一个函数, 该函数返回一个 React 元素
组件内部通过调用该函数, 来渲染部分内容
组件内调用函数时允许为函数传递任意参数, 可以是组件内部状态、方法、或其他任意数据

HOC高阶组件


 本质上就是一个函数, 是一个参数为组件, 返回值为新组件的函数
 强化 props: 类似 withRouter 为组件添加 props 属性,

强化组件功能劫持控制渲染逻辑: 
  通过反向继承方式, 拦截原组件的生命周期、渲染、内部组件状态...
动态加载组件, 根据 props 属性, 动态渲染组件, 比如添加 logding、错误处理等待...
拦截组件渲染,包括是否渲染组件、懒加载组件
更好地复用组件逻辑,
代码复用
组件增强优化
Refs 不会被传递: 需要使用 React.forwardRef 进行处理

fiber

内部状态和外部参数,
key的变化

16之前 树的深度优先遍历完成的,遍历是不能中断的
16之后循环来代替之前的递归.
一个循环就是一个时间切片,一个小任务
时间切片,在线程空闲的时候执行,
requestIdleCallback

虚拟DOM和diff算法

 当页面频繁操作时, 不去频繁操作真实 DOM
 虚拟 DOM可以跨平台:
一次性更新:

### diff
React 在执行 render 过程中会产生新的虚拟 DOM
React 会对新旧虚拟 DOM 进行 diff 算法找到它们之间的差异,
 尽量复用 DOM 从而提高性能;
  所以 diff 算法主要就是用于查找新旧虚拟 DOM 之间的差异

 tree 层级(同层级比较)
  考虑到在实际 DOM 操作中需要跨层级操作的次数很少很少,
  只需要对树遍历一次就 OK ,
 component 层级:
    如果是同一个类型的组件, 则会继续往下 diff 运算, 如果不是一个类型组件, 
    那么将直接删除这个组件下的所有子节点,
    只做,插入,移动和删除

element 层级: 是同一层级的节点的比较规则

(tree比较通常是对整个DOM树或虚拟DOM树进行比较,
而element比较则是对同一层级的两个具体节点进行比较。)

受控组件和非受控组件

受状态控制的组件,必须要有onChange方法,否则不能使用 
受控组件可以赋予默认值(官方推荐使用 受控组件) 
实现双向数据绑定 form 数据被DOM本身控制,
可以用ref获取value,叫非受控组件。

合成事件


合成事件是事件委托的一种实现, 
主要是利用事件冒泡机制将所有事件在 document 进行统一处理,
会先执行原生事件,然后处理 React 事件
在底层磨平不同浏览器的差异
优点
减少事件注册, 减少内存消耗, 提升性能, 
只在 document 上注册一次即可
统一处理, 并提供合成事件对象, 抹平浏览器的兼容性差异
useCallback
 const childFucntion = useCallback(() => {
    action()
 }, [a, b])
 
 当依赖数组中的值发生变化时,useCallback 会返回一个新的函数实例。
 否则,它将返回上一次创建的函数实例
他俩是如何做性能优化的呢
当你去改变父组件中的state,就会导致父组件重新构建,
而父组件重新构建的时候,会重新构建父组件中的所有函数
(旧函数销毁,新函数创建,等于更新了函数地址),
新的函数地址传入到子组件中被props检测到栈地址更新。
也就引发了子组件的重新渲染。

useCallBack并不能阻止函数重新创建
,它只能通过依赖决定返回新的函数还是旧的函数,
从而在依赖不变的情况下保证函数地址不变
useCallBack需要配合React.memo使用
useMemo会执行回调函数并且返回结果,
但是useCallback不会执行回调函数。

他俩都需要 结合React.Memo进行使用

useEffect

useEffect(()=>{},[])

setTimeout(()=>{
	dosomesing
},0)

你可能感兴趣的:(react,react.js,javascript,ecmascript)