React Hooks迁移

198 阅读5分钟

React Hooks被引入React,使状态和副作用在React Function Components中可用。以前只能在React类组件中实现这些功能;但由于React实现组件的方式多年来有所改变,我们现在可以在React功能组件中用React Hooks实现类组件的功能。

本教程展示了如何用React Hooks将React Class Components写成React Function Components的迁移路径。因此,我们将举例说明如何将类组件中的状态管理和副作用转换为函数组件中使用。

我并不打算鼓励开发者将他们所有的React类组件改写成带有Hooks的React函数组件。相反,本教程应该告诉你如何将类组件实现为具有相同特性的函数组件。从那里,你可以自己决定把所有未来的组件写成带有Hooks的函数组件。

使用React的useState钩子的组件状态

在实现有状态的组件时,React类组件是首选的解决方案。它可以在构造函数中分配初始状态,用给定的this.setState() 方法写入状态--这经常发生在类方法中--以及用this.state 从组件实例中读取状态。

class App extends React.Component {  constructor(props) {    super(props);
    this.state = {      value: '',    };  }
  onChange = event => {    this.setState({ value: event.target.value });  };
  render() {    return (      <div>        <h1>Hello React ES6 Class Component!</h1>
        <input          value={this.state.value}          type="text"          onChange={this.onChange}        />
        <p>{this.state.value}</p>      </div>    );  }}

现在,通过使用一个叫做useState的React钩子,Function Component也能做到这一点。这个钩子让我们分配初始状态(例如一个空字符串),并返回一个数组,其中有状态和一个设置状态的函数。通过使用JavaScript Array Destructuring,我们可以方便地在一行代码中提取钩子的返回值。

const App = () => {  const [value, setValue] = React.useState('');
  const onChange = event => setValue(event.target.value);
  return (    <div>      <h1>Hello React Function Component!</h1>
      <input value={value} type="text" onChange={onChange} />
      <p>{value}</p>    </div>  );};

从本质上讲,React函数组件比React类组件更轻巧。你不需要再处理构造函数或类方法,因为用于状态管理的React Hook可以让你初始化组件的状态,其他功能可以在函数组件中内联定义(例如:onChange() )。

如果你要实现的下一个React组件需要管理状态,不要默认为React类组件,而是给带有React Hooks的React功能组件一个机会。

使用React的useEffect钩子的组件侧面效果

让我们演化一下前面显示的使用副作用的例子。首先,我们将在我们的React类组件中引入副作用,然后展示如何用Hooks在React函数组件中实现它。在我们的例子中,副作用将通过在我们的组件中引入对浏览器本地存储的使用来展示。

class App extends React.Component {  constructor(props) {    super(props);
    this.state = {      value: localStorage.getItem('myValueInLocalStorage') || '',    };  }
  componentDidUpdate() {    localStorage.setItem('myValueInLocalStorage', this.state.value);  }
  onChange = event => {    this.setState({ value: event.target.value });  };
  render() {    return (      <div>        <h1>Hello React ES6 Class Component!</h1>
        <input          value={this.state.value}          type="text"          onChange={this.onChange}        />
        <p>{this.state.value}</p>      </div>    );  }}

现在,每次组件更新时(例如,当状态改变时),状态的值--最初来自输入字段的变化值--都会被存储在浏览器的本地存储中。当应用程序通过刷新浏览器再次启动时,该组件的构造函数会确保从本地存储中获取初始状态。

由于这个组件使用的是本地存储,它从渲染方法中的输出是无法预测的,因为这涉及到一个副作用,即从组件的输入(道具)以外的地方获取信息。

让我们看看如何使用React的useEffect Hook在函数组件中实现相同的功能--将输入字段的值与本地存储同步。

const App = () => {  const [value, setValue] = React.useState(    localStorage.getItem('myValueInLocalStorage') || '',  );
  React.useEffect(() => {    localStorage.setItem('myValueInLocalStorage', value);  }, [value]);
  const onChange = event => setValue(event.target.value);
  return (    <div>      <h1>Hello React Function Component!</h1>
      <input value={value} type="text" onChange={onChange} />
      <p>{value}</p>    </div>  );};

React的useEffect Hook在每次传递的数组(第二个参数)中的一个值发生变化时都会运行。在我们的例子中,每当输入字段的值发生变化时,我们就会更新本地存储。同时,本地存储的值也被用来设置输入字段的初始值。

同样,从本质上讲,函数组件是更轻量级的,因为它可以在其函数体中使用状态和副作用。同时,本地存储的使用也移到了函数体中,而不是像以前那样在不同的类方法中使用。

如果你要实现的下一个React组件必须要有副作用--比如调用浏览器的本地存储--不要默认为React类组件,而是给带有React Hooks的React函数组件一个机会。

使用自定义React Hooks的抽象性

到目前为止,我们看到的所有React Hooks都是由React提供的内置Hooks。然而,将React Hooks组合成新的自定义React Hooks的能力,是为了解决你或其他人的问题,这使得它们成为可重用组件逻辑的完美选择。在我们的案例中,我们可以将所有的状态逻辑和带有本地存储的副作用提取到一个自定义钩子中。

const useStateWithLocalStorage = localStorageKey => {  const [value, setValue] = React.useState(    localStorage.getItem(localStorageKey) || '',  );
  React.useEffect(() => {    localStorage.setItem(localStorageKey, value);  }, [value]);
  return [value, setValue];};
const App = () => {  const [value, setValue] = useStateWithLocalStorage(    'myValueInLocalStorage',  );
  const onChange = event => setValue(event.target.value);
  return (    <div>      <h1>Hello React Function Component!</h1>
      <input value={value} type="text" onChange={onChange} />
      <p>{value}</p>    </div>  );};

useStateWithLocalStorage 钩子允许我们有状态管理,但也可以将状态与浏览器的本地存储同步。每次组件挂载时,如果本地存储有一个值,就会使用本地存储中的状态。

自定义钩子将可重复使用的逻辑完美地放在一个函数中。所有这些逻辑都散落在之前看到的React类组件中,而React Hooks将所有这些碎片放在一起,并将它们封装起来。本来可以用一个高阶组件添加同样的抽象层--在这里演示--但那时的逻辑仍然散落在高阶组件中。


大多数演示的例子都可以在这里找到。我强烈建议你阅读该项目中不同的React组件类型,以便从历史角度更好地了解React的发展。

该教程已经向你展示了如何通过使用Hooks进行状态管理和副作用,将React类组件写成React功能组件。下次你实现一个带有状态或副作用的组件时,请检查你是否可以在React功能组件中用React Hook实现它。React提供了所有的工具来做到这一点。例如,用函数组件中的React Hook来获取数据是一个很好的练习,可以让你熟悉钩子的概念。