如何使用Ref来减少re-render

430 阅读2分钟

useRef "桥梁"

用传统的套路来写一个todo-list,把数据放在公共的父级组件里面,只要父级的data发生改变,所有的子组件都会被执行(在不使用React.memo之类的手段进行优化时);

但是通过分析你可以发下, 数据变化的集中区域只在List组件中, 而Control中添加增加Task的功能只需要传递一个工作内容而已, 那么我们可不可以把这个回调事件保存在父级, 通过父级传给List?

// App.jsx
function App() {
  const ref = useRef({})
  return (
    <div className="App">
      <Header />
      <List
        handler={ref}
      />
      <Control
        handler={ref}
      />
    </div>
  );
}
// List.jsx
useLayoutEffect(() => {
    handler.current.onInsert = (val) => {
      console.log("insert");
      if (val) {
        setData((r) => r.concat(createItemData(val)));
      }
      return val;
    };
 }, []);
// Control.jsx
 <button
    className="button"
    onClick={() => {
      setToggle(true);
      if (handler.current.onInsert(value)) {
        setToggle(false);
        setValue("");
      }
    }}
  >
     {toggle ? "确定" : "增加"}
  </button>

对比

每次操作会更新所有组件 branch-main 优化前

每次操作只更新相关的组件 branch-todo-list-ref 优化后

貌似contextAPI也不做到这点 playground

useRef 和 useMemo

共同点

  1. 都可以用来缓存数据
  2. 都不会引发页面的更新

不同点

  1. 改变value的条件不同, useRef可以随时改变 而 useMemo 必须要通过deps改变而且deps必须是响应式数据(eg: useState数据) 也就是说useMemo只能React来调用

  2. 由于useMemo 是由于state改变而引起的, 容易造成错觉, 以为useMemo改变了, 页面就会更新

  3. useRef 除了能够保存数据之外, 还能拿到dom元素

  // let count = 0
  let [count, setCount] = useState(0)
  const data = useMemo(() => {
    console.log(count)
    return `c==>${count}`
  }, [count])
  useEffect(() => {
    let timer = setInterval(() => {
      setCount(c => c + 1)
      // count++
    }, 1000)
    return () => {
      clearInterval(timer)
    }
  }, [])

关于re-render

此时也不会引起子组件的render, 因为Header是App的children

// App.jsx
<div className="App">
  <Test>
    <Header />
    {/* <List
      handler={ref}
    />
    <Control
      handler={ref}
    /> */}
  </Test>
</div>

// Test.jsx
function Test({ children }) {
  const [c, setC] = useState(0)
  useEffect(() => {
    setInterval(() => {
      setC(x => x + 1)
    }, 1000)
  }, [])
  useEffect(() => {
    console.log('update c', c, Test.name)
  }, [c])
  return children
}

小结:

  1. 闭包的本质就是局部数据共享
  2. 组件和闭包的本质一致
  3. 当组件之间需要共享数据时, 就应该在他们共有的父级组件中寻找突破口
  4. useRef可以充当这个桥梁, useMemo 适合"计算属性"

参考