1、useMemo
useMemo是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
- 能跳过代价昂贵的重新计算
- 跳过组件的重新渲染
- 记忆另一个hook的依赖
- 记忆一个函数
useMemo是通过Object.is来判断依赖项是否改变
2、useCallback
useCallback是一个允许你在多次渲染中缓存函数的React Hook
- 跳过组件的重新渲染
- 从记忆化回调中更新state
- 防止频繁触发Effect
- 优化定义Hook
- useCallback其实是useMemo的语法糖
3、react16.8之后做了哪些优化
Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染, 解决react15及以前更新不可中断的问题
-
React Hooks
1、引入了Hooks, 函数组件正式使用
-
生命周期
1、类组件使用的生命周期使用useEffect的hook代替
-
代码复用
1、类组件代码复用采用的HOC高阶组件, 可能会导致“嵌套地狱”
2、Hooks, 通过自定义Hooks来抽离和共享逻辑, 减少组件嵌套
-
占用内存
1、类组件因为要创建实例所以占用内存更大
-
性能
1、useMemo、useCallback可以对计算结果和函数进行缓存来提升性能
4、fiber为什么React性能的一个飞跃
fiber算一种协程, 他将长时间的任务拆分为小任务将阻塞的单线程拆分为可调度优先级的任务
fiber采用的是链表结构的形式实现递归相关操作, 以前用的是栈递归, 栈递归需要函数依次出栈, 会阻塞进程, 所以会出现卡顿问题, 链表结构存储的是下一个节点的指针, 更方便优先级高的任务进入, 修改插入删除相关操作速度会很快, 但是它存储了本节点的对象和下个节点的指针, 所以内存占据大, 是一种以空间换时间的方式来优化性能
5、react为啥不直接用requestIdleCallback?
- 一致性问题: requestIdleCallback的执行时机不可控,可能会导致不同环境下表现不一致
- 实时性问题: react需要实时性,requestIdleCallback执行的时机不一定满足实时性的需求
- 调度器控制: requestIdleCallback可能会破坏react内部的调度
- 兼容性: requestIdleCallback在浏览器不同内核下有兼容性
6、react闭包陷阱的理解,怎么解决?
类似上边的代码, 无论几次调用setCount, 内部打印的都是1, 原因是产生了闭包, 闭包就是在函数调用的时候就像拍了一个快照保存下来了
解决方案:
- useRef
- 设置对象
setObj((prevState) => { var nowObj = Object.assign(prevState, { name: "baobao", age: 24, }); return nowObj; });
7、React.memo() 和 useMemo() 的用法和区别
-
React.memo()是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的 props 发生变化 -
useMemo()是一个 React Hook,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算
8、react router
单页面应用路由实现原理是,切换url,监听url变化,从而渲染不同的页面组件。
react router有两种模式, 一种hash, 一种history
hash模式是监听hashChange
history是监听pushState、replactState
9、react中页面如何实现缓存
尝试过程
-
重写组件,可参考 react-live-route。重写可以实现我们想要的功能,但成本也比较高,需要注意对原始 功能的保存,以及多个 react-router 版本的兼容
-
重写路由库,可参考 react-keeper 。重写路由库成本是一般开发者无法承受的,且完全替换掉路由方案是一个风险较大的事情,需要较为慎重地考虑。
-
基于组件现有行为做拓展,可参考 react-router-cache-route 。在阅读了 的源码后发现,如果使用 component 或者 render 属性,都无法避免路由在不匹配时被卸载掉的命运。但将 children 属性当作方法来使用,我们就有手动控制渲染的行为的可能。
-
react-activation 的实现原理:由于 React 会卸载掉处于固有组件层级内的组件,所以我们需要将其中的组件,也就是其 children 属性抽取出来,渲染到一个不会被卸载的组件内,再使用 DOM 操作将不修改的真实内容移入对应组件中 ,就可以实现此功能
-
使用
react-activation的KeepAlive适合需要保持组件状态和 DOM 的场景。
10、react fiber
出现原因: js引擎线程GUI渲染线程是互斥的, 在react15中是stack架构模式, 是同步递归模式使用的js引擎自身的函数调用栈, 必须要执行到栈空才行,没有优先级, 所以阻塞渲染线程, 页面卡顿
实现: fiber是react内部定义的一种数据结构, 是fiber树的节点单元
React Fiber 的调度流程如下:
- 任务分割:将渲染任务分割成多个小的工作单元(fiber)。
- 任务调度:根据任务的优先级,将任务放入调度队列。
- 空闲时间执行:使用
requestIdleCallback在浏览器空闲时间执行低优先级任务。 - 高优先级调度:使用
MessageChannel调度高优先级任务。 - 渲染阶段:使用
requestAnimationFrame在每帧之前执行渲染任务。 - 中断与恢复:高优先级任务可以中断低优先级任务,并在空闲时间恢复执行。
11、react懒加载的实现原理
react的懒加载是通过React.lazy()和Suspense相结合实现的, lazy创建的动态组件具有Pending、Resolved、Rejected三种状态,Suspense通过捕获三种状态来展示组件
webpack的动态加载: webpack通过创建script标签来实现动态加载, 找出依赖对应的chunk信息, 然后生成script标签来动态加载chunk,每个chunk对应的状态: 未加载、加载中、已加载
12、react的render阶段
render阶段的主要工作是构建fiber树和生成effectList,effectList上的Fiber节点保存着对应的props变化
13、react的commit阶段
遍历effectList进行对应的dom操作和生命周期、hooks回调或销毁函数
14、react的性能优化方式
-
减少渲染节点、降低组件渲染复杂度
- 不要在渲染函数中进行不必要的计算: 不要在render函数中进行数组排序,数据转换、创建事件处理器等, 不应该放置太多的副作用
- 使用虚拟列表和惰性渲染
-
避免重新渲染
- 简化props
- 不变的事件处理器, 事件处理尽量避免箭头函数, 因为如果使用箭头函数每次都会创建新的事件处理器, 会导致重新渲染的, 同时事件传输中使用useCallback来优化事件
-
精准重新计算范围
- 不要滥用useContext, useContext是可以穿透React.memo或者shouldComponentUpdate的比对的, 所以如果context的value修改后, 所有依赖该context的组件都会被重新forceUpdate.
- 拆分组件尽量颗粒度小, 让一个组件用更少的state影响, 这样可以使用useMemo等控制
15、自定义hook
自定义hook实际上就是抽离重复逻辑代码放在一个函数里, 通过闭包的方式return出来, 这样做就是为了代码简洁, 在抽离hooks, 更多的内部是一个函数的实现, 可能是异步可能是同步, 对内部进行逻辑处理, 就要更多的考虑性能渲染问题例如useMemo、useCallback的使用
16、setAge(age + 1)和setAge(a => a + 1)的区别
-
setAge(age + 1): 直接传递新值
- 直接传入一个新的状态值, 然后更新组件的状态, 适用于确保获取的
age值是最新的情况 - 不涉及到异步, 如果在短时间内不会有多次状态更新,这种方式是安全的
- 如果在短时间内多次调用
setAge,并且这些调用之间有异步操作(如事件处理函数),你可能会遇到状态不一致的问题。 例如,假设age当前是5,调用setAge(age + 1)两次,但第一次更新后的状态还未被应用,第二次调用依然会基于旧的age值,从而导致最终结果不是预期的7而是6。
- 直接传入一个新的状态值, 然后更新组件的状态, 适用于确保获取的
-
setAge(a => a + 1): 使用函数更新方式
setAge接收一个函数,该函数会被 React 传入当前的状态值,并返回新的状态值,确保了每次状态更新都基于最新的状态值。- 适用于需要基于当前状态值进行计算的场景,适用于在同一渲染周期内有多次状态更新的场景,确保每次更新都能获取到最新的状态值