# React16
# 架构
React16架构分为三层:
- Scheduler(调度器)- 调度任务的优先级,高优先级优先进入
Reconciler
- Reconciler(协调器)- 负责找出变化的组件
- Renderer(渲染器)- 负责将变化的组件渲染到页面上
TIP
相比较于15,增加了Scheduler调度任务
# Scheduler(调度器)
如果需要实现异步,那么就需要知道浏览器是否有剩余时间,在浏览器中有原生的APIrequestIdleCallback
,但是该api存在以下问题
- 兼容性
- 触发频率不稳定,影响因素很多。比如当前tab切换后,之前tab注册的时间触发的频率会变得很低。
所以React实现了requestIdleCallback
pollyfill。除了在空闲时触发回调的功能外,还提供了多种调度优先级供任务设置。
# Reconciler(协调器)
16+版本中,Reconciler
更新工作从递归变成了可中断的循环过程。每次循环都会调用shouldYield
获取当前是否有剩余时间。并且Reconciler
和Renderer
不再是交替工作。当Schuduler
把任务交给Reconciler
之后,Reconciler
会为变化的虚拟DOM打上标记(增/删/更新)
整个过程都在内存中进行。只有当所有组件都完成Reconciler
的工作,再会统一交给Renderer
进行渲染工作
Reconciler
和 Schuduler
被称为 render
阶段,具体流程如下:
beginwork
递- 从
rootFiber
开始child
,如果遇到函数组件或者类组件则调用渲染函数生成新的child
(Fiber Array) reconcilerChildren
,对已存在的child与生成的child进行diff,如果是单节点,直接进入更新单节点,如果是数组,则会进入diff逻辑,尽可能复用已有Fiber,创建的effectTag包含更新、删除并且更新父Fiber
的tag,如果是新建的话就不需要打tagdiff算法
:oldChild
与新渲染产生的child循序遍历,比较tag与key,如果相同则复用,判断props是否需要更新,需要更新则标记update
并更新父节点effectTag
,更新lastPlaceIndex
- 一旦遇到不能复用,则把
oldChild
创建map,遍历新节点,在map中查找可复用节点,如果没找到则新建Fiber
- 遍历map,打上
delete
标记
- 从
completework
归- 处理flag:把
Fiber
自身的flag
以及subtreeFlag
冒泡到父Fiber
- 处理
Fiber
的stateNode
,如果没有则需要创建,并且把子孙节点append到当前DOM节点下,存在的话就更新props、事件绑定等 - 把每个
Fiber
的Effect
添加到父Fiber
上组成effectList
,在commit阶段会遍历单向链执行对应操作
- 处理flag:把
# Renderer(渲染器)
Renderer
根据Reconciler
的标记,同步执行对应的DOM操作
此阶段也称为commit 阶段
,分为三个步骤:
# before mutation
遍历 effectList
处理 DOM
节点 渲染/删除
后的 聚焦/失焦
逻辑,调用 getSnapShotBeforeUpdate
生命周期钩子,调度 useEffect
(使用SchedulerCallback调度)
# mutation
- 更新文本节点的content
- 更新ref
- 根据
effectTag
进行对应处理,包含操作:移动、插入、更新、删除等组合 - 更新
DOM
props、properties(遍历pendingProps,数据在completeWork阶段完成处理) - 如果是
FC
则需要调用useLayout
的销毁函数 - 完成后再进入
layout
阶段前需要把current指针切换,因为mutation
阶段会调用componentWillUnmount
,此时的DOM
还是更新前的
# layout
到达这个阶段,已经完成 离屏DOM
的布局,只是浏览器尚未完成渲染
- 遍历
effectList
执行commitLayoutEffcts
函数:赋值ref
、调用生命周期钩子和hooks - 执行
useLayout
回调函数,调度useEffect
的销毁函数以及回调函数 - 类组件
this.setState
的第二个回调函数也会执行 - 执行的生命周期钩子:
componentDidMount
,componentDidUpdate
# 生命周期(16.4+)
# 创建阶段
- constructor
实例过程中自动调用的方法,在方法内部通过
super
关键字获取来自父组件的props
- getDerivedStateFromProps
新增的生命周期,是一个静态的方法,因此不能访问到组件的实例
执行时机:组件创建和更新阶段,不论是props
还是state
变化,都会调用
参数:(props,state),即将更新的props
,上一个状态的state
- render
类组件用于渲染
DOM
结构的方法,可以访问组件state
和props
属性 - componentDidMount
组件挂载到真实
DOM
后执行
# 更新阶段
- getDerivedStateFromProps 同上
- shouldComponentUpdate
基于
props
和state
是否需要重新渲染组件,默认返回true
- render 同上
- getSnapshotBeforeUpdate
在
render
后执行,执行时DOM
还没被更新
参数props
和state
,返回的参数作为componentDidUpdate
的第三参数,目的在于获取组件更新前的一些信息,如滚动位置之类,在组件更新后可以恢复一些UI上的状态 - componentDidUpdate
在组件更新结束后执行,可以根据前后的
props
和state
的变化做相应的操作,如获取数据,修改DOM
样式等
# 卸载阶段
- componentWillUnmount
用于组件卸载前,清理一些注册监听事件,或者取消订阅的网络请求等。
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
# 与16.4-对比
减少了以下钩子,在现实使用中还能使用,只是增加了UNDAFE_
前缀,原因是fiber架构,会导致render阶段会被多次执行,一下钩子可能会被多次执行
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
# 错误边界
为了解决出现的错误导致整个应用崩溃的问题,16
引入了错误边界
的新概念。错误边界是一种React
组件,这种组件可以捕获发生在其子组件树任何位置的js
错误,并打印这些错误,同时展示降级UI
,而不会渲染那些发生崩溃的子组件树。
形成错误边界的两个条件,
- 使用了static getDerivedStataFromError,在错误抛出后,渲染备用
UI
- 使用了componentDidCatch打印错误信息
class ErrorBoundary extends React.Component{
state = {
hasError:false
}
constructor(){
super(props)
}
static getDerivedStateFromError(error){
return {
hasError:true
}
}
componentDidCatch(error,errorInfo){
// 可以将错误日志上报服务器
}
render(){
return this.state.hasError ? <h1>somthing wrong</h1> : this.props.children
}
}
如果不是渲染阶段产生的错误,并不会被错误边界捕获。需要在组件内监听,或者在全局监听。
- 使用trycatch语法捕获在组件内操作
UI
降级 - 在全局监听error事件,window.addEventListener('error',function(e){})
# 性能优化
- 避免使用内联函数
- 使用 React Fragments避免额外标记
- 使用Immutable
- 懒加载组件
- 事件绑定方式
- 服务端渲染
- memo useCallback ...
# redux
redux
是用于数据状态管理,而 React
是一个UI
渲染库,如果将两者连接在一起,需要搭配使用react-redux
或者类似的库,通过redux
将整个应用状态存储到store
中,组件可以派发dispatch
行为action
给store
,其他组件通过订阅store
中的状态state
来更新自身的视图
react-redux
将组件分成:
- 容器组件:存在逻辑处理
- UI组件:只负责显示和交互,内部不处理逻辑,状态由外部控制
react-redux
分成两大核心:
- provider
- connection
# provider
使用provider
把store内的state
提供给子组件使用
# connection
connect
方法将store
上的getState
和dispatch
包装成props
# v18新特性
# 新增hook
- useId 解决了客户端和服务端id不同的问题 原理:利用组件在组件数中的层级生成id 用法:
fc(){
const id = useId()
return (
<input type="checkbox" name="react" id={id} />
)
}
- useTransition
在
React
中状态更新分为紧急更新和过渡更新:
紧急更新:输出、点击、拖拽需要立即响应
过渡更新:将UI从一个视图过渡到另一个视图
使用useTransition
可以将一个任务变为非紧急状态 用法:
fc(){
const [isPending,startTransition] = useTransition()
return (
<>
<input type="text" value={value} onChange={(e)=>{
setValue(e.target.value)// 紧急任务
startTransition(()=>{
setSearch(e.target.value)// 非紧急任务
})
}} />
{isPending && 'loading……'}{/* 非紧急任务的状态 */}
</>
)
}
- useDeferredValue 将任务变成非紧急任务
与useTrransition区别
useDeferredValue把任务变成了延迟更新任务
,useTransition则是把一个状态变成了延迟状态
用法:
fc(){
const [state,setState] = useState('')
const deferedValue = useDeferrefValue(state)
return (
<>
<input type="text" value={value} onChange={(e)=>{
setState(e.target.value)// 紧急任务
}} />
<List search={deferredValue} />{/* 标记非紧急状态 */}
</>
)
}