随着不久前 React@16.8.0 的发布,Hooks 也算是正式来到了幕前。个人觉得对React来说,Hooks 可以算是一个里程碑式的功能。本文不会详细介绍 Hooks,如果对 Hooks 还不是很了解的,可以先看下 Introducing Hooks。
Hooks 带来了什么
简单来说 Hooks 带来了可以在 Function Components 使用 state 和其它 React 特性的能力。而这带来的第一个很直观的好处就是原来写的 Function Components 因为需求变动需要增加 state 时,再也不需要重构成 Class Components 了(重构不再是火葬场了)。
个人认为 Hooks 带来的最大益处就是能够帮我们更好地组织业务代码,提高代码复用率。我们通过一个例子来说明,假设有这么一个需求:「组件需要根据浏览器宽度的变化,来显示不同的内容」。
我们先用 Class Component 来实现:
export default class ResizeClassComponent extends React.Component {
constructor(props) {
super(props);
this.resizeHandler = null;
this.state = {
width: window.innerWidth,
}
}
componentDidMount() {
this.resizeHandler = () => {
this.setState({
width: window.innerWidth
})
}
window.addEventListener('resize', this.resizeHandler);
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeHandler);
}
render() {
const { width } = this.state;
return (
<div>width: {width}</div>
)
}
}
复制代码
目前来看没什么问题,但是如果说现在又有一个组件需要实现这个功能,一样的代码再写一遍?那如果有很多组件都需要这个功能呢?每次都写一遍肯定不现实,有人会说用render props
或者高阶组件
来处理这个问题,那就让我们来实现一下:
// render props
class ResizeRenderProps extends React.Component {
constructor(props) {
super(props);
this.resizeHandler = null;
this.state = {
width: window.innerWidth,
}
}
componentDidMount() {
this.resizeHandler = () => {
this.setState({
width: window.innerWidth
})
}
window.addEventListener('resize', this.resizeHandler);
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeHandler);
}
render() {
return this.props.children(this.state.width);
}
}
export default class ResizeClassComponent extends React.Component {
render() {
return (
<ResizeRenderProps>
{width => <div>width: {width}</div>}
</ResizeRenderProps>
)
}
}
复制代码
// 高阶组件
function withResize(WrappedComponent) {
return class ResizeHoc extends React.Component {
constructor(props) {
super(props);
this.resizeHandler = null;
this.state = {
width: window.innerWidth,
}
}
componentDidMount() {
this.resizeHandler = () => {
this.setState({
width: window.innerWidth
})
}
window.addEventListener('resize', this.resizeHandler);
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeHandler);
}
render() {
return <WrappedComponent width={this.state.width} />;
}
}
}
export default withResize(class ResizeClassComponent extends React.Component {
render() {
return (
<div>width: {this.props.width}</div>
)
}
})
复制代码
虽然实现了功能,但是两种方式都不可避免地对原来组件做了重构,并且增加了一个层级,如果用的多了就会导致「wrapper hell」,抛开这个不谈,render props
和高阶组件
本身自带的复杂度就不够友好。
那么 Hooks 是怎么解决的呢?
import React, { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const resizeHandler = () => setWidth(window.innerWidth);
window.addEventListener('resize', resizeHandler);
return () => window.removeEventListener('resize', resizeHandler)
})
return width;
}
export default function ResizeHooks() {
const width = useWindowWidth();
return (
<div>width: {width}</div>
)
}
复制代码
是不是感觉简单又清楚?只需要自定义一个 Hook(其实也就是一个普通的函数啦,只不过用了内置的几个Hook),使用的时候就像调用一个普通的函数一样,没有复杂的概念需要理解。正如前面所说,Hooks 可以帮助我们组织代码,让相关性的代码都在一块,而不是像在 Class Components 里散落在各个生命周期函数中,贴个图方便理解:
图片来源:twitter.com/prchdk/stat…
从图中可以发现逻辑相关的代码都被单独抽成了一个个 Hook,这意味着这部分有状态的逻辑(stateful logic)可以被复用,而且更容易理解。
稍微解释下什么叫「有状态的逻辑(stateful logic)」,这里的状态指的就是 React 的 state,对应的无状态的逻辑就是普通的函数啦。在 Hooks 之前,如果要复用这种有状态的逻辑,除了上述的 render props
和高阶组件
之外就没有别的更简单直接的办法了。而有了 Hooks 之后,这部分逻辑能够被单独抽离出,职责更明确,从而使复用更加简单,代码更加清晰,而且也更便于测试。
尝试一下
不知道我说的这些有没有引起你对 Hooks 的兴趣呢?其实从 React 官方的态度可以看出是比较倾向于 Hooks 的,或者说是 Function Components,虽然说是不会移除掉 Class Components(要是移除了,估计就炸了吧),但是可以在项目中先小范围试用下嘛,相信我你会喜欢上它的!
总结
前面写的有点分散,最后再总结一下 Hooks 带来的开发体验上的提升:
- 想给 Function Components 增加 state 时,无需重构代码
- 相比于
render props
和高阶组件
,提供了更简单直观复用有状态的逻辑(stateful logic)的方法 - 代码逻辑更加统一,更容易组织代码结构
- 便于测试
有了 Hooks 之后,Function Components 不再是「高级字符串模版」啦!?
以上只是我个人对 React Hooks 的一些想法,如有不对之处,欢迎指正~~
扩展阅读: