4. 优化更新 减少不必要的计算

0 阅读1分钟

本次课程我们来优化下目前的代码,目前我们在更新子组件的时候其它组件也会更新,会造成不必要的性能浪费。

问题现状

  • 初始app.js
import React from "./core/React.js";
​
let countFoo = 1;
function Foo() {
  console.log("foo rerun");
​
  function handleClick() {
    countFoo++;
    React.update();
  }
​
  return (
    <div>
      <h1>foo</h1>
      {countFoo}
      <button onClick={handleClick}>click</button>
    </div>
  );
}
​
let countBar = 1;
function Bar() {
  console.log("bar rerun");
  function handleClick() {
    countBar++;
    React.update();
  }
​
  return (
    <div>
      <h1>bar</h1>
      {countBar}
      <button onClick={handleClick}>click</button>
    </div>
  );
}
​
let countRoot = 1;
function App() {
  console.log("app rerun");
​
  function handleClick() {
    countRoot++;
    React.update();
  }
​
  return (
    <div>
      hi-mini-react count: {countRoot}
      <button onClick={handleClick}>click</button>
      <Foo></Foo>
      <Bar></Bar>
    </div>
  );
}
​
export default App;
  • 点了按钮之后每一个组件都会进行渲染

1.png

  • 期望每一个组件单独更新

2.png

出现问题的原因

  • 更新之后从根节点整颗树进行更新,重新创建根节点
function update() {
    nextWorkOfUnit = {
        dom: currentRoot.dom,
        props: currentRoot.props,
        alternate: currentRoot
    }
    wipRoot = nextWorkOfUnit
}

解决办法

  • 开始当前更新的组件
let wipFiber = null;
​
function updateFunctionComponent(fiber) {
    wipFiber = fiber
    const children = [fiber.type(fiber.props)]
    reconcileChildren(fiber, children)
}
​
function update() {
    console.log(`wipFiber`, wipFiber)
    nextWorkOfUnit = {
        dom: currentRoot.dom,
        props: currentRoot.props,
        alternate: currentRoot
    }
    wipRoot = nextWorkOfUnit
}
  • 发现问题不管点是foo还是bar都打印出来的type是bar,因为目前定义是全局的变量

3.png

  • 使用闭包来解决这个问题
function update() {
    let currentFiber = wipFiber
    return () => {
        console.log(`currentFiber`, currentFiber)
        nextWorkOfUnit = {
            dom: currentRoot.dom,
            props: currentRoot.props,
            alternate: currentRoot
        }
        nextWorkOfUnit = wipRoot
    }
}
  • 这样修改之后使用的地方app.jsx也需要修改
import React from "./core/React.js";
​
let countFoo = 1;
function Foo() {
  console.log("foo rerun");
  const update = React.update();
  function handleClick() {
    countFoo++;
    update();
  }
​
  return (
    <div>
      <h1>foo</h1>
      {countFoo}
      <button onClick={handleClick}>click</button>
    </div>
  );
}
​
let countBar = 1;
function Bar() {
  console.log("bar rerun");
  const update = React.update();
  function handleClick() {
    countBar++;
    update();
  }
​
  return (
    <div>
      <h1>bar</h1>
      {countBar}
      <button onClick={handleClick}>click</button>
    </div>
  );
}
​
let countRoot = 1;
function App() {
  console.log("app rerun");
​
  const update = React.update();
  function handleClick() {
    countRoot++;
    update();
  }
​
  return (
    <div>
      hi-mini-react count: {countRoot}
      <button onClick={handleClick}>click</button>
      <Foo></Foo>
      <Bar></Bar>
    </div>
  );
}
​
export default App;

4.png

  • 调整指针不在wipRoot不在指向根节点
function update() {
    let currentFiber = wipFiber
    return () => {
        console.log(`currentFiber`, currentFiber)
        wipRoot = {
            ...currentFiber,
            alternate: currentFiber
        }
        nextWorkOfUnit = wipRoot
    }
}
  • 结束点遍历完整颗树,当处理兄弟节点的时候,只执行一次
function workLoop(deadline) {
    let shouldYield = false
    while (!shouldYield && nextWorkOfUnit) {
        // 返回下一个任务
        nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
        if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
            console.log('hit', wipRoot, nextWorkOfUnit)
        }
        shouldYield = deadline.timeRemaining() < 1
    }
    if (!nextWorkOfUnit && wipRoot) {
        commitRoot()
    }
    requestIdleCallback(workLoop)
}

5.png