如何使用ReactHooks将React类组件转换为函数式组件

来自菜鸟教程
跳转至:导航、​搜索

介绍

React 的最新 alpha 版本引入了一个名为 Hooks 的新概念。 Hooks 被引入 React 以解决 常见问题 。 但是,它们主要用作类的替代品。 使用 Hooks,您可以创建使用状态和生命周期方法的功能组件。

Hooks 目前在 React v16.7.0-alpha 中可用。 没有取消课程的计划。 Hooks 提供了另一种编写 React 的方法。

鉴于 Hooks 仍然是新事物,许多开发人员正在寻求将这一概念应用到他们现有的 React 应用程序或新应用程序中。 在这篇文章中,您将探索使用 React Hooks 将 React 类组件转换为函数式组件的五种方法。

先决条件

要完成本教程,您需要:

  • 熟悉JavaScript。 您可以查看 如何在 JavaScript 系列中编码以了解更多信息并开始使用。
  • 熟悉 React。 您可以查看我们的 How To Code in React.js 系列以获取帮助您入门的指南。

无需本地开发,但提供了 CodeSandbox 示例以供进一步实验。

第 1 步 — 了解没有状态或生命周期方法的类

让我们从一个既没有状态也没有生命周期组件的 React 类开始:

ExampleClassComponent.js

import React, { Component } from 'react';

class App extends Component {
  alertName = () => {
    alert('John Doe');
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <button onClick={this.alertName}>
          Alert
        </button>
      </div>
    );
  }
};

export default App;

这里有一个典型的 React 类,它缺少状态或生命周期方法。 单击按钮时,它会提醒名称。

此类的等效功能如下所示:

ExampleFunctionalComponent.js

import React from 'react';

function App() {
  const alertName = () => {
    alert('John Doe');
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <button onClick={alertName}>
        Alert
      </button>
    </div>
  );
};

export default App;

与第一个示例一样,此功能类以典型方式运行。

然而,这个例子还没有使用 Hooks 或任何新的东西。 在这些示例中,您不需要状态或生命周期。

让我们看一下具有状态的基于类的组件,并学习如何使用 Hooks 将它们转换为函数式组件。

第 2 步 — 将钩子添加到具有状态的类

让我们考虑一种情况,您有一个全局名称变量,您可以在应用程序中从文本输入字段更新该变量。

在 React 中,您可以通过在 state 对象中定义 name 变量并在我们有一个新值来更新 name 变量时调用 setState() 来处理这样的情况:

ExampleClassComponentWithState.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    name: ''
  }

  alertName = () => {
    alert(this.state.name);
  };

  handleNameInput = e => {
    this.setState({ name: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleNameInput}
          value={this.state.name}
          placeholder="Your Name"
        />
        <button onClick={this.alertName}>
          Alert
        </button>
      </div>
    );
  }
}

export default App;

当用户在输入字段中键入名称并单击 Alert 按钮时,它会弹出一个带有在 state 中定义的名称的警报。

您可以使用 Hooks 将整个类转换为功能性 React 组件:

ExampleFunctionalComponentWithState.js

import React, { useState } from 'react';

function App() {
  const [name, setName] = useState('John Doe');

  const alertName = () => {
    alert(name);
  };

  const handleNameInput = e => {
    setName(e.target.value);
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleNameInput}
        value={name}
        placeholder="Your Name"
      />
      <button onClick={alertName}>
        Alert
      </button>
    </div>
  );
};

export default App;

在这里,您介绍了 useState Hook。 它允许您在 React 功能组件中使用状态。 通过 useState() Hook,您可以在此功能组件中使用状态。 它使用与数组解构赋值类似的语法。

考虑这一行:

const [name, setName] = useState('John Doe')

这里,name相当于普通类组件中的this.statesetName相当于this.setState

useState() Hook 中状态的初始值来自一个参数。 换句话说,useState() 参数是状态的初始值。 在您的情况下,您将其设置为 'John Doe'。 这意味着状态中名称的初始状态是'John Doe'

此代码是一个示例,说明如何使用 Hooks 将具有状态的基于类的 React 组件转换为功能组件。

让我们探索其他场景,包括具有多个状态属性的类。

第 3 步 — 向具有多个状态属性的类添加 Hook

您已经了解了如何使用 useState 转换一个状态属性,但是当您有多个状态属性时,相同的方法就不太适用了。 例如,如果您有两个或多个用于 userNamefirstNamelastName 的输入字段,那么您将拥有一个具有三个状态属性的基于类的组件:

ExampleClassComponentWithMultipleStateProperties.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    userName: '',
    firstName: '',
    lastName: ''
  };

  logName = () => {
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your Username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your First Name"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your Last Name"
        />
        <button
          className="btn btn-large right"
          onClick={this.logName}
        >
          Log Names
        </button>
      </div>
    );
  }
}

export default App;

要使用 Hooks 将此类转换为功能组件,您将不得不采取一些非常规的路线。 使用 useState() Hook,上面的例子可以写成:

