这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
Hooks是React 16.8新增的功能。Hooks允许我们使用状态和其他React特性,而无需编写Class组件。React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态逻辑复用方案,不会产生 JSX 嵌套地狱问题。
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
新的函数 useState 是一个我们学到的第一个 "Hook" ,但是这个例子只是个简单的例子
动机
Hooks 解决了在这五年里写和维护成百上千的 React 组件的时候,遇到的各种各样的互相不连接的问题。无论你什么时候开始学习 React, 并且日常都用它,或者甚至用一个不同的库但是是相似模型的组件,你都可能会遇到下面的这些问题
在不同的组件之间很难重用状态逻辑
react 不会提供一个重用组件的功能,(比如连接到 store)。如果你用 react 有一段时间了,你会熟悉 render props 和 高阶组件这些模式去解决这个问题。 但是使用这些模式需要你重构代码,会很麻烦而且代码很难追踪。 如果看了经典的 React 应用在 react DevTools 里面,你会找到组件的 “wrapper 黑洞” ,组件被包裹在 provider 层,消费者层,高阶组件, render props和其他抽象概念里面。React 需要一个更原生的写法去共享状态逻辑。
用 Hooks, 我们可以把状态逻辑抽象出来,独立测试和重用。Hooks 让你可以重用状态逻辑,而不需要对组件大改。这让你在很多组件和社区之间很容易分享 hooks
下面,我们来看一个hooks的例子
一个 Hooks 演变
我们先假想一个常见的需求,一个 Modal 里需要展示一些信息,这些信息需要通过 API 获取且跟 Modal 强业务相关,要求我们:
- 因为业务简单,没有引入额外状态管理库
- 因为业务强相关,并不想把数据跟组件分开放
- API 数据会随机变动,因此需要每次打开 Modal 才获取最新数据
- 为了后期优化,不可以有额外的组件创建和销毁
我们可能的实现如下:
class RandomUserModal extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {},
loading: false,
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
if (this.props.visible) {
this.fetchData();
}
}
componentDidUpdate(prevProps) {
if (!prevProps.visible && this.props.visible) {
this.fetchData();
}
}
fetchData() {
this.setState({ loading: true });
fetch('https://randomuser.me/api/')
.then(res => res.json())
.then(json => this.setState({
user: json.results[0],
loading: false,
}));
}
render() {
const user = this.state.user;
return (
<ReactModal
isOpen={this.props.visible}
>
<button onClick={this.props.handleCloseModal}>Close Modal</button>
{this.state.loading ?
<div>loading...</div>
:
<ul>
<li>Name: {`${(user.name || {}).first} ${(user.name || {}).last}`}</li>
<li>Gender: {user.gender}</li>
<li>Phone: {user.phone}</li>
</ul>
}
</ReactModal>
)
}
}
我们抽象了一个包含业务逻辑的 RandomUserModal
,该 Modal 的展示与否由父组件控制,因此会传入参数 visible
和 handleCloseModal
(用于 Modal 关闭自己)。
为了实现在 Modal 打开的时候才进行数据获取,我们需要同时在 componentDidMount
和 componentDidUpdate
两个生命周期里实现数据获取的逻辑,而且 constructor
里的一些初始化操作也少不了。
其实我们的要求很简单:在合适的时候通过 API 获取新的信息,这就是我们抽象出来的一个业务逻辑,为了这个业务逻辑能在 React 里正确工作,我们需要将其按照 React 组件生命周期进行拆解。这种拆解除了代码冗余,还很难复用。
下面我们看看采用 Hooks 改造后会是什么样:
function RandomUserModal(props) {
const [user, setUser] = React.useState({});
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
if (!props.visible) return;
setLoading(true);
fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
setUser(json.results[0]);
setLoading(false);
});
}, [props.visible]);
return (
// View 部分几乎与上面相同
);
}
很明显地可以看到我们把 Class 形式变成了 Function 形式,使用了两个 State Hook 进行数据管理(类比 constructor
),之前 cDM
和 cDU
两个生命周期里干的事我们直接在一个 Effect Hook 里做了这些,最大的优势是代码精简,业务逻辑变的紧凑,代码行数也从 50+ 行减少到 30+ 行。
Hooks 的强大之处还不仅仅是这个,最重要的是这些业务逻辑可以随意地的的抽离出去,跟普通的函数没什么区别(仅仅是看起来没区别),于是就变成了可以复用的自定义 Hook。具体可以看下面的进一步改造:
// 自定义 Hook
function useFetchUser(visible) {
const [user, setUser] = React.useState({});
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
if (!visible) return;
setLoading(true);
fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
setUser(json.results[0]);
setLoading(false);
});
}, [visible]);
return { user, loading };
}
function RandomUserModal(props) {
const { user, loading } = useFetchUser(props.visible);
return (
// 与上面相同
);
}
这里的 useFetchUser
为自定义 Hook,它的地位跟自带的 useState
等比也没什么区别,你可以在其它组件里使用,甚至在这个组件里使用两次,它们会天然地隔离开。