这如何解决反应钩子的状态关闭问题? [英] how does this fix the state closure problem with react hooks?

查看:34
本文介绍了这如何解决反应钩子的状态关闭问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在查看 formik 中的代码,这显然是一种绕过反应钩子的陈旧关闭问题.

function useEventCallback任何>(fn:T):T{const ref: any = React.useRef();//我们在每次渲染时将一个引用复制到作用域为当前状态/道具的回调useIsomorphicLayoutEffect(() => {ref.current = fn;});返回 React.useCallback((...args: any[]) =>ref.current.apply(void 0, args),[]) 作为 T;}

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

我不明白为什么在 useEffect() 中创建 ref 可以治愈任何事情.

它会使短绒棉静音吗?

解决方案

文档 实际上指出:

<块引用><块引用>

在任何一种情况下,我们都不推荐这种模式,只是为了完整性才在此处显示.相反,最好避免传递回调深度下.

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

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

const ParentContainer = () =>{//列表在父级中创建和维护const [list, setList] = React.useState([{ id: 1, val: true },{ id: 2, val: true },]);//获取当前列表的最简单方法是传递回调//到状态设置器,现在我们可以不使用 useCallback//在这个生命周期中依赖和永远不会重新创建切换const 切换 = React.useCallback(id =>设置列表(列表 =>list.map(item =>item.id === id?{ ...item, val: !item.val }: 物品)),[]);返回父({列表,切换});};const Parent = ({ list, toggle }) =>(<div>{list.map(item => (<物品容器键={item.id}项目={项目}//每一项都有相同的切换功能//在父生命周期中对切换的引用永远不会改变切换={切换}/>))}

);//添加备忘录使ItemContainer成为纯组件//只要 item 或 toggle 永远不会改变(渲染)函数//不会被执行//通常纯组件不应该有副作用,所以不要//在纯组件中做副作用(比如改变渲染的 var)//只是为了直观的显示这个函数被执行了多少次//调用const ItemContainer = React.memo(function ItemContainer({物品,切换:父切换,}) {const 渲染 = React.useRef(0);//切换 id 为 1 的项目不会增加渲染//其他项目(在本例中为 id 2 的项目)//这是因为这是一个纯组件和这段代码//由于切换或项目的事实不会被执行//当第 1 项改变时,第 2 项从未改变渲染.current++;const 切换 = React.useCallback(() =>parentToggle(item.id),[item.id, parentToggle]);返回项目({切换,项目,呈现});});const Item = ({toggle, item, render }) =>(

切换(item.id)}样式={{ 光标:'指针' }}><div>{item.val ?'[X]' : '[-]'}

<div>渲染次数:{rendered.current}</div>

);//渲染应用ReactDOM.render(<父容器/>,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>

I am looking at the code in formik that apparently is a way around the stale closure problem with react hooks.

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

I've seen this pattern a lot in other libs but I don't understand why this cures it.

I don't understand why creating a ref in a useEffect() cures anything.

Does it silence the linter?

解决方案

The documentation actually states:

In either case, we don’t recommend this pattern and only show it here for completeness. Instead, it is preferable to avoid passing callbacks deep down.

Let's say we can't avoid passing callbacks then the simplest way would be to use a callback for the state setter: setSomeState(currentState=>....return something based on current state)

I'm not sure how this would behave when concurrent mode is released but here is an example of how you can use a callback to the state setter:

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>

这篇关于这如何解决反应钩子的状态关闭问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