React:使用回调还是在内部组件中定义?

React: to useCallback or define in inner component?

我有两个组成部分,它们是:

const ChildOn = (/*...*/) => {
  //...
}

const Parent = () => {
  const [is42, setIs42] = useState(true)

  return (is42 ? <ChildOff ... > : <ChildOn ... />)
}

ChildOff的定义不重要。

我想将它们定义为以下任何一种,但我无法决定是哪种:

  1. 根据父项中的 variable/function 在每个子项中声明子项中使用的函数。

    type ChildOnProp = { setIs42: Dispatch<SetStateAction<boolean>> };
    const ChildOn = ({ setIs42 }: ChildOnProp) => {
      const f1 = () => { setIs42(true); };
      return <Text onPress={f1} />;
    };
    
    const Parent = () => {
      return is42 
        ? <ChildOff setIs42={setIs42} /> 
        : <ChildOn setIs42={setIs42} />;
    };
    
  2. 在父级内部定义子级使用的函数。

    type ChildOnProps = { func: () => void }
    const ChildOn = ({ func }: ChildOnProps) => {
      return <Text onPress={func} />
    }
    
    const Parent = () => {
      const [is42, setIs42] = useState(true)
      const f1 = useCallback(() => { setIs42(true) })
      const f2 = useCallback(() => { setIs42(false) })
      return (is42 ? <ChildOff func={f2} /> : <ChildOn func={f1} />)
    }
    

虽然 (1) 对我来说更漂亮,但 (2) 似乎更有效率。然而我不知道我是否适合这样判断,因为我读过很多关于 React 的文章,在什么时候最好使用 useCallback.

上相互矛盾

React 社区警告不要进行无用的记忆,因为在某些情况下它可能会增加复杂性而没有任何好处。

在这种情况下,我认为值得记住回调,因为它会减少子组件不必要的渲染次数,否则可能会产生负面影响,甚至会随着时间的推移引入问题。

为此,有一种创建自定义挂钩的常见模式,通常称为 useToggle,它会记住常见的设置器。

import {useState, useMemo} from 'react';

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  // Defined once, so guaranteed stability
  const setters = useMemo(() => ({
    toggle: () => setValue(v => !v),
    setFalse: () => setValue(false),
    setTrue: () => setValue(true),
    setValue,
  }), [setValue]);

  // Defined each time the value changes, so less than every render.
  return useMemo(() => ({
    ...setters,
    value
  }), [value, setters]);
}

这可以在父级中使用:

const Parent = () => {
  const { value: is42, setTrue, setFalse } = useToggle(true);
  return (is42 ? <ChildOff func={setFalse}> : <ChildOn func={setTrue} />);
}

TypeScript 会自动推断所有类型,因此它开箱即用。


如果有多个状态值需要切换回调,我们可以通过不解构来轻松识别每个状态值。

const firstState = useToggle(true);
const secondState = useToggle(true);
//...
return (
  <>
    <Foo onClick={firstState.toggle} />
    <Bar onClick={secondState.toggle} />
  </>
);

作为参考,这里有一个simpler implementation from Shopify。 至于记忆过多或过少,Meta (Facebook) 显然 experimenting with memoizing everything by default 在转译时,因此开发人员不必考虑它。