Как это решает проблему закрытия состояния с помощью ловушек реагирования? - PullRequest
1 голос
/ 01 октября 2019

Я смотрю на код в 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;
}

Я часто видел эту схему в другихlibs, но я не понимаю, почему это излечивает это.

Я не понимаю, почему создание ref в useEffect() излечивает что-либо.

Это заставляет замолчать линтера?

1 Ответ

1 голос
/ 26 октября 2019

Документация фактически гласит:

В любом случае, мы не рекомендуем этот шаблон и показываем его здесь только для полноты. Вместо этого предпочтительнее избегать передачи обратных вызовов вглубь .

Допустим, мы не можем избежать передачи обратных вызовов, тогда самым простым способом было бы использовать обратный вызов дляустановщик состояния: 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>
...