react 错误:如果在useEffect外部还调用了setState,则useSyncExternalStore不会更新内部值,从而导致存储集无法重新呈现

fhity93d  于 2022-10-28  发布在  React
关注(0)|答案(4)|浏览(188)

在一个自定义钩子中,我忘了将我的setState调用 Package 在一个useEffect中,这在React 18之前没有任何问题,但是现在不起作用了。如果存储更改回其初始值,组件不会重新呈现。我猜这可能是意料之中的,但是没有任何警告,所以很难发现。
所以这更像是一个问题:

  • 这种行为是错误吗?
  • 如果不是,那么警告另一个犯同样错误的开发人员是否有用?

React版本:18

重现步骤

1.让组件使用useSyncExternalStore(例如redux 8)
1.在useEffect或事件行程常式(在转译中)之外呼叫setState
1.来回触发对存储的更改
链接到代码示例:https://codesandbox.io/s/react-18-redux-8-reproduce-bug-ojdx43的最大值

当前行为

  • 如果存储值更新为与其初始值不同的值,则组件将重新呈现
  • 如果存储区再次更新为初始值,组件不会重新呈现

预期行为

  • 如果存储值更新为与其初始值不同的值,则组件将重新呈现
  • 如果存储区再次更新为初始值,组件*也会重新呈现

继续挖掘
当我试图追踪这个问题时,我发现在useEffect外部调用的setState会扰乱纤程更新队列,特别是之前的pushEffect应该用最新的值调用updateStoreInstance以更新内部缓存示例。由于setState未 Package ,它根本没有被调用(取消?覆盖?)。
因此,缓存的示例永远不会更新,除了初始值之外的任何其他值都会正确地触发重新呈现,因为checkIfSnapshotChanged最终总是与初始值进行比较。

aydmsdu9

aydmsdu91#

为了熟悉react代码库,我写了一个小测试,在这个模式上失败了。(即使是预期的那样)
以下是fwiw(单位:packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js):

test('store value is correctly stored in current hook instance even with interleaved effects occurring', async () => {
    const store = createExternalStore('value:initial');

    function App() {
      const value = useSyncExternalStore(store.subscribe, store.getState);
      const [sameValue, setSameValue] = useState(value);
      if (value !== sameValue) setSameValue(value);
      return <Text text={value} />;
    }

    const root = ReactNoop.createRoot();
    act(() => {
      // Start a render that reads from the store and yields value
      root.render(<App />);
    });
    expect(Scheduler).toHaveYielded(['value:initial']);

    await act(() => {
      store.set('value:changed');
    });
    expect(Scheduler).toHaveYielded(['value:changed']);

    await act(() => {
      store.set('value:initial');
    });
    expect(Scheduler).toHaveYielded(['value:initial']); 
  });
});

最后一个判断提示会因setSameValue行而失败,并在没有的情况下通过。

nc1teljy

nc1teljy2#

我不明白的是,在renderWithHooks中,有以下代码块:

// Check if there was a render phase update
  if (didScheduleRenderPhaseUpdateDuringThisPass) {

如果在render中调用了setState,它就会运行。然后,它会再次调用组件函数--但要这样做,它会重置workInProgress的状态,包括updateQueue。IIUC这会丢弃之前钩子推送的效果,而不刷新它们?
这就是为什么useSyncExternalStore效果更新存储值不运行的原因,在这种情况下。
事实上,有代码写来管理setState调用呈现,似乎承认这是一个合法的用例?
我一定是错过了一些东西😅,如何确保这些效果运行,即使组件功能被再次调用之前,工作结束?

tct7dpnv

tct7dpnv3#

是的,在函数组件的主体中调用setState()是法律的的,只要你有条件地这样做:

  • https://beta.reactjs.org/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
  • https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
  • https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#setting-state-while-rendering
i5desfxk

i5desfxk4#

好的,根据我之前的评论,IIUC,当状态在渲染阶段被分派时,丢弃正在进行的工作钩子是预期的。正在进行的工作队列应该被重新创建(以反映新的状态)。
useSyncExternalStore使用在制品memoizedState来比较先前的值,但在第二次呼叫的情况下,这已经包含新值,因此无法达成目的。
当优先使用currentHook时,它可以工作。请参阅附带的PR和修复程序,让我知道你的想法!

相关问题