React Hooks:在不更改事件处理程序函数引用的情况下跨函数访问状态

时间:2019-02-08 17:31:16

标签: javascript reactjs react-hooks

在基于类的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. 在component函数内部,将值绑定到方法,然后使用绑定的方法。

执行上述任一操作都会更改对我需要维护的功能的常量引用,以便不重新呈现input组件。我该如何使用挂钩?甚至有可能吗?

请注意,这是一个非常简化的人为示例。我的实际用例非常复杂,我绝对不想不必要地重新渲染组件。

编辑: 这不是What useCallback do in React?的副本,因为我试图弄清楚如何实现与以前使用类组件方式实现的效果类似的功能,而useCallback提供了 a < / em>的方式,对于可维护性问题而言并不理想。

4 个答案:

答案 0 :(得分:5)

定义组件函数内部的函数,并使用闭包传递值。然后,您正在寻找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)} />
  );
}

了解更多here

答案 1 :(得分:2)

您可以在这里build your own hook(Dan Abramov敦促您不要使用“自定义钩子”一词,因为它会使创建自己的钩子比原来更难/更高级,这只是复制/粘贴您的逻辑)提取文本转换逻辑

只需从Mohamed's answer中“剪切”下面的注释代码即可。

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

答案 2 :(得分:0)

这种情况并非特定于钩子,对于类组件和setState来说也是一样,如果应该从类中提取transformTextonChange。不需要提取单行函数,因此可以假定实际函数足够复杂以证明提取正确。

具有接受一个值作为参数的转换函数是非常好的。

对于事件处理程序,它应该引用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道具的更改引起的孩子的遗弃,onChange应该用useCallbackuseMemo来记住。之所以可以这样做是因为useState setter函数在组件更新之间不会更改:

...
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)} />
    );
}

答案 3 :(得分:0)

我知道回答我自己的问题是一种不好的形式,但是基于this replythis reply,看来我必须构建自己的自定义钩子才能做到这一点。

我基本上已经构建了一个钩子,该钩子将回调函数与给定参数绑定在一起并进行记忆。仅当给定参数更改时,它才会重新绑定回调。

如果有人发现需要类似的钩子,我将其开源为一个单独的项目。它在GithubNPM上可用。