ExampleFunctionalComponentWithMultipleStateProperties.js

import React, { useState } from 'react';

function App() {
  const [userName, setUsername] = useState('');
  const [firstName, setFirstname] = useState('');
  const [lastName, setLastname] = useState('');

  const logName = () => {
    console.log(userName);
    console.log(firstName);
    console.log(lastName);
  };

  const handleUserNameInput = e => {
    setUsername(e.target.value);
  };
  const handleFirstNameInput = e => {
    setFirstname(e.target.value);
  };
  const handleLastNameInput = e => {
    setLastname(e.target.value);
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="Your Username"
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="Your First Name"
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="Your Last Name"
      />
      <button
        className="btn btn-large right"
        onClick={logName}
      >
        Log Names
      </button>
    </div>
  );
};

export default App;

这是此示例的 CodeSandbox

这演示了如何使用 useState() Hook 将具有多个状态属性的基于类的组件转换为功能组件。

第 4 步 — 将钩子添加到具有状态和 componentDidMount 的类

让我们考虑一个具有 statecomponentDidMount 的类。 为了演示,您将看到一个场景,您为三个输入字段设置初始状态,并在五秒后将它们全部更新为一组不同的值。

为此,您将为输入字段声明初始状态值并实现 componentDidMount() 生命周期方法,该方法将在初始渲染后运行以更新状态值:

ExampleClassComponentWithStateAndComponentDidMount.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    // initial state
    userName: 'johndoe',
    firstName: 'John',
    lastName: 'Doe'
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        // update state
        userName: 'janedoe',
        firstName: 'Jane',
        lastName: 'Doe'
      });
    }, 5000);
  }

  logName = () => {
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your Username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your First Name"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your Last Name"
        />
        <button
          className="btn btn-large right"
          onClick={this.logName}
        >
          Log Names
        </button>
      </div>
    );
  }
}

export default App;

当应用程序运行时,输入字段将具有您在状态对象中定义的初始值。 这些值将在五秒后更新为您在 componentDidMount() 方法中定义的值。

接下来,您将使用 React useStateuseEffect Hooks 将此类转换为功能组件:

ExampleFunctionalComponentWithStateAndComponentDidMount.js

import React, { useState, useEffect } from 'react';

function App() {
  const [userName, setUsername] = useState('johndoe');
  const [firstName, setFirstname] = useState('John');
  const [lastName, setLastname] = useState('Doe');

  useEffect(() => {
    setInterval(() => {
      setUsername('janedoe');
      setFirstname('Jane');
      setLastname('Doe');
    }, 5000);
  });

  const logName = () => {
    console.log(userName);
    console.log(firstName);
    console.log(lastName);
  };

  const handleUserNameInput = e => {
    setUsername({ userName: e.target.value });
  };
  const handleFirstNameInput = e => {
    setFirstname({ firstName: e.target.value });
  };
  const handleLastNameInput = e => {
    setLastname({ lastName: e.target.value });
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="Your Username"
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="Your First Name"
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="Your Last Name"
      />
      <button
        className="btn btn-large right"
        onClick={logName}
      >
        Log Names
      </button>
    </div>
  );
};

export default App;

这是此示例的 CodeSandbox

在功能方面,该组件的功能与前面的示例完全相同。 唯一的区别是,您没有像在类组件中那样使用传统的 state 对象和 componentDidMount() 生命周期方法,而是使用了 useStateuseEffect挂钩。

第 5 步 — 将钩子添加到具有状态、componentDidMountcomponentDidUpdate 的类

接下来,让我们看一个具有状态和两个生命周期方法的 React 类:componentDidMountcomponentDidUpdate。 到目前为止,大多数解决方案都使用了 useState Hook。 在本例中,您将关注 useEffect 钩子。

为了最好地演示这是如何工作的,让我们修改您的代码以动态更新页面上的 <h3> 标头。

目前,标题显示 This is a Class Component。 现在,您将定义一个 componentDidMount() 方法以在三秒后更新标题为 Welcome to React Hooks

ExampleClassComponentWithStateAndTwoLifecycleMethods.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    header: 'Welcome to React Hooks'
  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  render() {
    return (
      <div>
        <h3 id="header">This is a Class Component</h3>
      </div>
    );
  }
}

export default App;

当应用程序运行时,它将以初始标题 This is a Class Component 开始,并在三秒后将其更改为 Welcome to React Hooks。 这是经典的 componentDidMount() 行为,因为它在 render 函数成功执行后运行。

让我们添加从另一个输入字段动态更新标题的功能,以便在您键入时使用新文本更新标题。

为此,您需要实现 componentDidUpdate() 生命周期方法:

ExampleClassComponent.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    header: 'Welcome to React Hooks'
  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  componentDidUpdate() {
    const node = document.querySelectorAll('#header')[0];
    node.innerHTML = this.state.header;
  }

  handleHeaderInput = e => {
    this.setState({ header: e.target.value });
  };

  render() {
    return (
      <div>
        <h3 id="header">This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleHeaderInput}
          value={this.state.header}
        />
      </div>
    );
  }
}

export default App;

在这里,您有 statecomponentDidMount()componentDidUpdate()。 当您运行应用程序时,componentDidMount() 函数将在三秒后将标题更新为 Welcome to React Hooks。 当您开始在标题文本输入字段中输入时,<h3> 文本将使用 componentDidUpdate() 方法中定义的输入文本进行更新。

接下来,您将使用 useEffect() Hook 将此类转换为功能组件:

ExampleFunctionalComponentWithStateAndTwoLifecycleMethods.js

import React, { useState, useEffect } from 'react';

function App() {
  const [header, setHeader] = useState('Welcome to React Hooks');

  useEffect(() => {
    const newheader = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      newheader.innerHTML = header;
    }, 3000);
  });

  const handleHeaderInput = e => {
    setHeader(e.target.value);
  };

  return (
    <div>
      <h3 id="header">This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleHeaderInput}
        value={header}
      />
    </div>
  );
};

export default App;

CodeSandbox 上查看此示例。

您使用此组件实现了与之前使用 useEffect() Hook 相同的功能。 您还优化了代码,因为您不必为 componentDidMount()componentDidUpdate() 函数编写单独的代码。 使用 useEffect() Hook,您可以获得两者的功能。 这是因为 useEffect() 默认在初始渲染后和每次后续更新后都会运行。

第 6 步 — 将 PureComponent 转换为 React memo

React PureComponent 的工作方式与 Component 类似。 它们之间的主要区别在于 React.Component 没有实现 shouldComponentUpdate() 生命周期方法,而 React.PureComponent 实现了。

如果您有一个应用程序,其中 render() 函数在给定相同的道具和状态的情况下呈现相同的结果,您可以在某些情况下使用 React.PureComponent 来提高性能。

React.memo() 以类似的方式工作。 当你的函数组件在给定相同的 props 的情况下呈现相同的结果时,你可以将它包装在对 React.memo() 的调用中以提高性能。 使用 PureComponentReact.memo() 可以显着提高 React 应用程序的性能,因为它减少了应用程序中的渲染操作数量。

要了解它们的作用,您将首先查看组件每两秒渲染一次的代码,无论值或状态是否发生变化:

ExampleClassComponent.js

import React, { Component } from 'react';

function Unstable(props) {
  // monitor how many times this component is rendered
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
};

class App extends Component {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}
export default App;

当您运行应用程序并检查日志时,您会注意到它每两秒渲染一次组件,状态或道具没有任何变化。 这种情况可以通过 PureComponentReact.memo() 来改善。

大多数时候,您只想在状态或道具发生变化时重新渲染组件。 使用上面的示例,您可以使用 PureComponent 对其进行改进,以便组件仅在状态或道具发生变化时重新渲染。

您可以通过导入 PureComponent 并扩展它来完成此操作:

ExamplePureComponent.js

import React, { PureComponent } from 'react';

function Unstable(props) {
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
};

class App extends PureComponent {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}

export default App;

现在,如果您再次运行该应用程序,您只会获得初始渲染。 之后没有其他事情发生。 这是因为你有 class App extends PureComponent {} 而不是 class App extends Component {}

这解决了组件被重新渲染而不考虑当前状态的问题。 但是,如果您在 setState 方法中实现状态更改,则会遇到另一个问题。

例如,考虑对 setState() 进行以下更改:

目前,value 设置为 1

componentDidMount() {
  setInterval(() => {
    this.setState(() => {
      return { value: 1 };
    });
  }, 2000);
}

让我们考虑将 value 设置为 Math.random() 的情况:

componentDidMount() {
  setInterval(() => {
    this.setState(() => {
      return { value: Math.round(Math.random()) };
    });
  }, 2000);
}

在这种情况下,第一个示例组件将在每次值更新为下一个随机数时重新渲染。 但是,PureComponent 可以仅在状态或道具发生变化时重新渲染组件。

现在您可以探索如何使用 React.memo() 来实现相同的修复。 为此,请使用 React.memo() 包装组件:

ExampleReactMemo.js

import React, { Component } from 'react';

const Unstable = React.memo(function Unstable (props) {
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
});

class App extends Component {
  state = {
    val: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable val={this.state.val} />
      </div>
    );
  }
}

export default App;

这是此示例的 CodeSandbox

这实现了与使用 PureComponent 相同的结果。 该组件仅在初始渲染后渲染,并且在状态或道具发生变化之前不会再次重新渲染。

结论

在本教程中,您探索了几种使用 React Hooks 将现有的基于类的组件转换为功能组件的方法。

您还查看了将 React PureComponent 类转换为 React.memo() 的特殊情况。

要在您的应用程序中使用 Hooks,请务必将您的 React 版本更新到支持的版本:

"react": "^16.7.0-alpha",
"react-dom": "^16.7.0-alpha",

你现在有了进一步试验 React Hooks 的基础。

详细了解 React Hooks 入门使用 React Hooks 构建 React To-Do 应用程序