React Hooks: доступ к состоянию между функциями без изменения ссылок на функции обработчика событий - PullRequest
0 голосов
/ 08 февраля 2019

В компоненте React на основе классов я делаю что-то вроде этого:

class SomeComponent extends React.Component{
    onChange(ev){
        this.setState({text: ev.currentValue.text});
    }
    transformText(){
        return this.state.text.toUpperCase();
    }
    render(){
        return (
            <input type="text" onChange={this.onChange} value={this.transformText()} />
        );
    }
}

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

Важные моменты, на которые следует обратить внимание:

  1. this.onChange является постоянной ссылкой на ту же функцию.
  2. this.onChange должен иметь возможность доступа к установщику состояния (в данном случае this.setState)

Теперь, если янужно было переписать этот компонент с помощью хуков:

function onChange(setText, ev) {
    setText(ev.currentValue.text);
};

function transformText(text) {
    return text.toUpperCase();
};

function SomeComponent(props) {
    const [text, setText] = useState('');

    return (
        <input type="text" onChange={onChange} value={transformText()} />
    );
}

Теперь проблема в том, что мне нужно передать text в transformText и setText в onChange методы соответственно.Возможные решения, которые я могу придумать:

  1. Определить функции внутри функции компонента и использовать замыкания для передачи значения.
  2. Внутри функции компонента свяжите значение сметоды, а затем используйте связанные методы.

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

Обратите внимание, что это очень упрощенный, надуманный пример.Мой реальный вариант использования довольно сложный, и я абсолютно не хочу перерисовывать компоненты без необходимости.

Редактировать: Это не дубликат Что делать с помощью CallCallback в React? , потому что яЯ пытаюсь выяснить, как добиться эффекта, подобного тому, что раньше делалось с помощью компонента класса, и хотя useCallback обеспечивает способ сделать это, он не идеален с точки зрения обслуживания.

Ответы [ 4 ]

0 голосов
/ 08 февраля 2019

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

Я в основном построил ловушку, которая связывает функцию обратного вызова с заданными аргументами и запоминает ее.Он только повторяет обратный вызов, если заданные аргументы изменяются.

Если кто-то обнаружит необходимость в подобном хуке, я открыл его как отдельный проект.Он доступен на Github и NPM .

0 голосов
/ 08 февраля 2019

Здесь вы можете создать свой собственный хук (Дан Абрамов призвал не использовать термин "Custom Hooks", поскольку это делает создание вашего собственного хука более сложным / более сложным, чем он есть, то есть просто копирование/ вставьте свою логику) извлекая логику преобразования текста

Просто "вырезайте" закомментированный код ниже из ответа Мохамеда .

function SomeComponent(props) {
  // const [text, setText] = React.useState("");

  // const onChange = ev => {
  //   setText(ev.target.value);
  // };

  // function transformText(text) {
  //   return text.toUpperCase();
  // }

  const { onChange, text } = useTransformedText();

  return (
    <input type="text" onChange={React.useCallback(onChange)} value={text} />
  );
}

и вставьте его в новыйфункция (префикс «use *» по соглашению).Назовите возвращаемое состояние и обратный вызов (в виде объекта или массива в зависимости от вашей ситуации)

function useTransformedText(textTransformer = text => text.toUpperCase()) {
  const [text, setText] = React.useState("");

  const onChange = ev => {
    setText(ev.target.value);
  };

  return { onChange, text: textTransformer(text) };
}

Поскольку логику преобразования можно передать (но по умолчанию использует UpperCase), вы можете использовать общую логикуиспользуя свой собственный хук.

function UpperCaseInput(props) {
  const { onChange, text } = useTransformedText();

  return (
    <input type="text" onChange={React.useCallback(onChange)} value={text} />
  );
}

function LowerCaseInput(props) {
  const { onChange, text } = useTransformedText(text => text.toLowerCase());

  return (
    <input type="text" onChange={React.useCallback(onChange)} value={text} />
  );
}

Вы можете использовать вышеуказанные компоненты, как показано ниже.

function App() {
  return (
    <div className="App">
      To Upper case: <UpperCaseInput />
      <br />
      To Lower case: <LowerCaseInput />
    </div>
  );
}

Результат будет выглядеть следующим образом.

result demo

Вы можете запустить рабочий код здесь.
Edit so.answer.54597527

0 голосов
/ 08 февраля 2019

Случай не относится к хукам, он будет таким же для компонента класса и setState в случае, если transformText и onChange должны быть извлечены из класса.Нет необходимости извлекать однострочные функции, поэтому можно предположить, что реальные функции достаточно сложны, чтобы оправдать извлечение.

Прекрасно иметь функцию преобразования, которая принимает значение в качестве аргумента.

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

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

const transformText = text => text.toUpperCase();

const onChange = val => _prevState => ({ text: val });

function SomeComponent(props) {
    const [text, setText] = useState('');

    return (
        <input type="text" onChange={e => setText(onChange(e.currentValue.text)} value={transformText(text)} />
    );
}

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

Способ, специфичный для хуков, заключается в том, что setText может быть передан как обратный вызов, в отличие от this.setState.Так что onChange может быть функцией более высокого порядка:

const transformText = text => text.toUpperCase();

const onChange = setState => e => setState({ text: e.currentValue.text });

function SomeComponent(props) {
    const [text, setText] = useState('');

    return (
        <input type="text" onChange={onChange(setText)} value={transformText(text)} />
    );
}

Если целью является уменьшение повторного рендеринга детей, вызванного изменениями в onChange prop, onChange следует запомнить с useCallbackили useMemo.Это возможно, поскольку useState функция установки не изменяется между обновлениями компонента:

...
function SomeComponent(props) {
    const [text, setText] = useState('');
    const memoizedOnChange = useMemo(() => onChange(setText), []);

    return (
        <input type="text" onChange={memoizedOnChange} value={transformText(text)} />
    );
}

То же самое можно достичь, не извлекая onChange и используя useCallback:

...
function SomeComponent(props) {
    const [text, setText] = useState('');
    const onChange = e => setText({ text: e.currentValue.text });
    const memoizedOnChange = useCallback(onChange, []);

    return (
        <input type="text" onChange={memoizedOnChange} value={transformText(text)} />
    );
}
0 голосов
/ 08 февраля 2019

Определите функции внутри функции компонента и используйте замыкания для передачи значения.Тогда вы ищете useCallback, чтобы избежать ненужных повторных визуализаций.(для этого примера это не очень полезно)

function SomeComponent(props) {
  const [text, setText] = useState('');

  const onChange = (ev)  => {
    setText(ev.target.value);
  };

  function transformText(text) {
    return text.toUpperCase();
  };

  return (
    <input type="text" onChange={useCallback(onChange)} value={transformText(text)} />
  );
}

Подробнее здесь

...