Как вернуть запомненный обратный вызов React из заводской функции - PullRequest
1 голос
/ 28 апреля 2020

Мне интересно, как лучше вернуть запомненную функцию обратного вызова в React при использовании фабрики для генерации этого обратного вызова. Цель состоит в том, чтобы вернуть тот же экземпляр функции, когда фабрика вызывается с тем же параметром.

Например:

function MyComponent() {
    // This will always return a new function
    const makeCallback = param => () => {
        /* perform action with 'param' */
    };

    return (
        <>
            <Button onClick={makeCallback('foo')} />
            <Button onClick={makeCallback('bar')} />
            <Button onClick={makeCallback('baz')} />
        </>
    );

Я не верю, что обертывание самой фабрики с помощью useCallback предоставить какую-либо выгоду, так как эта функция фактически не передается ни одному из дочерних элементов, поэтому я хотел вернуть функцию useCallback с фабрики.

Например:

const makeCallback = param => React.useCallback(
    () => {
        /* perform action with 'param' */
    },
    [param]
);

Однако , это было недопустимо и не удалось во время сборки.

React Hook "React.useCallback" is called in function "makeCallback" which is neither a React function component or a custom React Hook function - react-hooks/rules-of-hooks

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

Мой лучший вариант - превратить мою фабрику в ловушку и явно вызвать ее сверху уровень для каждого случая? Я бы предпочел простоту построения обратного вызова в самой кнопке, так как она немного меньше печатает, а часть параметра более очевидна и очевидна, если удерживать ее нажатой.

// Same factory function, but now it's magically a "hook"
const useCallbackFactory = param => {
    return React.useCallback(() => { /* do 'param' stuff */ }, [param]);
}

function MyComponent() {
    // Define the callbacks ahead of time
    const fooCb = useCallbackFactory('foo');
    const barCb = useCallbackFactory('bar');
    const bazCb = useCallbackFactory('baz');

    return (
        <>
            <Button onClick={fooCb} />
            <Button onClick={barCb} />
            <Button onClick={bazCb} />
        </>
    );
}

Ответы [ 4 ]

1 голос
/ 28 апреля 2020

Один из возможных способов сделать это - создать собственный хук: (Не воспринимайте этот пример слишком серьезно, просто чтобы подчеркнуть).

const useFactory = (type) => {
    let f;
    if (type === 'a') {
    f = () => console.log('hello world');
  } else {
    f = () => console.log('bye world');
  }

  return React.useCallback(f, [type]);
};

const Button = React.memo(({ onClick, children }) => {
    console.log('re render');
    return <button onClick={onClick}>{children}</button>;
});

const App = () => {
  const f1 = useFactory('a');
  const f2 = useFactory('b');

  return (
    <div>
      <Button onClick={f1}>F1</Button>
      <Button onClick={f2}>F1</Button>
    </div>  
  );

};

https://jsfiddle.net/kadoshms/740zkeso/9/

0 голосов
/ 28 апреля 2020

Если обратные вызовы должны оставаться одинаковыми между различными «экземплярами» компонента и перемонтированием компонента, может иметь смысл объявить их заранее вне области действия компонента.

const cb1 = params => { ... };
const cb2 = params => { ... };

const App = () => (
  <div>
    <Button onClick={cb1}>F1</Button>
    <Button onClick={cb2}>F2</Button>
  </div>  
);

Это не очень удобен, поэтому может иметь смысл запомнить заводские результаты и использовать их повторно.

const makeMemoizedCallback = factory => {
  let cache = {};
  return (...args) => {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = factory(...args);
    }
    return cache[key];
  };
};

const makeCallback = makeMemoizedCallback(param => runtime => {
  console.log(param, runtime);
});

console.log(makeCallback("a") === makeCallback("a"));
makeCallback("a")(1);
makeCallback("b")(2);

Использование:

const makeButtonClickHandler = makeMemoizedCallback(param => e => {
  console.log(param, e);
});

const App = () => (
  <div>
    <Button onClick={makeButtonClickHandler("F1")}>F1</Button>
    <Button onClick={makeButtonClickHandler("F2")}>F1</Button>
  </div>
);
0 голосов
/ 28 апреля 2020

Я думаю, что вы близки к useCallback, но вы действительно хотите запомнить результат фабрики. Хук useMemo может сделать это.

const callback = useMemo(() => {
  return callbackFactory(param);
}, [param]);

Хук не пересчитает свое возвращаемое значение (то есть обратный вызов), пока не изменится param в массиве зависимостей.

Демо

const callbackFactory = param => {
  console.log("invoked callback factory!!");
  switch (param) {
    case "a":
      return () => console.log("callback A");

    case "b":
      return () => console.log("callback B");

    default:
      return () => {};
  }
};

export default function App() {
  const [param, setParam] = useState("a");
  const [, setCount] = useState(0);

const callback = useMemo(() => {
  return callbackFactory(param);
}, [param]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>

      <select defaultValue="a" onChange={e => setParam(e.target.value)}>
        {["a", "b"].map(param => (
          <option key={param} value={param}>
            {param}
          </option>
        ))}
      </select>

      <button type="button" onClick={callback}>
        Press Me
      </button>
      <button type="button" onClick={() => setCount(c => c + 1)}>
        Rerender!
      </button>
    </div>
  );
}

Edit memoized callbacks from factory

0 голосов
/ 28 апреля 2020

Цель состоит в том, чтобы вернуть тот же экземпляр функции, когда фабрика вызывается с тем же параметром.

Я думаю, что вы хотите что-то вроде этого:

 let memoizedcb = React.useCallback(
    memoize((fieldName) => (val) => doSomething(fieldName, val)),
    []
  );

где

import memoize from "fast-memoize";

Теперь функция, возвращаемая memoizedcb, будет одинаковой для всех рендеров для одного и того же аргумента.

Теперь вы можете использовать ее как

<TextField onChange={memoizedcb("name")} value={name}/>
<TextField onChange={memoizedcb("surname")} value={surname}}/>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...