首页 > 解决方案 > 这如何解决反应钩子的状态关闭问题?

问题描述

我正在查看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;
}

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

我不明白为什么创建一个refin auseEffect()可以治愈任何事情。

它会使 linter 静音吗?

标签: reactjstypescriptref

解决方案


文档实际上指出:

无论哪种情况,我们都不推荐这种模式,只是为了完整性而在此处显示。相反,最好避免在深处传递回调

假设我们无法避免传递回调,那么最简单的方法是为状态设置器使用回调:setSomeState(currentState=>....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>


推荐阅读