探讨组件更新大致的过程,场景只是组件的更新。

多组件更新

多组件是我们常用的场景,若我们更新其中的某组件,react如何实现高效更新呢?

  1. class Child1 extends Component{
  2. state={
  3. count: 1
  4. }
  5. onClickFn = ()=>{
  6. this.setState({ count: this.state.count + 1 })
  7. }
  8. componentDidUpdate(){
  9. console.log('didUpdate Child1');
  10. }
  11. render(){
  12. return <div onClick={ this.onClickFn }>child1{ this.state.count }</div>
  13. }
  14. }
  15. class Child2 extends Component{
  16. state={
  17. count: 1
  18. }
  19. componentDidUpdate(){
  20. console.log('didUpdate Child2');
  21. }
  22. onClickFn = ()=>{
  23. this.setState({ count: this.state.count + 1 })
  24. }
  25. render(){
  26. return <div onClick={ this.onClickFn } >child2{ this.state.count }</div>
  27. }
  28. }
  29. class App extends Component {
  30. componentDidUpdate(){
  31. console.log('didUpdate');
  32. }
  33. render() {
  34. return (<div id="appId">
  35. Hello World!
  36. <Child1></Child1>
  37. <Child2></Child2>
  38. </div>)
  39. }
  40. }
  41. ReactDOM.render(<App/>, document.getElementById('root'));

点击组件的click事件,触发组件的setState,调用enqueueSetState。

  1. enqueueSetState: function enqueueSetState(inst, payload, callback) {
  2. var fiber = get(inst);
  3. var currentTime = requestCurrentTime();
  4. var expirationTime = computeExpirationForFiber(currentTime, fiber);
  5. var update = createUpdate(expirationTime);
  6. update.payload = payload;
  7. if (callback !== undefined && callback !== null) {
  8. update.callback = callback;
  9. }
  10. flushPassiveEffects();
  11. enqueueUpdate(fiber, update);
  12. scheduleWork(fiber, expirationTime);//
  13. }

在 scheduleWork 中 scheduleWorkToRoot 为当前fiber添加expirationTime,同时为fiber 父级的childExpirationTime 添加 expirationTime,找到并返回ReactRoot(fiberRoot)。

  1. function scheduleWork(fiber, expirationTime) {
  2. var root = scheduleWorkToRoot(fiber, expirationTime);
  3. if (root === null) { return; }
  4. //...
  5. markPendingPriorityLevel(root, expirationTime);// 为 root 添加 expirationTime 等
  6. if (!isWorking || isCommitting$1 ||nextRoot !== root) {
  7. var rootExpirationTime = root.expirationTime;
  8. requestWork(root, rootExpirationTime);
  9. }
  10. if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
  11. nestedUpdateCount = 0;
  12. }
  13. }

在 markPendingPriorityLevel 为当前root添加 expirationTime 等值,其它的和事件派发类似,执行完事件方法接着执行 performSyncWork 等方法。更新的高效性和 expirationTime 有无有关。

workLoop

最初的nextUnitOfWork 为 createWorkInProgress 创建的ReactRoot(fiberRoot)。更新的高效处理在 beginWork 中的 bailoutOnAlreadyFinishedWork中。

beginWork

在 执行到更新的 fiber 之前,workInProgress.expirationTime = 0。

  1. function beginWork(current$$1, workInProgress, renderExpirationTime) {
  2. var updateExpirationTime = workInProgress.expirationTime;
  3. if (current$$1 !== null) {
  4. var oldProps = current$$1.memoizedProps;
  5. var newProps = workInProgress.pendingProps;
  6. if (oldProps === newProps && !hasContextChanged() &&
  7. updateExpirationTime < renderExpirationTime) {
  8. //....
  9. return bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime);
  10. }
  11. }
  12. workInProgress.expirationTime = NoWork; // NoWork = 0;
  13. //..创建与更新
  14. }

在scheduleWork 下 scheduleWorkToRoot 中为 fiber的父级添加 childExpirationTime。这就让bailoutOnAlreadyFinishedWork 中 childExpirationTime < renderExpirationTime 为 false,进而执行cloneChildFibers。

  1. function bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime) {
  2. cancelWorkTimer(workInProgress);
  3. if (current$$1 !== null) {
  4. workInProgress.firstContextDependency = current$$1.firstContextDependency;
  5. }
  6. if (enableProfilerTimer) {
  7. stopProfilerTimerIfRunning(workInProgress);
  8. }
  9. var childExpirationTime = workInProgress.childExpirationTime;// expirationTime
  10. if (childExpirationTime < renderExpirationTime) {
  11. return null;
  12. } else {
  13. cloneChildFibers(current$$1, workInProgress);
  14. return workInProgress.child;
  15. }
  16. }

这样就能让更新的fiber的 上级 都进行cloneChildFibers,进而检查组件的更新。

cloneChildFibers

  1. function cloneChildFibers(current$$1, workInProgress) {
  2. !(current$$1 === null || workInProgress.child === current$$1.child) ?
  3. invariant(false, 'Resuming work not yet implemented.') : void 0;
  4. if (workInProgress.child === null) { return; }
  5. var currentChild = workInProgress.child;
  6. // 克隆fiber,currentChild.pendingProps为当前fiber上的属性
  7. var newChild = createWorkInProgress(currentChild,
  8. currentChild.pendingProps,
  9. currentChild.expirationTime);
  10. workInProgress.child = newChild;
  11. newChild.return = workInProgress;
  12. while (currentChild.sibling !== null) {
  13. currentChild = currentChild.sibling;
  14. newChild = newChild.sibling = createWorkInProgress(currentChild,
  15. currentChild.pendingProps,
  16. currentChild.expirationTime);
  17. newChild.return = workInProgress;
  18. }
  19. newChild.sibling = null;
  20. }

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之间只是属性不同。用组件封装不会减慢运行速度,相反使用恰当的话,可以提升运行速度。