React常见面试题:https://segmentfault.com/a/1190000008102870
文档阅读说明
以 ==优化-== 开头的是阅读过程中关于react的优化操作
React的合成事件
react并不会吧事件真正绑定在真实的节点上,而是把所有事件绑定在了结构的最外层使用一个统一的事件监听器进行监听事件和处理函数,而且在浏览器兼容上实现了统一的接口
React核心–reconciler 调和
包含自定义组件的实现、组件生命周期机制、setState机制、dff算法等
VDOM为什么提高了性能
web开发中对dom的频繁操作通常是产生性能瓶颈的原因,而vdom是dom结构的数据结构,利用vdom能够通过diff算法比较出需要更新的节点,尽可能的减少dom的无必要更新,而不是渲染整个树。
另外vdom的批处理更新,多次的vdom更新将会合并(多次的this.setState)添加到队列中,真正的更新是在事件循环结束后根据dom的最终状态操作真实的dom更新,
React 生命周期
画个图
需要注意的是:componentWillMount/willrecevprops里setState是不会出发re-render,而是合并到更新队列中
一般我们将网络请求放到componentDidMount中,保证视图已经渲染完毕,
当然服务端渲染请放到willmount中做同构,不操作dom可以放在willmount中
在react中将会一直递归渲染直到子组件全部渲染完毕
严禁在shouldcomponentupdate和componentWillUpdae中调用setstate会造成无线递归调用而导致浏览器崩溃
this.setState
Reac通过this.setState来更新state当this.setState被调用的时候,React会重新调用render来重新渲染UI
基于事务机制,react将一次事件循环内的setState通过队列实现更新,多次的setState会合并到一个队列中,不会立刻更新this.state,和现代浏览器的多次dom更新会尝试合并是一个道理,为了性能
//react 15.x的setstate源码
//将新的state合并到状态更新队列中
var nexState = this._processPendingState(nextProps,nextContext)
同步更新策略
采用传入函数的方式来更新
increment() { // 采用传入函数的方式来更新
this.setState((prevState, props) => ({ count: prevState.count + 1 }));
this.setState((prevState, props) => ({ count: prevState.count + 1 }));
}
setTimeout
setTimeout(()=>{
this.setState({
count:this.state.count+1
})
this.setState({
count:this.state.count+1
})
},0)
es6异步
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
async componentDidMount() {
StatusBar.setNetworkActivityIndicatorVisible(true)
const res = await fetch('https://api.ipify.org?format=json')
const {ip} = await res.json()
await this.setStateAsync({ipAddress: ip})
//await之后已经是下一个事件循环了 相当于promise.then
StatusBar.setNetworkActivityIndicatorVisible(false)
}
回掉函数
this.setState({
load: !this.state.load,
count: this.state.count + 1
}, () => {
console.log(this.state.count);
console.log('加载完成')
});
DIFF算法
diff 与 virtual dom结合 让react尽可能的针对变化的dom节点进行更新操作而不是重新渲染整个页面
传统的diff
传统的diff通过循环递归对节点依次对比效率是十分低的 O(n^3),一颗1000节点的书需要十亿次对比!
React的diff
react通过大胆的策略将时间复杂度降到了O(n)
1. DOM节点的跨层级移动是很少的,忽略不计
2. 拥有相同类的两个组件树形结构相似,拥有不同类的组件树形结构是不同的
3. 对于同一层的子节点,可以通过id来区分
tree diff
react只会对两个树的相同层的节点比较。如果同一个父节点,发现新树下子节点不存在了,那么不存在的这个子节点和他的所有子节点都被删除
对于跨层级移动,react不建议这么做,因为会将跨层级移动的那个节点的所有节点重新创建到要移动的位置上,然后删除原来的所有节点,这是一个耗时的过程
==优化-建议保持稳定的dom结构,通过css的隐藏或显示节点,而不是真正的删除==
component diff
==优化-建议通过shouldComponentUpdate()来手动告诉组件是否更新==
element diff
当处于同一层时候 diff提供三种操作
1. insert_markup 新的组件类型不在旧的集合中,执行插入新结点
2. move_existing 旧的集合有新组件类型 做移动操作,复用以前的dom节点
3. remove_node 旧组件类型在新集合中也有,但element不同需要删除,或者 旧组件不存在于新集合也要删除
针对上图的性能问题React提出了优化的策略,那就是给每一个节点添加一个唯一key,当react发现新旧集合拥有的节点的key都是相同的时候,B D不做任何操作,A C进行移动g
同层节点diff比较算法 待完善
lastIndex = Max{pre._mountIndex ,lastIndex};
有新的节点直接插入,更新lastindex为新集合中该节点的位置
if(X._mountIndex<lastIndex)
//移动
else
//不变
这样的diff算法是有缺陷的,abcd=>dabc 操作是将abc全部移动到d的后面
==优化-开发过程中请避免将最后的节点移动到列表首部的操作==
React Patch 方法
所谓patch就是将上面的tree diff计算出来的dom差异队列更新到了真实的dom节点上,最终让浏览器能够渲染出更新的数据。
patch主要通过遍历差异队列实现的,根据差异队列进行依次操作