React中Key的作用
- key的作用主要是用来减少没必要的diff算法对比。
- key是一个组件、节点的身份标识,在rerender时,可以通过key来判断该组件是否已经存在,是否需要跟新或者销毁,新建等操作,提高了diff算法在同级节点上的操作。
setState是同步还是异步更新
- 同步代码下异步执行,异步代码下同步执行。可以看这里
触发多次setstate,那么render会执行几次
- 看上道题中的链接,就知道答案了。
setState之后干了什么
- setState 执行 enqueueUpdate,并且执行React自己的更新策略 ReactDefaultBatchingStrategy
- isBatchingUpdates 是核定变量,默认是false。
- isBatchingUpdates 如果为true,则开启批量更新模式,当前组件会被push到dirtyComponent中,并且在事务结束后会将其置为false
- isBatchingUpdates 如果 是false,则执行batchedUpdates方法,即时的更新state。调用batchedUpdates方法,并且激活更新策略。去调用transaction.perform 执行更新的事务。wrapper执行组件的更新。
更新策略
- React的更新策略已经被启动时(事件触发时):React响应事件处理 => 启动更新策略事务(绑定了wrapper) => 事务perform =>setState => 获取内部实例 => 存储新的状态 => 发现更新策略事务已启动 => 将当前内部实例放入脏组件数组 => setState执行结束 => 更新策略事务perform完毕 =>wrapper处理组件状态的更新
- React的更新策略没有被启动时(异步触发时):setState => 获取内部实例 => 存储新的状态 => 发现更新策略事务未启动 => 启动更新策略事务(绑定了wrapper) => 事务perform => 将当前内部实例放入脏组件数组 => 更新策略事务perform完毕 =>wrapper处理组件状态的更新=> setState执行结束
更新事务中有两个wrapper 【FLUSH_BATCHED_UPDATES】、【RESET_BATCHED_UPDATES】
- RESET_BATCHED_UPDATES close 中 会将isBatchingUpdates = false;
- FLUSH_BATCHED_UPDATES 负责执行循环所有的dirtyComponent。
react diff算法介绍一下
diff算法用于计算出两个virtual dom的差异,是react中开销最大的地方。
传统diff算法通过循环递归对比差异,算法复杂度为O(n3)。
react diff算法制定了三条策略,将算法复杂度从 O(n3)降低到O(n)。
- WebUI中DOM节点跨节点的操作特别少,可以忽略不计。
- 拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。
- 同一层级的子节点,可以根据唯一的ID来区分。
针对这三个策略,react diff实施的具体策略是:
- diff对树进行分层比较,只对比两棵树同级别的节点。跨层级移动节点,将会导致节点删除,重新插入,无法复用。
- diff对组件进行类比较,类相同的递归diff子节点,不同的直接销毁重建。diff对同一层级的子节点进行处理时,会根据key进行简要的复用。两棵树中存在相同key的节点时,只会移动节点。
tree diff
React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较
当发现节点已经不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较
当出现节点跨层级移动,直接创建新的删除旧的,而不是移动。
component diff
- 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那么就可以节省大量的 diff 运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需进行diff算法分析,但是如果调用了forceUpdate方法,shouldComponentUpdate则失效。
element diff
当节点处于同一层级时,diff 提供了 3 种节点操作,分别为 INSERT_MARKUP (插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)。
- INSERT_MARKUP :新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作。
- MOVE_EXISTING :旧集合中有新组件类型,且 element 是可更新的类型,generateComponentChildren 已调用
receiveComponent ,这种情况下 prevChild=nextChild ,就需要做移动操作,可以复用以前的 DOM 节点。 - REMOVE_NODE :旧组件类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者
旧组件不在新集合里的,也需要执行删除操作。
性能提升方面有什么可做的
前端项目2大基本点优化:1、尽可能的减少请求连接数。2、尽可能的减少重绘、重拍版。react优化主要做第2条
- 使用纯组件
- setState方面 合并执行
- scu render方面,context的影响
- 懒加载组件
- 列表的优化
- 使用 React Fragments 避免额外标记
- 不要使用内联函数定义
- 避免 componentWillMount() 中的异步请求
- 在 Constructor 的早期绑定函数
- 箭头函数与构造函数中的绑定
当我们添加箭头函数时,该函数被添加为对象实例,而不是类的原型属性。这意味着如果我们多次复用组件,那么在组件外创建的每个对象中都会有这些函数的多个实例。影响了可复用性.
- 避免使用内联样式属性
- 优化 React 中的条件渲染
- 不要在 render 方法中导出数据
- 为组件创建错误边界
static getDerivedStateFromError() 和 componentDidCatch()。
- 组件的不可变数据结构
- 使用唯一key迭代
- 事件节流和防抖
- 使用 CDN
- 用 CSS 动画代替 JavaScript 动画
- 在 Web 服务器上启用 gzip 压缩
- 使用 Web Workers 处理 CPU 密集任务
- React 组件的服务端渲染
react状态管理
- 组件内部使用state来做状态管理,需要尽可能的减少state的复杂度。并且在异步代码中要合并之后再执行setState
- 父子组件之间使用props传递数据。此类场景的话,子组件最好搞成受控组件
- 跨层级组件中使用context来传递数据。
context有一些问题需要注意,目前React中存在两套context 实现方案,一种是老式的 getChildContext方式,一种是新的Provider和 Customer 模式。 老版数据传递依赖于props的传递路线,如果中层组件使用scu 并且返回了false,会导致context无法传递到目标组件中。新版context 不存在这个问题。因为Context作为全局对象,直接注入到目标组件中。
React生命周期的变化
- -
- 还有两个处理错误的生命周期方法 static getDerivedStateFromError() componentDidCatch()
-
取消 UNSAFE_componentWillMount
数据初始化(应该放在constructor)
fetch数据
并没有可以获得优先渲染优势,因为reconciliation和commit是同步操作
服务端渲染会有问题,异步模式下会触发多次fetch数据
应该放在companyDidMount生命周期内
订阅事件造成内存泄漏
服务端渲染componentWillUnmount不会调用,异步会被打断,导致componentWillUnmount不会调用 -
- 取消UNSAFE_componentWillReceiveProps(preProps, preState) ,UNSAFE_componentWillUpdate(nextProps, nextState)原因:
这两个生命周期都在reconciliation过程中
在异步模式下,reconciliation会被打断,所以可能触发多次
如果在这两个生命周期做一些fetch请求、props的回调等产生副作用的事情,那么可能会导致执行多次
- 取消UNSAFE_componentWillReceiveProps(preProps, preState) ,UNSAFE_componentWillUpdate(nextProps, nextState)原因:
-
增加static getDerivedStateFromProps(props, state) 的原因是
组件有受控状态和非受控状态,Derive state生命周期通常指受控状态(即state由props决定),但是如果组件又可以自行设置state,那么state的来源就有props和组件自身行为,这违反了数据单一来源的原则
react 希望你将组件改成受控组件;
用key的办法来从新reset组件;假如不想重新渲染组件,也可以使用的key作为一个props的属性来使用Derive state生命周期进行reset过程
HOC 方面
在hook没有出现之前,hoc是一个比较好的手段
- 属性代理
书写方式
Wrap 被包裹的组件
function Hoc(Wrap){
return class Box extends Component{
render(){
return <Wrap {...this.props}/>
}
}
}
使用的方式
1、使用装饰器 @Hoc 使用此方式居多
2、
class Detail extends Component{
....
}
export default Hoc(Detail)
- 控制props
在 hoc 中 this.props可以访问到props,可以修改或者添加props - 可以使用refs 访问
具体实现就是:
在HOC 方法中
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
getRef(wrappedComponentInstance) {
// 可以得到 组件的this,所以可以调用组件的 方法、prop、state
wrappedComponentInstance.handleClick();
// 可以改写 组件的 state
wrappedComponentInstance.setState({
stateA:'stateA'
list:[{a:1}]
})
console.log(wrappedComponentInstance)
}
render() {
const props = Object.assign({}, this.props, {ref: this.getRef.bind(this)})
return <WrappedComponent {...props}/>
}
}
}
- 抽象state
其实就是控制state,hoc 以props 的形式传一个回调进去 来改变 props
- 使用其他元素包裹wrappedComponent
这个就很好解释了,一般是为了布局或者 样式的处理 -
反向继承
书写方式
function MyContainer(WrappedComponent) {
return class MyComponent extends WrappedComponent {
render() {
return super.render()
}
}
}
在反向继承中高阶组价可以通过this来访问到wrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。但是它不能保证完整的子组件树被解析
- 渲染劫持
在render之前可以控制render 的渲染,条件渲染等。
export function MyContainer(WrappedComponent) {
return class MyComponent extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
)
}
}
}
- 操作state
因为可以this访问到wrap ,所以可以操作stat。
文章评论