多组件更新
多组件是我们常用的场景,若我们更新其中的某组件,react如何实现高效更新呢?
class Child1 extends Component{
state={
count: 1
}
onClickFn = ()=>{
this.setState({ count: this.state.count + 1 })
}
componentDidUpdate(){
console.log('didUpdate Child1');
}
render(){
return <div onClick={ this.onClickFn }>child1{ this.state.count }</div>
}
}
class Child2 extends Component{
state={
count: 1
}
componentDidUpdate(){
console.log('didUpdate Child2');
}
onClickFn = ()=>{
this.setState({ count: this.state.count + 1 })
}
render(){
return <div onClick={ this.onClickFn } >child2{ this.state.count }</div>
}
}
class App extends Component {
componentDidUpdate(){
console.log('didUpdate');
}
render() {
return (<div id="appId">
Hello World!
<Child1></Child1>
<Child2></Child2>
</div>)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
点击组件的click事件,触发组件的setState,调用enqueueSetState。
enqueueSetState: function enqueueSetState(inst, payload, callback) {
var fiber = get(inst);
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
flushPassiveEffects();
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);//
}
在 scheduleWork 中 scheduleWorkToRoot 为当前fiber添加expirationTime,同时为fiber 父级的childExpirationTime 添加 expirationTime,找到并返回ReactRoot(fiberRoot)。
function scheduleWork(fiber, expirationTime) {
var root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) { return; }
//...
markPendingPriorityLevel(root, expirationTime);// 为 root 添加 expirationTime 等
if (!isWorking || isCommitting$1 ||nextRoot !== root) {
var rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
}
}
在 markPendingPriorityLevel 为当前root添加 expirationTime 等值,其它的和事件派发类似,执行完事件方法接着执行 performSyncWork 等方法。更新的高效性和 expirationTime 有无有关。
workLoop
最初的nextUnitOfWork 为 createWorkInProgress 创建的ReactRoot(fiberRoot)。更新的高效处理在 beginWork 中的 bailoutOnAlreadyFinishedWork中。
beginWork
在 执行到更新的 fiber 之前,workInProgress.expirationTime = 0。
function beginWork(current$$1, workInProgress, renderExpirationTime) {
var updateExpirationTime = workInProgress.expirationTime;
if (current$$1 !== null) {
var oldProps = current$$1.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps === newProps && !hasContextChanged() &&
updateExpirationTime < renderExpirationTime) {
//....
return bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime);
}
}
workInProgress.expirationTime = NoWork; // NoWork = 0;
//..创建与更新
}
在scheduleWork 下 scheduleWorkToRoot 中为 fiber的父级添加 childExpirationTime。这就让bailoutOnAlreadyFinishedWork 中 childExpirationTime < renderExpirationTime 为 false,进而执行cloneChildFibers。
function bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime) {
cancelWorkTimer(workInProgress);
if (current$$1 !== null) {
workInProgress.firstContextDependency = current$$1.firstContextDependency;
}
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
var childExpirationTime = workInProgress.childExpirationTime;// expirationTime
if (childExpirationTime < renderExpirationTime) {
return null;
} else {
cloneChildFibers(current$$1, workInProgress);
return workInProgress.child;
}
}
这样就能让更新的fiber的 上级 都进行cloneChildFibers,进而检查组件的更新。
cloneChildFibers
function cloneChildFibers(current$$1, workInProgress) {
!(current$$1 === null || workInProgress.child === current$$1.child) ?
invariant(false, 'Resuming work not yet implemented.') : void 0;
if (workInProgress.child === null) { return; }
var currentChild = workInProgress.child;
// 克隆fiber,currentChild.pendingProps为当前fiber上的属性
var newChild = createWorkInProgress(currentChild,
currentChild.pendingProps,
currentChild.expirationTime);
workInProgress.child = newChild;
newChild.return = workInProgress;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(currentChild,
currentChild.pendingProps,
currentChild.expirationTime);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
cloneChildFibers 直接使用 前一次渲染的结果,此处使用 currentChild.pendingProps ,这让 beginWork 中 oldProps === newProps 为true。
复用
随着workLoop的执行,会逐渐检查所有子fiber,beginWork 中 oldProps === newProps && !hasContextChanged() && updateExpirationTime < renderExpirationTime 条件会过滤是否更新。符合条件的则会调用 bailoutOnAlreadyFinishedWork检查 childExpirationTime 是否有值,若childExpirationTime 有值则克隆并返回 child ;若childExpirationTime 无值则返回null ,进而忽略此fiber。这样就只更新调用 setState 的fiber。若无更新在completeUnitOfWork也会复用实例。
调用setState的fiber,我们用newFiber表示。通过 过滤 及复用fiber 属性,这样可以 高效的找到newFiber,然后再得到 此fiber下 要更新的元素。在workLoop中,循环 newFiber后,还要循环newFiber中的子元素,而 newFiber 中的子元素有些是符合 beginWork 中的更新条件, 但有些子元素的 oldProps === newProps 为true(基本上是文本)。
以上面的例子为例,点击 Child1 组件触发click 调用setState,更新时只有Child1 会调用 updateClassComponent 更新,其它 reactElement 调用bailoutOnAlreadyFinishedWork。Child1 中的‘child1’文本,符合beginWork条件,也会调用bailoutOnAlreadyFinishedWork。
建议
fiber是 React Tree中的’dom’。不同reactElemnt转化为fiber时,fiber之间只是属性不同。用组件封装不会减慢运行速度,相反使用恰当的话,可以提升运行速度。