实现useMemoizedFn

1,307 阅读1分钟

自定义hooks

作为react的使用者,自定义hooks是常见的业务需求,小弟技术水平有限,只能先借花献佛聊下公司里大佬们封装的自定义hooks了

import { useMemo, useRef } from 'react';

type noop = (...args: any[]) => any;

/**
 * 持久化 function 的 Hook
 * @param fn 需要持久化的函数
 * @returns 引用地址永远不会变化的 fn
 */
export function useMemoizedFn<T extends noop>(fn: T) {
  if (process.env.NODE_ENV !== 'development') {
    if (typeof fn !== 'function') {
      console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
    }
  }

  const fnRef = useRef<T>(fn);

  fnRef.current = useMemo(() => fn, [fn]);

  const memoizedFn = useRef<T>();

  if (!memoizedFn.current) {
    memoizedFn.current = function(...args) {
      // eslint-disable-next-line @typescript-eslint/no-invalid-this
      return fnRef.current.apply(this, args);
    } as T
  }

  return memoizedFn.current;
}

useMemoizedFn 会保留每次运行过程中最新的函数地址引用,通常用于较为复杂的组件的props传递中以减少不必要的re-render。

用法如下

import React, { useState, useCallback } from 'react';
import { Button, Space, message } from 'antd';
import { useMemoizedFn } from '@pansy/react-hooks';

export default () => {
  const [count, setCount] = useState(0);

  const callbackFn = useCallback(
    () => {
      message.info(`Current count is ${count}`);
    },
    [count]
  );

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

  return (
    <>
      <p>count: {count}</p>

      <Space>
        <Button
          onClick={() => {
            setCount((c) => c + 1);
          }}
        >
          Add Count
        </Button>

        <Button
          onClick={callbackFn}
        >
          call callbackFn
        </Button>

        <Button
          onClick={memoizedFn}
        >
          call memoizedFn
        </Button>
      </Space>
    </>
  )
}
import React, { useState, useCallback, useRef } from 'react';
import { Button, Space, message } from 'antd';
import { useMemoizedFn } from '@pansy/react-hooks';

export default () => {
  const [count, setCount] = useState(0);

  const callbackFn = useCallback(() => {
    message.info(`Current count is ${count}`);
  }, [count]);

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

  return (
    <>
      <p>count: {count}</p>
      <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 }}>
        <h3>Component with useCallback function:</h3>
        {/* use callback function, ExpensiveTree component will re-render on state change */}
        <ExpensiveTree showCount={callbackFn} />
      </div>

      <div style={{ marginTop: 32 }}>
        <h3>Component with useMemoizedFn function:</h3>
        {/* use memoized function, ExpensiveTree component will only render once */}
        <ExpensiveTree showCount={memoizedFn} />
      </div>
    </>
  );

}

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>
  );
});

个人的分享记录,不喜勿喷。