【问题标题】:useState vs useReduceruseState 与 useReducer
【发布时间】:2019-07-05 21:23:48
【问题描述】:

当您有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常比 useState 更可取。 useReducer 还允许您优化触发深度更新的组件的性能,因为您可以向下传递调度而不是回调。

(引自https://reactjs.org/docs/hooks-reference.html#usereducer

我对粗体部分感兴趣,它指出在上下文中使用时应使用useReducer 而不是useState

我尝试了这两种变体,但它们似乎没有什么不同。

我比较这两种方法的方式如下:

const [state, updateState] = useState();
const [reducerState, dispatch] = useReducer(myReducerFunction);

我将它们中的每一个都传递给一个上下文对象,该对象在更深层次的子对象中使用(我只是运行了单独的测试,将值替换为我想要测试的函数)。

<ContextObject.Provider value={updateState // dispatch}>

孩子包含这些功能

const updateFunction = useContext(ContextObject);
useEffect(
  () => {
    console.log('effect triggered');
    console.log(updateFunction);
  },
  [updateFunction]
);

在这两种情况下,当父级重新渲染时(由于另一个本地状态更改),效果从未运行,表明更新函数在两次渲染之间没有更改。 我读错了引用中的粗体句子吗?还是我忽略了什么?

【问题讨论】:

  • 我的猜测是 dispatch 是一个不可变的函数,并且在嵌套函数中创建的回调每次都会创建新的引用,因此会破坏 shouldComponentUpdate 中的相等性检查

标签: reactjs react-hooks


【解决方案1】:

useReducer 还允许您优化组件的性能 触发深度更新,因为您可以向下传递调度而不是 回调。

上述语句并不是要表明useState 返回的setter 在每次更新或渲染时都是新创建的。这意味着当您有一个复杂的逻辑来更新状态时,您根本不会直接使用 setter 来更新状态,而是会编写一个复杂的函数,该函数反过来会调用具有更新状态的 setter,例如

const handleStateChange = () => {
   // lots of logic to derive updated state
   updateState(newState);
}

ContextObject.Provider value={{state, handleStateChange}}>

现在在上述情况下,每次重新渲染父级时都会创建一个新的 handleStateChange 实例,从而导致上下文使用者也重新渲染。

上述情况的一个解决方案是使用useCallback并记忆状态更新器方法并使用它。但是,为此,您需要处理与在方法中使用值相关的闭包问题。

因此建议使用useReducer,它返回一个dispatch 方法,在重新渲染之间不会改变,您可以在reducer 中使用操作逻辑。

【讨论】:

  • 那么,如果我理解正确的话,如果我只用它来直接设置一个值,那么两个函数是相等的吗?
  • @JDansercoer,是的,在这种情况下,两个函数都是相等的。实际上 useState 在内部使用了 useReducer
  • 另外,旁注:在你的例子中,它总是会让孩子重新渲染,因为值对象是在每次渲染时创建的,并且对象是通过引用,因此不一样:)
  • 没错,我只是想演示一下。
  • @ecoe,没有 useReducer 在功能上不等同于 useCallback。 useCallback 用于函数的记忆,而 useReducer 用于存储和更新状态。 useReducer 返回的 dispatch 在每次渲染时都不会改变,因此可以将它传递给应用 memoization 的子组件
【解决方案2】:

当你需要关心它时

如果您在渲染上创建回调并将其传递给子组件,则该子组件的道具将发生变化。但是,当父级渲染时,常规组件将重新渲染(到虚拟 dom),即使道具保持不变。例外的是实现了 shouldComponentUpdate 的 classComponent,并比较了 props(如 PureComponent)。

这是一种优化,只有在重新渲染子组件需要大量计算(如果您将它多次渲染到同一个屏幕,或者如果它需要深度或大量重新渲染)时,您才应该关心它。

如果是这种情况,您应该确保:

  1. 您的孩子是扩展 PureComponent 的类组件
  2. 避免将新创建的函数作为道具传递。相反,通过 dispatch,从 React.useState 返回的 setter 或 memoized 自定义设置器。

使用记忆化的自定义设置器

虽然我不建议为特定组件构建一个独特的 memoized setter(您需要注意一些事项),但您可以使用一个通用的钩子来为您处理实现。

这是一个 useObjState 钩子的示例,它提供了一个简单的 API,并且不会导致额外的重新渲染。


const useObjState = initialObj => {
  const [obj, setObj] = React.useState(initialObj);
  const memoizedSetObj = React.useMemo(() => {
    const helper = {};
    Object.keys(initialObj).forEach(key => {
      helper[key] = newVal =>
        setObj(prevObj => ({ ...prevObj, [key]: newVal }));
    });
    return helper;
  }, []);
  return [obj, memoizedSetObj];
};

function App() {
  const [user, memoizedSetUser] = useObjState({
    id: 1,
    name: "ed",
    age: null,
  });

  return (
      <NameComp
        setter={memoizedSetUser.name}
        name={user.name}
      />
  );
}

const NameComp = ({name, setter}) => (
  <div>
    <h1>{name}</h1>
      <input
        value={name}
        onChange={e => setter(e.target.value)}
      />
  </div>
)

Demo

【讨论】:

  • 不是正确的。 useStateuseReducer 的设置器保证在渲染之间是相等的(如 React 文档中所述),因此无需记忆它。
  • @JDansercoer,从 useState 返回的设置器通过渲染是相等的。我没有记住那个 setter,而是从 useObjectState 返回的 setter。
  • @JDansercoer、StackOverFlow 和社区都渴望成为一个礼貌和热情的环境。至少它会提供更好的答案质量和 cmets。你不明白什么?您不喜欢当前答案的哪些方面?请告诉我,我会调整和澄清。
猜你喜欢
  • 2020-06-27
  • 1970-01-01
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 2021-05-10
  • 1970-01-01
相关资源
最近更新 更多