持久化 function 的 Hook函数usePersistFn源码理解?

新手上路,请多包涵
function usePersistFn(fn) {
  var fnRef = react_1.useRef(fn);
  fnRef.current = fn;
  
  var persistFn = react_1.useRef();

  if (!persistFn.current) {
    persistFn.current = function () {
      var args = [];

      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
      }

      return fnRef.current.apply(this, args);
    };
  }

  return persistFn.current;
}

怎么解读 usePersistFn函数?

阅读 5.1k
1 个回答

就拿umi官方的例子来说明,有如下代码

import React, { useState, useCallback, useRef } from 'react';
import { Button, message } from 'antd';
import { usePersistFn } from '@umijs/hooks';
export default () => {
  const [count, setCount] = useState(0);
  const showCountPersistFn = usePersistFn(() => {
    message.info(`Current count is ${count}`);
  });
  const showCountCommon = useCallback(
    () => {
      message.info(`Current count is ${count}`);
    },
    [count],
  )
  return (
    <>
      <Button onClick={() => { setCount(c => c + 1) }}>Add Count</Button>
      <p>You can click the button to see the number of sub-component renderings</p>
      <div style={{ marginTop: 32 }}>
        <h4>Component with persist function:</h4>
        {/* use persist function, ExpensiveTree component will only render once */}
        <ExpensiveTree showCount={showCountPersistFn} />
      </div>
      <div style={{ marginTop: 32 }}>
        <h4>Component without persist function:</h4>
        {/* without persist function, ExpensiveTree component will re-render on state change */}
        <ExpensiveTree showCount={showCountCommon} />
      </div>
    </>
  );
};
// some expensive component with React.memo
const ExpensiveTree = React.memo<{ [key: string]: any }>(({ showCount }) => {
  const renderCountRef = useRef(0);
  renderCountRef.current += 1;
  return (
    <div>
      <p>Render Count: {renderCountRef.current}</p>
      <Button onClick={showCount}>showParentCount</Button>
    </div>
  )
})

首先我们得明白什么情况下会导致函数组件的重新渲染,有三种情况

  • Props不严格相等
  • Context变化
  • 组件内部调用了dispatchAction也就是useState返回的第二个参数

很明显我们要谈论的情况属于第一种Props不严格相等,由于每次更新是count都会变化所以showCountCommon总是返回本次更新时创建的函数,所以在这种情况下useCallback没什么卵用,所以我们把他简写成等价的代码,这也就是他每次都重新渲染的原因,因为Props每次都不能严格相等

 const showCountCommon = () => {
      message.info(`Current count is ${count}`);
 }

usePersistFn则另辟蹊径,既然showCount不得不为了在闭包中捕获最新的count值而每次都指向新创建的函数导致前后Props不能严格相等,那么我干脆不把showCount作为Props传给子组件了,我在创建一个引用地址永不会变的函数传给子组件让子组件的Props每次都能严格相等,然后再在这个引用不会变的函数里捕获了指向该showCount函数的引用,所以只需要在showCount函数更新时把这个引用里面的showCount地址变更就行,而因为usePersistFn里面拥有showCount的ref每次执行时他总是能执行最新的showCount函数,详情看下面的注释

function usePersistFn(fn) {
  //fn为每次更新时新创建的函数,他每次更新都会重新创建,已在闭包中捕获新的state
  
  //创建一个fnRef,当fn变化时就将他的current执行新的fn,
  //所以fnRef.current总是指向最新的fn
  var fnRef = react_1.useRef(fn);
  fnRef.current = fn;
  
  //该函数的关键,创建一个引用永不会变更的函数引用
  var persistFn = react_1.useRef();

  //该函数仅仅进行了一次初始化,之后一直都没有变化
  if (!persistFn.current) {
    persistFn.current = function () {
      var args = [];

      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
      }

      //前面我们已经说过fnRef.current总是指向最新的fn,
      //所以他内部捕获的总是最新的state,所以showCountPersistFn的结果总是符合预期
      return fnRef.current.apply(this, args);
    };
  }

  //返回一个地址永远不会变的函数,他在更新前后总是严格相等的
  return persistFn.current;
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题