React 函数组件和类组件的区别
函数组件和类组件有什么不同,在编码过程中应该如何选择呢?
一、什么是函数组件
定义一个组件最简单的方式就是使用 JavaScript
函数:
import React from 'react'
const Welcome = (props) => {
return <h1>welcome, {props.name}</h1>
}
export default Welcome
这个函数接收一个 props
对象并返回一个 react
元素
二、什么是类组件
React 可以使用 ES6 class
语法去写一个组件:
import React from 'react'
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return <h1>welcome, {this.props.name}</h1>
}
}
export default Welcome
这两个版本是等价的,它们具有相同的输出。那么我们应该去选择哪一种实现方式呢?
三、函数组件与类组件的区别
1、语法上
两者最明显的不同就是在语法上:
函数组件是一个纯函数,它接收一个 props
对象返回一个 react
元素;
类组件需要去继承 React.Component
并且创建 render
函数返回 react
元素,虽然实现的效果相同,但需要更多的代码。
2、状态管理
因为函数组件是一个纯函数,所以不能在组件中使用 setState()
,这也是为什么把函数组件称作为无状态组件。
如果要在组件中使用 state
,可以选择创建一个类组件或者将 state
提升到你的父组件中,然后通过 props
对象传递到子组件。
3、生命周期钩子
函数组件中不能使用生命周期钩子,原因和不能使用 state
一样,所有的生命周期钩子都来自于继承的 React.Component
中。
因此,如果要使用生命周期钩子,就需要使用类组件。
注意:在
react16.8
版本中添加了hooks
,使得我们可以在函数组件中使用useState
钩子去管理state
,使用useEffect
钩子去使用生命周期函数。
因此,2、3 两点就不是它们的区别点。
而从这个改版中我们也可以看出 React 团队更看重函数组件,而且曾提及到在react
之后的版本将会对函数组件的性能方面进行提升。
4、调用方式
如果 SayHi
是一个函数,React
需要调用它:
// 你的代码
function SayHi() {
return <p>Hello, React</p>
}
// React 内部
const result = SayHi(props) // » <p>Hello, React</p>
如果 SayHi
是一个类,React
需要先用 new
操作符将其实例化,然后调用刚才生成实例的 render
方法:
// 你的代码
class SayHi extends React.Component {
render() {
return <p>Hello, React</p>
}
}
// React 内部
const instance = new SayHi(props) // » SayHi {}
const result = instance.render() // » <p>Hello, React</p>
可想而知,函数组件重新渲染将重新调用组件方法返回新的 react
元素,类组件重新渲染将 new
一个新的组件实例,然后调用 render
类方法返回 react
元素,这也说明为什么类组件中 this
是可变的。
5、获取渲染时的值
这一点是他们最大差异,但又常常被人们忽略。
考虑以下组件:
function ProfilePage(props) {
const showMessage = () => {
alert('Followed ' + props.user);
}
const handleClick = () => {
setTimeout(showMessage, 3000);
}
return (
<button onClick={handleClick}>Follow</button>
)
}
UserProfile
组件很简单,就一个 Follow
按钮,该按钮使用了 setTimeout
模拟网络请求。用户点击这个按钮之后会弹出一个警告框。如果 props.user
为 'Dan'
,它将在三秒钟后显示 'Followed Dan'
。
我们如何将其编写为类?天真的翻译可能像这样:
class ProfilePage extends React.Component {
showMessage() {
alert('Followed ' + this.props.user);
}
handleClick() {
setTimeout(this.showMessage.bind(this), 3000);
}
render() {
return <button onClick={this.handleClick.bind(this)}>Follow</button>
}
}
通常认为这两个代码段是等效的。人们经常在这些模式之间自由重构,而没有注意到它们的含义
但是,这两个代码段是完全不同的。
分别按下面的顺序来操作 Follow
按钮:
- 先点击
Follow
按钮 - 在
3s
之前更改下拉选择项的选项 - 阅读弹出的警告框内容
这就发现函数组件和类组件是有区别的:
函数组件:按上面所列的三个步骤操作时,当用户在 3s
前更改下拉选择框的选项时,h1
的用户名会立马改变,而 3s
后弹出的警告框中的用户名并不会改变
类组件:按上面所列的三个步骤操作时,当用户在 3s
前更改下拉选择框的选项时,h1
中的用户名会立马改变,而 3s
后弹出的警告框中的用户名也会改变
那么,为什么我们的类示例会这样表现呢?
让我们仔细看一下 showMessage
类中的方法:
showMessage() {
alert('Followed ' + this.props.user);
}
在 showMessage
方法中读取了 this.props.user
(也是我们要输出的用户名称)。而 React
中的 props
是不可变的,但是 this
是可变的,而且是一直是可变的。这也是类组件中 this
的目的。React
自身会随着时间的推移对 this
进行修改,以便在 render
函数或生命周期中读取新的版本。
因此,如果组件在请求重新渲染时,this.props
将会改变。showMessage
方法会从新的 props
中读取 user
。所看到的效果也正是因为这个原因。
在 React
中的组件,UI
在概念上可以理解是程序当前状态的函数,那么事件处理就是让 UI
的渲染结果一部分一部分可视化输出。我们的事件处理程序属于具有特定 props
和 state
的特定渲染。但是,当回调超时的话,this.props
就会打破这种联系。示例中的 showMessage
方法在回调时没有绑定到任何特定的渲染,因此它会丢失真正的 props
。
那么有没有一种较好的方式可以使用正确的 props
来修复 render
和 showMessage
回调之间的联系呢?
我们可以在事件发生的早期,将 this.props
传递给超时完成的处理程序来尝试着解决这个问题。这种解决方式属于闭包的范畴。
class ProfilePage extends React.Component {
showMessage(user) {
alert('Followed ' + user);
}
handleClick() {
cosnt {user} = this.props
setTimeout(this.showMessage.bind(this, user), 3000);
}
render() {
return <button onClick={this.handleClick.bind(this)}>Follow</button>
}
}
我们使用闭包机制将上一状态的值保存下来待 showMessage
方法调用。即使 this.props
发生变化,但并不改变 user
这种方法虽然解决我们前面所提到的问题,但是这种方法代码会随着 props
的个数增加,代码也会变得更加冗余也易于出错。
如果我们也需要访问 state
。如果 showMessage
调用另一个方法,该方法会读取 this.props.something
或 this.state.something
。
我们又会碰到同样的问题。所以我们必须通过 this.props
作为 showMessage
的参数来修复它们之间存在的问题。
但这么做会破坏类提供的特性,也令人难于记住或执行。
另外,在 handleClick
中内联 alert
中的代码并不能解决更大的问题。
我们希望以一种允许代码分解成更多方法的方式来构造代码,同时还可以读取与其相关的 render
所对应的 props
和 state
。
或许,我们可以在类的构造函数中绑定这些方法:
class ProfilePage extends React.Component {
render() {
// 获取 props
cosnt props = this.props
// 它们不是类方法
const showMessage = () => {
alert('Followed ' + props.user);
}
const handleClick = () => {
setTimeout(showMessage, 3000)
}
return <button onClick={handleClick}>Follow</button>
}
}
这样一来,函数组件和类组件所达到的效果都一样了。在类组件中可以捕获渲染时的 props
。效果上看上去是一样了,但看起来怪怪的。如果在类组件中的 render
中定义函数而不是使用类方法,那么还有使用类的必要性?