繁体   English   中英

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

[英]React Hooks: accessing state across functions without changing event handler function references

在基于类的 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传递给transformTextsetTextonChange方法。 我能想到的可能解决方案是:

  1. 在组件函数内部定义函数,并使用闭包传递值。
  2. 在组件函数内部,将值绑定到方法,然后使用绑定的方法。

执行这些操作中的任何一个都会更改对我需要维护的函数的常量引用,以便不重新渲染input组件。 我如何用钩子做到这一点? 甚至有可能吗?

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

编辑:这不是什么 useCallback 在 React 中做的重复 因为我试图弄清楚如何实现与过去在类组件方式中所做的类似的效果,虽然useCallback提供了一种方法,但它对于可维护性问题并不理想。

在组件函数内定义回调,并使用闭包传递值。 那么您正在寻找的是useCallback钩子,以避免不必要的重新渲染。 (对于这个例子,它不是很有用)

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

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

  const onChange = useCallback((ev)  => {
    setText(ev.target.value);
  }, []);

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

在这里阅读更多

在这里您可以构建自己的钩子(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) };
}

由于可以传递转换逻辑(但默认使用大写),您可以使用自己的钩子使用共享逻辑。

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

结果看起来像这样。

结果演示

您可以在此处运行工作代码。
编辑so.answer.54597527

这种情况不是特定于钩子的,如果应该从类中提取transformTextonChange ,则类组件和setState将相同。 不需要提取一行函数,因此可以假设实际函数足够复杂以证明提取是合理的。

拥有接受值作为参数的转换函数是完全没问题的。

至于事件处理程序,它应该有一个对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没有多大用处。 这也意味着提取是不合理的。

this.setState相比,特定于钩子的一种方式是setText可以作为回调传递。 所以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应该与memoized 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)} />
    );
}

我知道回答我自己的问题是不好的形式,但根据这个回复这个回复,看起来我必须构建自己的自定义钩子来做到这一点。

我基本上构建了一个钩子,它将回调函数与给定的参数绑定并记住它。 如果给定的参数发生变化,它只会重新绑定回调。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM