27

文档

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

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

4

4 回答 4

25

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


据我所知,init作为第三个参数的函数是initialState.

这意味着它initialState不会用作初始状态,而是用作init函数的 arg。这一个的回报将是真实的initialStateuseReducer在初始化行期间避免巨大的参数可能很有用。

/* 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>

于 2020-04-16T08:32:37.840 回答
10

我的理解是延迟初始化是为初始化状态的代码是内存密集型或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()hook也可以达到类似的效果。但是惰性初始化仍然是最直接的解决方案。

于 2020-06-21T23:02:12.733 回答
6

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

例如:

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

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

例如:Codesandbox 示例链接

于 2019-11-25T23:07:00.650 回答
2

我认为一个很好的理解方法useReduceruseState作为一个例子,其中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
    );
  }
}
于 2021-12-11T20:01:23.337 回答