【问题标题】:What's the purpose of the 3rd argument in useReducer?useReducer 中第三个参数的目的是什么?
【发布时间】:2020-03-21 07:20:47
【问题描述】:

来自docs

[init, the 3d argument] 让你提取在 reducer 之外计算初始状态的逻辑。这对于稍后重置状态以响应操作也很方便。

还有代码:

function init(initialCount) {
  return { count: initialCount };
}

function reducer(state, action) {
  switch (action.type) {
    ...
    case 'reset':
      return init(action.payload);
    ...
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  ...
}

我为什么要重复使用常量initialState

const initialState = {
  count: 5,
};

function reducer(state, action) {
  switch (action.type) {
    ...
    case 'reset':
      return initialState;
    ...
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  ...
}

对我来说看起来不那么冗长。

【问题讨论】:

  • 如果你问他们为什么提供看似无用的 API,答案是延迟初始化。这只是一个不错的功能。既然 useReducer 钩子显然取自 Redux,他们为什么不保持 API 一致呢。
  • 明白了。但是状态不是用默认值初始化的吗? initialCount 仍然作为第二个参数存在。
  • 是的,您可以选择两种用法中的任何一种。我认为文档很清楚,link
  • 对不起,我问的是即使你设置了第三个参数,是否设置了初始状态?如果答案是否定的,我想这就是延迟加载的意思。也就是说,用户必须发送 reset 更新以设置初始计数,否则 count 为空?
  • 你弄错了。情况 1,useReducer(reducer, 0),则初始化计数为 0。情况 2useReducer(reducer, 7, n => 2 * n),则初始化计数为 14。清楚吗?

标签: javascript reactjs react-hooks use-reducer


【解决方案1】:

useReducer 接受一个可选的第三个参数,initialAction。如果提供,则在初始渲染期间应用初始操作。

例如:

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialState, {
    type: "reset",
    payload: initialCount
  });

如您所见,第三个参数是要执行的初始操作,在初始渲染期间应用。

例如:Codesandbox Example Link

【讨论】:

  • 我还是不明白,因为文档说:It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action。以函数为例。
  • 我不认为这是正确的。更多信息在这里 - reactjs.org/docs/hooks-reference.html#lazy-initialization
【解决方案2】:

2020 年 7 月编辑:React documentation 现在对这个名为 lazy initializer 的参数有了更好的解释。由于未记录的效果,以另一种方式使用此功能可能会导致破坏性更改。以下答案仍然有效。


据我所知,作为第三个参数的init 函数是initialState 的转换器。

这意味着initialState 不会用作初始状态,而是用作init 函数的arg。这个返回将是真正的initialState。在 useReducer 初始化行期间避免使用大参数可能很有用。

/* Here is the magic. The `initialState` pass to 
 * `useReducer` as second argument will be hook
 * here to init the real `initialState` as return
 * of this function
 */
const countInitializer = initialState => {
  return {
    count: initialState,
    otherProp: 0
  };
};

const countReducer = state => state; // Dummy reducer

const App = () => {
  const [countState /*, countDispatch */] =
    React.useReducer(countReducer, 2, countInitializer);

  // Note the `countState` will be initialized state direct on first render
  return JSON.stringify(countState, null, 2);
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

【讨论】:

  • 这应该被标记为接受,imo。 @developerKumar 的说法是错误的(它确实有效,但它是错误的),它确实不是初始动作,而是初始状态转换器。
  • @AlexanderKim 如果你有兴趣我已经升级了我的答案以便更明确
【解决方案3】:

我的理解是惰性初始化是为初始化状态的代码是内存密集型或CPU密集型的特殊情况而设计的,因此开发人员希望将状态数据的范围保持在组件内部。

例如,如果您要设计一个 PhotoPane 组件,其中包含用于编辑的高清照片。

const PhotoPane = (props) => {
    const initialPixelData = loadPhoto(props.photoID);
    const [pixelData, dispatch] = useReducer(reducerFunc, initialPixelData);
    ...
}

以上代码存在严重的性能问题,因为loadPhoto() 被重复调用。如果不想每次组件渲染时都重新加载照片,直观的反应是将loadPhoto(props.photoID)移出组件。但这会导致另一个问题。您必须将所有照片加载到 Context 或其他地方的内存中,这肯定会造成内存占用。

所以现在是我们介绍延迟初始化的时候了。请查看下面的代码。

const PhotoPane = (props) => {
    const init = (photoID) => loadPhoto(photoID);
    const [pixelData, dispatch] = useReducer(reducerFunc, props.photoID, init);
    ...
}

init() 函数仅在首次调用 useReducer 时执行一次。

其实useEffect()钩子可以达到类似的效果。但是惰性初始化仍然是最直接的解决方案。

【讨论】:

    【解决方案4】:

    我认为理解useReducer 的一个好方法是以useState 为例,其中useState 具有初始值或惰性初始化器。

    import { Dispatch, useReducer } from "react";
    export function useStateUsingReducer<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
      if (typeof initialState === "function") {
        return useReducer(
          (state: S, newState: S) => (Object.is(state, newState) ? state : newState),
          null as unknown as S,
          initialState as () => S
        );
      } else {
        return useReducer(
          (state: S, newState: S) => (equals(state, newState) ? state : newState),
          initialState
        );
      }
    }
    

    这个更实用的版本是做深度等于useState 只到Object.is

    import { equals } from "ramda";
    import { Dispatch, useReducer } from "react";
    export function useDeepState<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
      if (typeof initialState === "function") {
        return useReducer(
          (state: S, newState: S) => (equals(state, newState) ? state : newState),
          null as unknown as S,
          initialState as () => S
        );
      } else {
        return useReducer(
          (state: S, newState: S) => (equals(state, newState) ? state : newState),
          initialState
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-26
      • 2020-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多