2

我想input type="file"在组件外部的状态发生变化时触发点击。

当更新状态作为 prop 传递并执行 useEffect 函数时,一切都按预期工作。

当更新状态被传递时,useRecoilState它似乎不起作用(但仅在 safari 中) - 它按预期触发 useEffect 但inputRef.current.click()不起作用。

这是提出问题的工作代码:https ://codesandbox.io/s/useeffect-recoil-trigger-vs-prop-w7lwq?file=/src/index.js:315-329

这是代码:

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

4

1 回答 1

1

这是最好奇的。useEffect如果您使用代替useLayoutEffect(但也仅在 Safari 中,FF 和 Chromium 的行为符合预期),两者都将不起作用。因为在 Safari 中,如果你使用它就可以工作,useLayoutEffect我怀疑 Safari 和 Reacts 的异步性质不太好。

我的猜测是 Safari 在以编程方式打开对话框方面非常严格。所以我认为 Safari 在某处丢失了用户诱导的点击是 React 的整个异步过程。

它仅useLayoutEffect在与 prop/useState 相关的情况下起作用的原因是,此效果将在与操作本身相同的渲染周期中触发(与之相反,useEffect称为异步)。所以 Safari 仍然知道用户触发了一个动作,结果是打开了对话框,Safari 很高兴地允许这样做。

但是将它与反冲一起使用(就此而言,可能与任何其他支持并发模式的外部状态管理一起使用),事情就有些不同了。

因为您正在设置外部状态,所以 React 不会立即触发提交阶段。尤其是反冲会在短时间内收集状态更新,然后在 React 中触发新的提交和渲染周期。所以用户动作、状态更新和渲染阶段是三个不同的时间片。由于反冲支持并发模式,这甚至可能意味着多个提交阶段,因此渲染可能会被搁置更长时间。这一切都是异步发生的,因此useLayoutEffect侦听反冲触发器的会在不同的渲染周期中执行,然后用户单击按钮。

我认为这就是它无法在 Safari 中运行的原因。Safari 不会将这两者连接在一起,或者说已经“忘记”了用户单击了一个按钮。在设置反冲状态后的渲染周期中,Safari 只看到点击文件输入的命令式调用,但不记得用户点击触发此循环中的任何操作。

如果我所描述的确实是这里发生的事情,我要做的是转到 Recoil 的 Github 页面并发布你准备得到确认的代码和框。

于 2021-03-30T09:24:46.500 回答