【问题标题】:how does this fix the state closure problem with react hooks?这如何解决反应钩子的状态关闭问题?
【发布时间】:2020-01-31 02:51:55
【问题描述】:

我正在查看 formik 中的代码,这显然是解决反应钩子过时关闭问题的一种方法。

function useEventCallback<T extends (...args: any[]) => any>(fn: T): T {
  const ref: any = React.useRef();

  // we copy a ref to the callback scoped to the current state/props on each render
  useIsomorphicLayoutEffect(() => {
    ref.current = fn;
  });

  return React.useCallback(
    (...args: any[]) => ref.current.apply(void 0, args),
    []
  ) as T;
}

我在其他库中经常看到这种模式,但我不明白为什么它可以治愈它。

我不明白为什么在 useEffect() 中创建 ref 可以解决任何问题。

它会使 linter 静音吗?

【问题讨论】:

  • 你的意思是这会治愈react-hooks/exhaustive-deps规则?我想是因为useRef 返回一个object that will persist for the full lifetime of the component.。 linter 知道这一点,并且不会抱怨缺少作为 ref 的依赖项。但是即使您将 ref 添加为依赖项,它也不会为组件的生命周期重新创建返回值,因为永远不会重新创建 ref。
  • 这样做会不会容易得多:useCallback( () =&gt; console.log('never changes during comp lifecycle'), [] ); 或者您是否将闭包函数传递给 useEventCallback
  • 我发现这篇文章很有帮助。如果您有时间,请阅读所有内容,否则,请跳至第 9-12 级:medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb
  • @ArashMotamedi 这是一篇不错的文章,但没有解释此自定义挂钩带来的解决方案。创建一个在其闭包范围内具有状态的函数,该函数将更改该状态,而无需在每次更改状态时都创建新函数。

标签: reactjs typescript ref


【解决方案1】:

documentation 实际上声明:

在任何一种情况下,我们都不推荐这种模式,只是为了完整性而在此处显示。相反,最好是avoid passing callbacks deep down

假设我们无法避免传递回调,那么最简单的方法是为状态设置器使用回调:setSomeState(currentState=&gt;....return something based on current state)

我不确定释放并发模式时这会如何表现,但这里有一个示例,说明如何使用回调状态设置器:

const ParentContainer = () => {
  //list is created and maintained in parent
  const [list, setList] = React.useState([
    { id: 1, val: true },
    { id: 2, val: true },
  ]);
  //simplest way to get current list is to pass a callback
  //  to the state setter, now we can use useCallback without
  //  dependencies and never re create toggle during this life cycle
  const toggle = React.useCallback(
    id =>
      setList(list =>
        list.map(item =>
          item.id === id
            ? { ...item, val: !item.val }
            : item
        )
      ),
    []
  );
  return Parent({ list, toggle });
};
const Parent = ({ list, toggle }) => (
  <div>
    {list.map(item => (
      <ItemContainer
        key={item.id}
        item={item}
        //every item gets the same toggle function
        //  reference to toggle never changes during Parent life cycle
        toggle={toggle}
      />
    ))}
  </div>
);
//Added memo to make ItemContainer a pure component
//  as long as item or toggle never changes the (render) function
//  will not be executed
//  normally a pure component should not have side effects so don't
//  do side effects in pure compnents (like mutating rendered var)
//  it is only to visibly display how many times this function was
//  called
const ItemContainer = React.memo(function ItemContainer({
  item,
  toggle: parentToggle,
}) {
  const rendered = React.useRef(0);
  //toggling item with id 1 will not increase render for
  //  other items (in this case item with id 2)
  //  this is because this is a pure component and this code
  //  will not be executed due to the fact that toggle or item
  //  never changed for item 2 when item 1 changed
  rendered.current++;
  const toggle = React.useCallback(
    () => parentToggle(item.id),
    [item.id, parentToggle]
  );
  return Item({ toggle, item, rendered });
});
const Item = ({ toggle, item, rendered }) => (
  <div
    onClick={() => toggle(item.id)}
    style={{ cursor: 'pointer' }}
  >
    <div>{item.val ? '[X]' : '[-]'}</div>
    <div>times rendered:{rendered.current}</div>
  </div>
);

//render app
ReactDOM.render(
  <ParentContainer />,
  document.getElementById('root')
);
<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>
<div id="root"></div>

【讨论】:

    猜你喜欢
    • 2020-10-23
    • 2020-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    相关资源
    最近更新 更多