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

介绍

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

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

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

先决条件

要完成本教程,您需要:

不需要本地开发,但提供了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 变量setState()在我们有新值时调用来更新name变量来处理这样的情况:

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;

当用户在输入字段中键入名称并单击“警报”按钮时,它会弹出一个带有 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;

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

考虑这一行:

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

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

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

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

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

第 3 步 – 向具有多个状态属性的类添加钩子

您已经了解了如何使用 来转换一个状态属性useState,但是当您有多个状态属性时,同样的方法就不太适用了。如果,例如,你有两个或多个输入字段userNamefirstName以及lastName,那么你将有三个状态特性基于类的成分:

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

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

为此,您将为输入字段声明一个初始状态值并实现一个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()在五秒后更新为您在方法中定义的值

接下来,您将使用 ReactuseStateuseEffectHooks将此类转换为函数式组件

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 步 – 将钩子添加到具有状态componentDidMount、 和componentDidUpdate

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

为了最好地演示这是如何工作的,让我们修改您的代码以动态更新<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 ComponentWelcome 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 步 — 转换PureComponentReact 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;

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

多个渲染操作的控制台日志输出

大多数情况下,您只想在 state 或 props 发生变化时重新渲染组件。使用上面的示例,您可以改进它,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()

示例ReactMemo.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. 该组件仅在初始渲染之后渲染,并且在 state 或 props 发生变化之前不会再次重新渲染。

结论

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

您还查看了将 ReactPureComponent转换React.memo().

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

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

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

了解更多关于入门阵营挂钩,并建立一个反应待办事项应用与之反应钩

觉得文章有用?

点个广告表达一下你的爱意吧 !😁