原文地址:www.twilio.com/blog/react-…
原文作者:www.twilio.com/blog/author…
发布时间:2020年8月18日
在React的世界里,有两种编写React组件的方式。一种是使用函数,另一种是使用类。最近函数式组件变得越来越流行,那么这是为什么呢?
这篇文章将帮助你理解函数式组件和类组件之间的区别,通过示例代码逐一介绍,以便你能深入到现代React的世界中去!
渲染JSX
首先,明显的区别是语法。就像它们的名字一样,功能型组件只是一个返回JSX的普通JavaScript函数。类组件是一个扩展了React.Component的JavaScript类,它有一个渲染方法。有点混乱?让我们看一下一个简单的例子。
import React from "react";
const FunctionalComponent = () => {
return <h1>Hello, world</h1>;
};
正如你所看到的,一个功能组件是一个返回JSX的函数。如果你对ES6中引入的箭头函数不熟悉,你也可以不看下面的例子。
import React from "react";
function FunctionalComponent() {
return <h1>Hello, world</h1>;
}
另一方面,当定义一个类组件时,你必须做一个扩展React.Component的类。要渲染的JSX将在render方法中返回。
import React, { Component } from "react";
class ClassComponent extends Component {
render() {
return <h1>Hello, world</h1>;
}
}
下面是同一个例子,但没有使用析构。如果你不熟悉解构赋值,你可以了解更多关于解构赋值和ES6中引入的箭头函数的信息
import React from "react";
class ClassComponent extends React.Component {
render() {
return <h1>Hello, world</h1>;
}
}
传递道具
传递道具可能会让人困惑,但让我们看看在类和功能组件中是如何写的。假设我们像下面这样传递名字为 "Shiori "的道具。
<Component name="Shiori" />
const FunctionalComponent = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
在一个功能组件中,我们将props作为函数的参数传递。请注意,我们在这里使用的是析构法。另外,我们也可以不这样写。
const FunctionalComponent = (props) => {
return <h1>Hello, {props.name}</h1>;
};
在这种情况下,你必须使用props.name而不是name。
class ClassComponent extends React.Component {
render() {
const { name } = this.props;
return <h1>Hello, { name }</h1>;
}
}
由于它是一个类,你需要用它来引用props。当然,在利用基于类的组件时,我们也可以使用析构法来获得props内部的名称。
处理状态
现在我们都知道,在React项目中我们不能避免处理状态变量。直到最近,处理状态只能在类组件中进行,但从React 16.8开始,React Hook useState被引入,允许开发者编写有状态的功能组件。你可以从官方文档中了解更多关于Hooks的信息。在这里,我们要做一个简单的计数器,从0开始,只要点击一下按钮,计数器就会递增1。
处理功能组件的状态
const FunctionalComponent = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
};
要在一个功能组件中使用状态变量,我们需要使用useState Hook,它需要一个初始状态的参数。在这种情况下,我们从0次点击开始,所以计数的初始状态将是0。
当然,你可以有更多种类的初始状态,包括null,字符串,甚至是对象--任何JavaScript允许的类型。而在左边,由于useState返回的是当前状态和一个更新状态的函数,我们是这样对数组进行解构的。如果你对数组中的两个元素有点迷惑,你可以把它们看作是一个状态和它的设置器。在这个例子中,我们把它们命名为count和setCount,以便于理解这两者之间的联系。
在类组件中处理状态
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>count: {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click
</button>
</div>
);
}
}
这个想法仍然是一样的,但是类组件处理状态的方式有点不同。首先,我们需要了解React.Component构造函数的重要性。下面是官方文档中的定义。
"React组件的构造函数在它被安装之前被调用。当为React.Component子类实现构造函数时,你应该在任何其他语句之前调用super(props)。否则,this.props将在构造函数中未被定义,这可能导致bug。"
基本上,如果不实现构造函数并调用super(props),你试图使用的所有状态变量都将被未定义。所以让我们先定义构造函数。在构造函数中,你将创建一个带有状态键和初始值的状态对象。而在JSX里面,我们使用this.state.count来访问我们在构造函数中定义的状态键的值,以显示计数。Setter几乎是一样的,只是语法不同。
另外,你也可以写一个onClick函数。记住,setState函数需要的参数是state,props(可选),如果需要的话。
onClick={() =>
this.setState((state) => {
return { count: state.count + 1 };
})
}
生命周期方法
最后,让我们来谈谈生命周期的问题。坚持住,我们就快到了! 正如你已经知道的那样,生命周期在渲染的时间上起着重要作用。对于那些从类组件迁移到功能组件的人来说,你一定想知道什么可以取代类组件中的生命周期方法,比如componentDidMount()。是的,有一个钩子可以完美地实现这个目的,让我们来看看吧
安装时(componentDidMount
生命周期方法componentDidMount在第一次渲染完成后就被调用。以前有一个 componentWillMount,发生在第一次渲染之前,但它被认为是传统的,不建议在新版本的React中使用。
const FunctionalComponent = () => {
React.useEffect(() => {
console.log("Hello");
}, []);
return <h1>Hello, World</h1>;
};
取代componentDidMount,我们使用useEffect钩子的第二个参数为[]。useState钩子的第二个参数通常是一个变化的状态数组,useEffect将只在这些选定的变化上被调用。但是当它像这个例子一样是一个空数组时,它将在安装时被调用一次。这是对componentDidMount的一个完美替代。
class ClassComponent extends React.Component {
componentDidMount() {
console.log("Hello");
}
render() {
return <h1>Hello, World</h1>;
}
}
基本上同样的事情发生在这里:componentDidMount是一个生命周期方法,在第一次渲染后被调用一次。
卸载时(componentWillUnmount)
const FunctionalComponent = () => {
React.useEffect(() => {
return () => {
console.log("Bye");
};
}, []);
return <h1>Bye, World</h1>;
};
我很高兴地告诉你,我们也可以使用useState钩子来卸载。但是要注意,语法有点不同。你需要做的是在useEffect函数中返回一个在卸载时运行的函数。这在你需要清理订阅的时候特别有用,比如clearInterval函数,否则会在更大的项目中造成严重的内存泄露。使用useEffect的一个好处是,我们可以在同一个地方编写挂载和卸载的函数。
class ClassComponent extends React.Component {
componentWillUnmount() {
console.log("Bye");
}
render() {
return <h1>Bye, World</h1>;
}
}
总结
两种风格各有利弊,但我想得出的结论是,在可预见的未来,函数式组件将取代现代React。
正如我们在例子中所注意到的,功能型组件写得更短、更简单,这使得它更容易开发、理解和测试。类组件也会因为有这么多的用途而让人感到困惑。使用函数式组件可以很容易地避免这种混乱,并保持一切的干净。
还需要注意的是,React团队正在支持更多的React钩子的功能组件,以取代甚至改进类组件。为了跟进,React团队在早些时候提到,他们将通过避免不必要的检查和内存分配来对功能组件进行性能优化。听起来很有希望,最近为功能组件引入了新的钩子,如useState或useEffect,同时也承诺它们不会淘汰类组件。团队正在寻求在较新的情况下逐步采用带有钩子的功能组件,这意味着没有必要将现有的利用类组件的项目切换到用功能组件进行整个重写,这样就可以保持一致。
同样,React中也有很多有效的编码方式。然而,由于上面列出的那些原因,我更喜欢使用功能组件而不是类组件。我希望这篇文章能帮助你更熟悉现代React。想了解更多,请查看官方文档!你也可以看看我们关于构建Twilio视频应用的文章,看看带钩子的功能组件的实际应用。
Shiori Yamazaki是平台体验团队的一名软件工程实习生。她喜欢开发现代网络应用。你可以通过syamazaki [at] twilio.com或LinkedIn联系她。