【笔记摘要】React 框架
useEffect 将组件连接到外部系统,处理网络、浏览器、DOM、动画、使用不同 UI 库编写的小部件以及其他非 React 代码。
useInsertionEffect 在 React 对 DOM 进行更改之前触发,库可以在此处插入动态 CSS
默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,但如果你发现某次重新渲染很慢,你可以将 子组件包裹在 memo 中。如果 props 和上一次渲染时相同,那么 子组件将跳过重新渲染。 useCallback 在多次渲染中缓存函数,只要这些依赖没有改变,子组件就会收到同样的 props 并且跳过重新渲染。
使用memo缓存组件后,组件函数内就不需要useCallback了
useCallback 只应作用于性能优化。
useMemo 经常与 useCallback 一同出现
使用 useCallback 缓存函数仅在少数情况下有意义:
• 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。
• 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 useCallback 中的函数依赖于它,或者依赖于 useEffect 中的函数
从 Effect 中删除不必要的依赖关系
Object.fromEntries 键和值的数组,或者是具有 Map 对象=>json对象
• 当需要将一组键值对数据(如从服务器获取的 JSON 数据)转换为对象以便于操作时。
• 在处理 URLSearchParams、解码查询字符串等场景,可以先转换为键值对数组,再通过 Object.fromEntries() 转换为对象处理
const formData = new FormData(e.target);
const orderDetails = {
...Object.fromEntries(formData),
count
};
函数作为Effect依赖,防止Effect频繁出发, 调用的函数包裹在 useCallback 中。但是,最好消除对函数依赖项的需求。将你的函数移入 Effect 内部:
自定义 Hook,建议将它返回的任何函数包裹在 useCallback 中
不加任何依赖,useCallback 缓存失效
在最顶层调用 useCallback
不能在循环中调用 useCallback,相反,为单个项目提取一个组件,然后使用 useCallback
——————————————————————————————————————
useContext() 总是在调用它的组件 上面 寻找最近的 provider。它向上搜索, 不考虑 调用 useContext() 的组件中的 provider。
createContext() create同一个context, 可以共用一个
export const LevelContext = createContext(0); 多文件导入
可以共用一个
当useCallback的依赖列表为空[]时 永久缓存;
适用场景
• 不需要依赖的高性能函数:如果你有一个性能敏感的函数,且确定它不依赖于任何可能变化的值,可以使用空依赖数组来确保它在整个组件生命周期内只被创建一次。
• 传递给子组件的回调:如果你将回调函数作为props传递给子组件,并且该回调不依赖于任何可能在渲染期间变化的值,使用空依赖列表可以避免子组件因回调函数引用变化而引起的不必要的重新渲染。
React 重新渲染树中调用 useContext(XX) 的所有组件
通义千问
——————————————————————————
如果你 没有打算与某个外部系统同步,那么你可能不需要 Effect。只能在 组件的顶层 或自己的 Hook 中调用它
如果你的 Effect 不是由交互(比如点击)引起的,你的 Effect 做一些UI相关的事情(例如,定位一个 tooltip),并且有显著的延迟(例如,它会闪烁),那么将 useEffect 替换为 useLayoutEffect
Effect 是由一个交互(比如点击)引起的,React 也可能允许浏览器在处理 Effect 内部的状态更新之前重新绘制屏幕。通常,这样是符合预期的。但是,如果你一定要阻止浏览器重新绘制屏幕,则需要用 useLayoutEffect
useEffect用法
外部系统 事件、动画、dom、请求、计时器、第三方等react之外的,通常会和return一个cleanup函数一起使用,销毁什么的
useRef 控制dom
响应式值<=>依赖项
如果完全没有依赖数组,则 Effect 会在组件的 每次单独渲染(和重新渲染)之后 运行。没有依赖项数组:每次重新渲染后重新运行
Effect 在每次 依赖项 更改时再次执行 cleanup 和 setup;
合理的去掉依赖项使用c => c + 1 状态更新器;
setCount(c => c + 1)
Effect 依赖于在渲染期间创建的对象或函数,则它可能会频繁运行,则吧函数或对象放到effect内部,移除对应的依赖项;
useEffectEvent 可以实现把某个响应依赖项的变成不响应,从中分离出来;
ref(它不会触发重新渲染)
cleanup 函数不仅在卸载期间运行,也在每个依赖项变更的重新渲染前运行
如果 Effect 一定要阻止浏览器绘制屏幕,使用 useLayoutEffect 替换 useEffect。
——————————————————————————————————————
useImperativeHandle
自定义由 ref 暴露出来的句柄 结合forwardRef使用
useLayoutEffect 是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发,影响性能
useMemo 是一个 React Hook,所以你只能 在组件的顶层 或者自定义 Hook 中调用它
Object.is() Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候
Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true
0. 避免 不必要地更新 state 的 Effect。React 应用程序中的大多数性能问题都是由 Effect 创造的更新链引起的,这些更新链导致组件反复重新渲染。
0. 尽力 从 Effect 中移除不必要的依赖项。例如, 相比于记忆化,在 Effect 内部或组件外部移动某些对象或函数通常更简单
要使用 useMemo 记忆函数,你的计算函数必须返回另一个函数 等价于 useCallback
————————————————————————————————————————————————
reducer 里面不允许改state,正确的做法是返回新的对象
useImmerReducer
useRef 定义不响应的值。一个不需要渲染的值,改变 ref 不会触发重新渲染, ref 不适合用于存储期望显示在屏幕上的信息, 要用state
• 使用用 ref 引用一个值
• 通过 ref 操作 DOM
• 避免重复创建 ref 的内容
根据先前的 state 更新 state,传递一个 更新函数。
如果它是从某个其他状态变量的先前状态计算出的,则你可能希望将它们结合成一个对象然后 使用 reducer。
setState更新状态中的对象和数组,使用新对象替换
避免重复创建初始状态
如果将函数传递给 useState,React 仅在初始化期间调用它。
使用 key 重置状态
通过向组件传递不同的 key 来重置组件的状态
在渲染过程中不能调用事件处理函数,可以传递事件函数
存储传递函数 ,使用() =>
useTransition 在不阻塞 UI 的情况下更新状态,中断之前未完成的渲染,马上执行新的渲染
React 框架或路由,我们建议将页面导航标记为转换效果
这么做有两个好处:
• 转换效果是可中断的,这样用户可以在等待重新渲染完成之前点击其他地方。
• 转换效果可以防止不必要的加载指示符,这样用户就可以避免在导航时产生不协调的跳转。
useFormStatus 仅会返回父级 <form> 的状态信息。
如果调用 useFormStatus 的组件未嵌套在 <form> 中,status.pending 总是返回 false。请验证 useFormStatus 是否在 <form> 元素的子组件中调用。
——————————————————————————————
• 对于大多数基本类型的比较,Object.is() 和 === 的行为是一致的。
• 当需要特别区分 NaN 和正负零 (+0 和 -0) 时,应该使用 Object.is()。
• 在日常开发中,=== 通常足够使用,但在处理特殊数值(如 NaN 和正负零)时,使用 Object.is() 可以提供更精确的比较结果。
——————————————————
useTransition — 中断之前渲染
transition 将整个更新标记为非紧急的
useDeferredValue — 优化渲染必备 渲染期间的hook
延迟更新 UI
• 在新内容加载期间显示旧内容。
• 表明内容已过时
• 延迟渲染 UI 的某些部分
在更新期间,延迟值 会“滞后于”最新的 值。具体地说,React 首先会在不更新延迟值的情况下进行重新渲染,然后在后台尝试使用新接收到的值进行重新渲染
React 将会中断渲染,并从新值开始重新渲染;
使用useDeferredValue的值将某些组件渲染优先级降低;
配合memo使用 ,还可以用于<Suspense>
应用场景:useTransition 更适合用于控制那些影响用户体验的关键状态更新,如页面导航、排序、过滤等操作,确保这些动作流畅进行;而 useDeferredValue 则适用于优化那些对用户体验影响较小,但频繁更新导致性能瓶颈的场景,比如长列表滚动
• 性能优化方式:useTransition 通过控制更新的时机来优化,让UI有时间准备和展示过渡动画;useDeferredValue 则是通过引入一个延时的值来减少不必要的渲染,特别是对于那些依赖状态频繁变化的部分。
• 目的:两者都旨在提升用户体验,但通过不同的机制实现:useTransition强调平滑过渡,而useDeferredValue注重减少不必要的工作量
————————————————————————————————
组件<Suspense>
在子组件完成加载前展示后备方案
• 当内容正在加载时显示后备方案
• 同时展示内容
• 逐步加载内容
• 在新内容加载时展示过时内容
• 阻止隐藏已经显示的内容
• 表明 Transition 正在发生
• 在导航时重置 Suspense 边界 设置key,确保 React 将不同路由视为不同的组件,并在导航期间重置 Suspense 边界,触发fallback
如果 children 在渲染中被挂起,Suspense 边界将会渲染 fallback.
延迟值和 transition 都可以让你避免显示 Suspense 后备方案,
使用 startTransition 将更新标记为非紧急的。在 Transition 期间,React 将等待足够的数据加载,以防止不需要的后备方案出现
启用了 Suspense 的路由在默认情况下会将导航更新包装至 Transition 中
————————————————————————————————
组件<Fragment> (<>...</>)
在不添加额外节点的情况下将子元素组合
• 返回多个元素
• 分配多个元素给一个变量
• 组合文本与组件
• 渲染 Fragment 列表. <Fragment key={post.id}>
————————————————————————
API- cache
cache(fn)
React 只允许在组件内访问记忆化函数的缓存
useMemo 的缓存仅在组件内部可用
cache 应用于服务器组件以记忆化可以跨组件共享的工作
cache 也推荐用于 记忆化数据获取,而 useMemo 只应用于计算。
与 useMemo 相比,memo 根据 props 而不是特定计算来记忆化组件渲染。与 useMemo 类似,记忆化的组件只缓存了具有最后一组 prop 值的最后一次渲染。一旦 props 更改,缓存将失效,组件将重新渲染
memo应用于整个组件层面,用来决定是否重新渲染组件;而useMemo则是在组件内部,针对某个特定值的计算进行优化。
整个组件的渲染逻辑,考虑使用memo;如果是组件内部某个计算值的优化,则使用useMemo;而cache更多是一种思想,通过各种方式实现数据或计算结果的复用。
forwardRef
允许组件使用 ref 将 DOM 节点暴露给父组件
//父组件ref
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
返回一个可以在 JSX 中渲染的 React 组件
forwardRef 返回的组件还能够接收 ref 属性。
useImperativeHandle
在组件顶层通过调用 useImperativeHandle 来自定改写父组件 ref
const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... 你的方法 ...
};
}, []);
ref传递给子组件,父组件就能访问到子组件的ref,通常会暴露可重用的低级组件的 DOM 节点,例如按钮或文本输入框。
lazy
在组件外部调用 lazy,以声明一个懒加载的 React 组件 结合 <Suspense>
lazy(() => import(‘’)
使用这种模式要求导入的懒加载组件必须作为 default 导出。
memo 允许你的组件在props、state 和 context没有改变的情况下跳过重新渲染;
use 是一个 React API,它可以让你读取类似于 Promise 或 context 的资源的值
flushSync 允许你强制 React 在提供的回调函数内同步刷新任何更新,这将确保 DOM 立即更新
createPortal 允许你将 JSX 作为 children 渲染至 DOM 的不同部分;portal 来管理在 React 之外管理的 DOM 节点
——————————————————————————————————
React 理解
纯函数:组件作为公式
纯函数仅执行计算操作,不做其他操作
• 只负责自己的任务。它不会更改在该函数调用前就已存在的对象或变量。
• 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果
更新屏幕、启动动画、更改数据等,它们被称为 副作用。它们是 “额外” 发生的事情,与渲染过程无关.副作用通常属于 事件处理程序.使用useEffect
因为纯函数总是返回相同的结果,所以可以安全地缓存它们,可以为那些输入未更改的组件来 跳过渲染.=>延伸到相关跳过渲染的hook和api
可以从组件树 的概念理解。顶级组件会影响其下所有组件的渲染性能,而叶子组件通常会频繁重新渲染
react的能力:从数据获取到动画再到性能,保持组件的纯粹。
数据=>useContext/createContext redux useState useDeferredValue useRef useReducer
动画=>useTranstion/startTransition useDeferredValue
渲染=>纯函数
性能=>缓存、记忆化hook 或api memo、lazy、useCallback useMemo、cache
其他=>副作用 useEffect
dom=>useImperativeHandle forwardRef useRef createPortal
React 19
表单 useActionState
——————————————————————————————
返回新对象、新书组的都可以用来克隆。
使用slice() 数组等克隆副本arr.slice()
更改数据不变性很重要:
不变性使复杂的功能更容易实现,避免数据直接突变可以让你保持以前版本的数据完好无损,并在以后重用它们.
不变性使得组件比较其数据是否已更改的成本非常低,避免重新渲染的问题;
——————————————
Reducer 可以让您合并多个状态变量到一个对象中
useReducer
将组件的所有状态更新逻辑整合到一个外部函数中, reducer 整合状态逻辑
Immer 简化 reduce
useImmerReducer
——————————————————
并发模式原理 分块渲染: React 将渲染工作分成更小的块,从而允许浏览器在渲染之间执行高优先级任务
增量渲染:允许 React 将渲染工作分成几块并将其分布到多个帧上,从而防止主线程被阻塞
React Fiber 通过将渲染过程分解为增量工作单元来提高渲染效率,尤其是在复杂应用程序中。这使 React 能够暂停、恢复和确定任务的优先级
——————————————————————————————
React Fiber的底层原理涉及到一些较为深入的技术细节,主要包括任务拆分、工作循环、优先级调度以及可中断的渲染流程。以下是对这些核心概念的详细解释:
1. 任务拆分与工作单元(Work Units)
Reconciliation拆分为小任务:在Fiber之前,React的reconciliation(同步的DOM树差异比较)是一个连续的过程,容易阻塞UI线程。Fiber将这个过程拆分成一系列小任务,每个任务对应虚拟DOM树中的一个Fiber节点。这种拆分使得React可以逐个处理这些节点,而不是一次性处理整个树。
Fiber节点:每个Fiber节点代表了虚拟DOM中的一个元素,包含当前节点的状态、孩子节点引用、父节点引用等信息。Fiber节点是Fiber算法工作的基本单位。
2. 工作循环(Work Loop)
请求Idle回调:React通过浏览器提供的requestIdleCallback API(或类似的自定义实现)来调度工作循环。当浏览器主线程空闲时,React会开始执行工作循环,执行尽可能多的小任务,直到达到预定的时间切片(time slice)限制或没有更多任务可执行。
递归遍历与工作:在每个时间切片内,React会从根节点开始,递归遍历Fiber树,对每个节点执行“render”阶段(生成新的Fiber节点表示),然后是“commit”阶段(如果有变动,则实际更新DOM)。
3. 优先级调度
任务队列:React维护多个优先级的任务队列,不同类型的更新(如UI更新、数据获取等)会被分配到不同的优先级队列中。
调度策略:在工作循环中,React会优先处理最高优先级的队列中的任务。如果高优先级任务执行完毕,再转向较低优先级的任务。这种机制确保了对用户交互的快速响应。
4. 可中断与恢复
中断点:在每个Fiber节点处理完后,React都会检查是否有更高优先级的任务需要处理,或者是否达到了时间切片的限制。如果是,则中断当前任务,保存当前状态,以便之后恢复。
恢复执行:当高优先级任务处理完毕或浏览器再次空闲时,React会从上次中断的地方恢复执行,继续遍历Fiber树,这种能力使得React可以灵活地管理和调度任务。
总结
Fiber通过将渲染过程细粒度拆分,并引入可中断、可恢复的工作循环机制,实现了对渲染任务的高效调度和优先级管理。这种设计不仅提高了React应用的响应性,也为并发模式、Suspense等高级特性提供了基础,使得React能够更好地适应复杂多变的Web应用需求。
——————————————————————————————
setState方法在React中是异步
为了优化性能,减少不必要的渲染
0. 批量更新: 为了提高性能,React可能会把多个setState调用合并成一次更新,而不是对每个setState立即执行DOM更新。这意味着状态更新并不会立即反映在DOM上,而是等到当前调用堆栈清空后,React会在未来的某个时刻批量执行这些更新。
0. 异步队列: setState调用实际上被放入了一个队列中,React会在合适的时机(比如在事件处理结束后、组件即将渲染之前)处理这个队列,执行状态更新和重新渲染。
React Hooks解决了HOC和Render Props的嵌套问题,更加简洁;
简洁: React Hooks解决了HOC和Render Props的嵌套问题,更加简洁
解耦: React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦
组合: Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千
函数友好: React Hooks为函数组件而生,从而解决了类组件的几大问题:
• this 指向容易错误
• 分割在不同声明周期中的逻辑使得代码难以理解和维护
• 代码复用成本高(高阶组件容易使代码量剧增)
Fiber:一种将 recocilation (递归 diff),拆分成无数个小任务的算法;它随时能够停止,恢复。停止恢复的时机取决于当前的一帧(16ms)内,还有没有足够的时间允许计算
React 在 V16 之前会面临的主要性能问题是:当组件树很庞大时,更新状态可能造成页面卡顿,根本原因在于——更新流程是同步、不可中断的
React 16之前 ,reconcilation 算法实际上是递归,想要中断递归是很困难的,React 16 开始使用了循环来代替之前的递归.
Fiber 架构怎么做的?
• 让 React 渲染的过程可以被中断,可以将控制权交回浏览器,让浏览器及时地相应用户的交互——异步可中断
• 通过将工作任务拆分成一个个工作单元分别来执行
破解 JavaScript 中同步操作时间过长的方法其实很简单——分片
Redux
工作流程
0. Dispatch Action:组件通过调用store.dispatch(action)触发一个Action。
0. Reducer处理:Store接收到Action后,会将Action和当前的State传递给Reducer。
0. 计算新State:Reducer根据Action的类型计算出新的State,并返回。
0. State更新:Store接收到Reducer返回的新State后,更新其内部的State,并触发更新通知到所有订阅的组件
useEffect属于异步执行,并不会等待 DOM 真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;可以获取更新后的 state
组合优于继承”的设计概念
React18中,setState已经默认所有的更新都将自动批量处理
————————————————————————————————
数据=>useContext/createContext redux useState useDeferredValue useRef useReducer
动画=>useTranstion/startTransition useDeferredValue
渲染=>纯函数
性能=>缓存、记忆化hook 或api memo、lazy、useCallback useMemo、cache
其他=>副作用 useEffect
dom=>useImperativeHandle forwardRef useRef createPortal
路由=>useLocation useHistory
——————————————————————————
Hooks
Hooks只能在函数组件的顶层调用,并且每次渲染时调用顺序保持一致
React内部维护了一个 Fiber 节点树,用于跟踪组件的状态和更新。当使用Hooks时,React会为每个组件维护一个“Hook链表”,记录当前组件使用的每一个Hook的当前状态。每次渲染时,React会遍历这个Hook链表,按顺序执行每个Hook对应的逻辑,从而保证了状态的连续性和可预测性。
1.确保Hook调用顺序的确定性:React依赖于Hooks调用顺序来维护每个Hook的状态 State\effect;每个Hook调用都会在Fiber节点中分配一个槽位来存储状态信息。如果Hooks可以在条件或循环中调用,那么React就需要在每次渲染时动态调整这些槽位
“Hooks规则”:只在React函数组件的顶层调用Hooks,并且确保每次渲染时调用顺序保持一致。如果需要根据条件执行某些逻辑或循环渲染组件,应该在Hook内部处理这些逻辑,而不是动态地调用Hook本身。
_______________
• useTransition:用来标记低优先的 state 更新
• useDeferredValue:可以用来标记低优先的变量
• re-render 时的 JS 计算拆分成更小粒度的任务,可以随时暂停、继续和丢弃执行的任务。
• 当 JS 计算的时间达到 16 毫秒之后使其暂停,把主线程让给 UI 绘制,防止出现渲染掉帧的问题。requestIdleCallback
• 在浏览器空闲的时候继续执行之前没执行完的小任务
useState是React的一个 Hook,用于在函数组件中添加状态。它提供的状态仅在组件的生命周期内存在,即从组件挂载到卸载的这段时间
React.memo() 是一个高阶组件 (HOC),它接收一个组件 A 作为参数并返回一个组件 B,如果组件 B 的 props(或其中的值)没有改变,则组件 B 会阻止组件 A 重新渲染 。
useMemo() 是一个 React Hook。
React.memo() 是一个高阶组件,可以使用它来包装不想重新渲染的组件,除非其中的 props 发生变化
useMemo() 是一个 React Hook,可以使用它在组件中包装函数。 可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算
React.lazy 需要配合 Suspense 组件一起使用
const OtherComponent = React.lazy(() => import('./OtherComponent'));
webpack 检测到这种import() 函数语法会自动代码分割。使用这种动态导入语法代替以前的静态引入,可以让组件在渲染的时候,再去加载组件对应的资源
effect 中返回的函数(其清除函数)中,获取到的 state 是更新前的。
每个 effect “属于”一次特定的渲染。
effect 的清除阶段(返回函数)在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。它会在调用一个新的 effect 之前对前一个 effect 进行清理
不能在 useEffect 中使用 useState
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”
18之后,setState都会表现为异步(即批处理)
React合成事件
React通过顶层监听的形式,通过事件委托的方式来统一管理所有的事件,避免浏览器兼容性
事件委托的意思就是可以通过给父元素绑定事件委托
React JSX 转换成真实 DOM 的过程
1. JSX 编译 jsx=>babel=>函数
2. React.createElement => React树(虚拟DOM)
3. Reconciliation(协调/ diff 过程) => 数据改变,生成新树,比较新旧React树
4. Commit 阶段=>更新差异 增量更新 累积变更,批量更新
5. 渲染到页面=>DOM树异步插入
useLayoutEffect dom变更之后,浏览器绘制之前
useEffect 浏览器绘制后
useLayoutEffect总是比useEffect先执行
生命周期
挂载阶段(Mounting)
0. constructor: 组件实例被创建时调用,用于初始化state和绑定this。
0. getDerivedStateFromProps: (不推荐使用)在组件实例被创建和更新时调用,用于根据props计算state。在React 16.3中引入,作为替代componentWillReceiveProps的更安全选项。
0. render: 必须实现的方法,用于返回React元素,描述组件应该在DOM中呈现的样子。
0. componentDidMount: 组件已经被渲染到DOM中后调用,常用于发起网络请求、设置定时器等初始化工作。
更新阶段(Updating)
0. getDerivedStateFromProps: 同挂载阶段,但在组件接收到新的props或state时也会被调用。
0. shouldComponentUpdate: (可选)决定组件是否需要重新渲染。返回true则继续更新流程,返回false则跳过后续的更新过程。
0. render: 更新后再次渲染组件。
0. getSnapshotBeforeUpdate: (不常用)在DOM更新前调用,可以访问到即将被替换掉的DOM快照,用于获取例如滚动位置等信息,返回值会作为componentDidUpdate的第三个参数。
0. componentDidUpdate: 组件更新后立即调用,可以进行DOM操作或网络请求等。应当比较新旧props和state来决定是否执行某些操作。
卸载阶段(Unmounting)
0. componentWillUnmount: 组件即将被卸载和销毁之前调用,用于清理工作,如取消网络请求、清除定时器等
useEffect Hook 可以合并componentDidMount、componentDidUpdate和componentWillUnmount的
FiberReconciler 原来的树形结构转换成 Fiber 链表的形式,整个 Fiber 的遍历是基于循环而非递归,可以随时中断。之前的stack 递归不能中断 渲染过程中进行并发处理 浏览器空闲执行
路由监听
useLocation + useEffect 或者useHistory
——————————————————————————————
8.8
setState 同步执行:
setTimeout 或 setInterval 回调
原生事件处理
异步代码
Hook 解决了函数组件中无法使用状态和副作用的问题
在React中如何避免不必要的render
shouldComponentUpdate
PureComponent
减少组件的state和props
对于大型对象或数组,尽量使用不可变数据结构,或者提 供一个新的对象或数组,而不是直接修改原对象或数组。这样可以避免不必要的渲染
使用列表的键(key)
使用React.lazy和Suspense懒加载组件
拆分组件
使用context和hooks管理状态
避免使用内联函数=>匿名和箭头函数以及内联对象
使用React.Fragment避免添加额外的DOM
Immutable原理
不可变
持久化数据结构
结构性共享
useLayoutEffect 则在 DOM 更新之后立即同步执行,适用于对 DOM 布局和样式有影响的操作
Hooks 在单链表中是没有名字和 key 的,它们完全依赖于声明的顺序
FiberNode对象,fiber.memoizedState存储的就是Hooks单链表
顺序依赖: React 依赖于 Hooks 被调用的顺序来正确地映射和更新状态
如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。React 通过合成事件实现了事件委托机制, 对于合成事件来说,有一个事件池专门来管理它们的创建和销毁
react合成事件特点
按需绑定 绑定在document上统一 真实dom上的click事件被react底层替换成空函数
react事件原理:
第一步,会将你写的click事件,绑定到button对应的fiber树
第二步,进入diff阶段,会先判断该事件是不是合成事件,如果是合成事件,则会将用户写的事件向document注册
第三步,在注册事件监听器函数中,先找到 React 合成事件对应的原生事件集合,比如 onClick -> ['click'] , onChange -> [blur , change , input , keydown , keyup],然后遍历依赖项的数组,绑定事件。
第四步,统一对所有事件进行处理,添加事件监听器addEventListener,绑定对应事件
包裹原生事件 事件委托 事件池
————————————————————————
8.9
在线绘制clip-path
https://bennettfeely.com/clippy/
react事件
dispatchEvent 文档根层监听捕获事件
batchUpdate 合并更新减少重复渲染
extractEvents 原生事件转合成事件
生成事件队列 从源头向上遍历dom树,节点事件入队(模拟事件冒泡过程)
runEventsInBatch 执行事件队列,执行后的每个事件,合成事件对象放入事件池,便下次用
react 17对事件系统的改变
事件绑定到根容器上: 事件统一绑定container上,ReactDOM.render(app, container);而不是document上,有利于多个 React 版本共存,例如微前端的场景。
2.原生捕获事件的支持
3.取消事件池
链表的缺点:
• 比顺序结构数据更占用空间,因为每个节点对象还保存有指向下一个对象的指针。
• 不能自由读取,必须找到他的上一个节点
Fiber
数据结构
优先级调度
空间换时间
Fiber 架构中的 Scheduler(调度器)
Scheduler 根据浏览器剩余空闲时间、优先级等因素派发给 Reconciler(协调器)
Scheduler 调度实现,主要做以下事情:
0. 维护一个任务池
0. 定义应用优先级决定任务池的调用顺序
0. 派发任务(调 requestHostCallback) : 注册一个在帧间空闲时间执行的回调函数
0. 及时中断(在 shouldYieldToHost 时终止派发):随时判断是否需要让出线程(避免卡帧)
• 当前帧的结束时间戳,如果当前时间超过帧结束时间,说明已经卡到帧了
React实现时间切片的发展历史
requestIdleCallback=>requestAnimationFrame + MessageChannel=>高频短间隔调度
宏任务机制,以高频(5ms间隔setTimeout(()=>{},5))对任务进行切片执行
React 18都新增了哪些新特性和新API
1.ReactDOM.createRoot:启用 React 18 中的并发功能(concurrency并发渲染
2.setState 自动批处理
3.去掉了对IE浏览器的支持
4.React 18 中,不再检查因返回 undefined 而导致崩溃。既能返回 null,也能返回 undefined
Suspense useDeferredValue 数据 useTransition 函数