简体   繁体   English

由后坐力触发的 useEffect state 在 safari 中无法按预期工作

[英]useEffect triggered by recoil state doesn't work as expected in safari

I want to trigger input type="file" click when state outside the component has changed.我想在组件外部的 state 发生变化时触发input type="file"点击。

When updated state is passed as prop and useEffect function is excecuted everything work as expected.当更新 state 作为 prop 和 useEffect function 被执行时,一切都按预期工作。

When updated state is passed as useRecoilState it seems like it doesn't work (but only in safari) - it triggers useEffect as expected but inputRef.current.click() doesn't work.当更新 state 作为useRecoilState传递时,它似乎不起作用(但仅在 safari 中) - 它按预期触发 useEffect 但inputRef.current.click()不起作用。

Here is the working code presenting the problem: https://codesandbox.io/s/useeffect-recoil-trigger-vs-prop-w7lwq?file=/src/index.js:315-329这是出现问题的工作代码: https://codesandbox.io/s/useeffect-recoil-trigger-vs-prop-w7lwq?file=/src/index.js:315-329

And here is code:这是代码:

export const recoilTriggerAtom = atom({
  key: "recoilTriggerAtom",
  default: 0
});

function ChildComponent({ propTrigger }) {
  const [recoilTrigger] = useRecoilState(recoilTriggerAtom);
  const inputRef = useRef(null);

  useLayoutEffect(() => {
    if (recoilTrigger > 0) {
      console.log("here it doesn't work in Safari");
      inputRef.current.click();
    }
  }, [recoilTrigger]);

  useLayoutEffect(() => {
    if (propTrigger > 0) {
      console.log("here it works in Safari");
      inputRef.current.click();
    }
  }, [propTrigger]);

  const onFileSelected = (e) => {
    console.log("onFileSelected", e.target.files[0]);
  };

  return (
    <div className="App">
      <input type="file" ref={inputRef} onChange={onFileSelected} />
    </div>
  );
}

function ParentComponent() {
  const [propTrigger, setPropTrigger] = useState(0);
  const [recoilTrigger, setRecoilTrigger] = useRecoilState(recoilTriggerAtom);

  const updateRecoilTrigger = () => {
    setRecoilTrigger(recoilTrigger + 1);
  };

  const updatePropTrigger = () => {
    setPropTrigger(propTrigger + 1);
  };

  return (
    <div>
      <button onClick={updateRecoilTrigger}>Recoil trigger</button>
      <button onClick={updatePropTrigger}>Prop trigger</button>
      <ChildComponent propTrigger={propTrigger} />
    </div>
  );
}

function Root() {
  return (
    <RecoilRoot>
      <ParentComponent />
    </RecoilRoot>
  );
}

This is most curious.这是最好奇的。 Both won't work if you use useEffect instead of useLayoutEffect (but also only in Safari, FF and Chromium behave as expected).如果您使用useEffect而不是useLayoutEffect两者都将不起作用(但也仅在 Safari 中,FF 和 Chromium 的行为符合预期)。 Since in Safari it works if you use useLayoutEffect I am suspecting that Safari and Reacts async nature don't go along very well.由于在 Safari 中,如果您使用useLayoutEffect它可以工作,我怀疑 Safari 和 Reacts 异步性质不会很好地处理 go。

My guess is that Safari is extremely restrictive regarding programmatically opening dialogs.我的猜测是 Safari 对于以编程方式打开对话框非常严格。 So I think that Safari looses the user induced click somewhere is the whole async process of React.所以我认为 Safari 在某处失去用户诱导的点击是 React 的整个异步过程。

The reason it only works using useLayoutEffect in relation with a prop/useState is that this effect will trigger in the same render cycle as the action itself (in opposition to useEffect which is called async).它仅使用与 prop/ useLayoutEffect相关的 useLayoutEffect 起作用的原因是,此效果将在与操作本身相同的渲染周期中触发(与称为异步的useEffect )。 So Safari still knows that the user triggered an action in which the result is the opening of the dialog, which Safari happily allows.所以 Safari 仍然知道用户触发了一个动作,结果是打开了对话框,Safari 很高兴地允许了。

But using this with recoil (and for that matter probably with any other external state management supporting concurrent mode), things are bit different.但是将其与后坐力一起使用(就此而言,可能与支持并发模式的任何其他外部 state 管理一起使用),情况有所不同。

Because you are setting an external state React won't trigger a commit phase right away.因为您正在设置外部 state,所以 React 不会立即触发提交阶段。 And recoil in particular collects state update for a short period of time and then triggers a new commit and rendering cycle inside React.尤其是反冲会在短时间内收集 state 更新,然后在 React 中触发新的提交和渲染周期。 So user action, state update and rendering phase are three different time slices.所以用户操作,state 更新和渲染阶段是三个不同的时间片。 Since recoil supports concurrent mode this can even mean multiple phases of commits, so rendering might be put on hold even longer.由于反冲支持并发模式,这甚至可能意味着多个提交阶段,因此渲染可能会被搁置更长时间。 This is all happening asynchronous, so the useLayoutEffect that listens to the recoil trigger gets executed in a different rendering cycle then the user clicked on the button.这一切都是异步发生的,因此监听反冲触发器的useLayoutEffect会在不同的渲染周期中执行,然后用户单击按钮。

And I think this is the reason why it won't work inside Safari.我认为这就是它在 Safari 中不起作用的原因。 Safari does not connect those two together or rather already "forgot" that the user clicked a button. Safari 没有将这两个连接在一起,或者更确切地说已经“忘记”了用户点击了一个按钮。 In the rendering cycle after setting the recoil state Safari only sees an imperative call to click on the file input, but has no recollection of the user clicking triggering any action in this cycle.在设置recoil state Safari 后的渲染周期中,只看到点击文件输入的命令式调用,但不记得用户点击触发此循环中的任何操作。

What I would do is go to the Github page of recoil and post the codesandbox you prepared to get confirmation if what I described is indeed what's happening here.我要做的是 go 到 Github 反冲页面,然后发布您准备好的代码框,以确认我所描述的确实是这里发生的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM