与React类组件相比,React函数式组件究竟有何不同呢?之前听到最典型的回答就是:类组件提供了更多特性(如:State)。当然随着 hooks
出现,事实早非如此
举个例子
相信很多人都看过一个非常经典的例子:
这是 react
函数组件的实现
function ProfilePage(props) {
const showMessage = () => {
alert('您已成功关注' + props.user);
};
// 用setTimeout 模拟网络请求
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Follow</button>
);
}
这是 React
类组件的实现
class ProfilePage extends React.Component {
showMessage = () => {
alert('您已成功关注' + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
不知道您是否能看出两个实现之间的差异。你可能就认为这是一个很简单的代码重构,但你是否发现其中的问题所在。
UI界面大致如下
当我选择了小红进入小红的主页后,关注了她。由于网络延迟,3s内我又选择了小明的主页。而代码上都触发了 showMessage ()
。但是这时候你会发现,两个组件呈现的效果是不一样的。
如果你有兴趣可以在 SandBox 中尝试。
结果:
- 当使用 函数式组件 实现的 ProfilePage, 当前账号是 小明 时点击 关注ta 按钮,然后立马切换当前账号到 小红,弹出的文本将依旧是 小明。
- 当使用 类组件 实现的 ProfilePage, 弹出的文本将是 小红:
原因分析
在这里例子中,我们认为函数式组件实现的是期待的效果,因为关注的事件行为不应该因为切换主页的行为而发生改变。
为什么例子中类组件会出现这样的现象呢?
我们看看类组件中的 showMessage
方法中,这个类方法从 this.props.user
中读取数据。在 React
中 Props
是不可变(immutable
)的,所以他们永远不会改变。然而,this
是,而且永远是,可变(mutable
)的。事实上,这就是类组件 this 存在的意义。React本身会随着时间的推移而改变,以便你可以在渲染方法以及生命周期方法中得到最新的实例。
所以如果在请求已经发出的情况下我们的组件进行了重新渲染,this.props
将会改变。showMessage
方法从一个“过于新”的 props
中得到了user
。
这暴露了一个关于用户界面性质的一个有趣观察。如果我们说UI在概念上是当前应用状态的一个函数,那么事件处理程序则是渲染结果的一部分 —— 就像视觉输出一样。我们的事件处理程序“属于”一个拥有特定 props
和 state
的特定渲染。然而,调用一个回调函数读取 this.props
的 timeout
会打断这种关联。我们的 showMessage
回调并没有与任何一个特定的渲染“绑定”在一起,所以它“失去”了正确的 props
。从 this
中读取数据的这种行为,切断了这种联系。
假设函数式组件不存在。我们将如何解决这个问题?
我们想要以某种方式“修复”拥有正确 props
的渲染与读取这些 props
的 showMessage
回调之间的联系。在某个地方 props
被弄丢了。一种方法是在调用事件之前读取 this.props
,然后将他们显式地传递到 timeout
回调函数中去:
class ProfilePage extends React.Component {
showMessage = (user) => {
alert('Followed ' + user);
};
handleClick = () => {
const {user} = this.props;
setTimeout(() => this.showMessage(user), 3000);
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
这种方法 会起作用。然而,这种方法使得代码明显变得更加冗长,并且随着时间推移容易出错。如果我们需要的不止是一个props怎么办?如果我们还需要访问state怎么办?如果 showMessage 调用了另一个方法,然后那个方法中读取了 this.props.xxx
或者 this.state.xxx
,我们又将遇到同样的问题。然后我们不得不将 this.props
和 this.state
以函数参数的形式在被 showMessage
调用的每个方法中一路传递下去。这个问题并不是React所独有的 —— 你可以在任何一个将数据放入类似 this 这样的可变对象中的UI库中重现它。
总结
从组件的角度上来看,类组件和函数组件在使用方式上和呈现上是没有任何不同的,而在现代浏览器中他们的性能差异也是可以忽略不计的。
但在开发模式和思维上,两者存是存在差异的。
类组组件要是面向对象编程,主打的是继承,生命周期等核心概念等特点
函数组件主要是函数式编程,主打的是immutable,无副作用,引用透明等特点
因为两者主打的特点不一样,所以在使用场景上自然就存在一些差异了:
如果组件本身依赖生命周期,设计上也有继承的需要,那自然选择类组件会更合适一点
由于Ract hooks的推出,淡化生命周期的概念,函数组件可以完全取代函数组件,而且官方也推崇“组合优于继承”的设计概念,所以类组件的优势也在慢慢的淡出。
性能优化上,类组件主要依靠shouldComponentUpdate生命周期来阻断渲染,从而提升性能,而函数组件是依靠React.memo缓存渲染结果来提升性能。
从上手程度而言,类组件更容易上手,而从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。