为什么当使用函数引用作为初始状态时,'useState'钩子会调用它?

15

React有一个叫做useState的hook,用于在函数组件中添加状态。

Hooks API Reference指出:

useState:

const [state, setState] = useState(initialState);

Returns a stateful value, and a function to update it.

During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).

The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.

React文档说明:

我们在useState中传递什么参数?

useState() Hook的唯一参数是初始状态。与类不同,状态不必是对象。如果我们只需要保留数字或字符串,那么可以使用它们。在我们的示例中,我们只想要一个数字来表示用户点击的次数,因此将0作为变量的初始状态传递即可。(如果我们想在状态中存储两个不同的值,则需要调用useState()两次。)

意外行为:

然而,我注意到了一些奇怪的、看似未记录的行为。

如果我尝试使用useState hook来存储一个函数作为状态,react会调用函数引用。例如:

const arbitraryFunction = () => {
    console.log("I have been invoked!");
    return 100;
};

const MyComponent = () => {

    // Trying to store a string - works as expected:
    const [website, setWebsite] = useState("stackoverflow"); // Stores the string
    console.log(typeof website);                             // Prints "string"
    console.log(website);                                    // Prints "stackoverflow"

    // Trying to store a function - doesn't work as expected:
    const [fn, setFn] = useState(arbitraryFunction);         // Prints "I have been invoked!"
    console.log(typeof fn);                                  // Prints "number" (expecting "function")
    console.log(fn);                                         // Prints "100"

    return null; // Don't need to render anything for this example...
};

当我们调用 useState(arbitraryFunction) 时,React 将调用 arbitraryFunction 并使用其返回值作为状态。

解决方法:

我们可以通过将函数引用包装在另一个函数中来将函数存储为状态。例如:
const [fn, setFn] = useState(() => arbitraryFunction)

我还没有遇到过将函数作为状态存储的实际原因,但有人明确选择以不同的方式处理函数参数似乎很奇怪。
这个选择可以在React代码库的多个地方看到:
initialState = typeof initialArg === 'function' ? initialArg() : initialArg;

为什么会存在这个貌似未记录的功能?

我想不出为什么有人会希望/期望调用他们的函数引用,但也许你可以。

如果这是有记录的,请问在哪里可以找到记录?


4
https://reactjs.org/docs/hooks-reference.html#lazy-initial-state - tkausl
1
@tkausl 很棒,我不确定我是怎么错过这个的。 - byxor
2
这非常有趣!我学到了一件事情! - Joe Lloyd
1
@tkausl 这是一个答案,而不是评论 - 将其发布为答案并观察您的声望增长 :) - gustafc
1个回答

16

这里有关于“懒加载初始状态”的文档:

懒加载初始状态

initialState参数是在初始渲染期间使用的状态。 在后续的渲染中,它将被忽略。如果初始状态是昂贵计算的结果, 您可以提供一个函数,该函数仅在初始渲染时执行:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
将回调函数传递给 setState也会调用回调函数,但原因不同

函数式更新

如果新状态是使用先前的状态计算出来的,您可以将一个函数传递给 setState。该函数将接收先前的值并返回更新后的值。下面是一个计数器组件的示例,它使用了两种形式的 setState:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接