介绍
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class
的情况下使用 state
以及其他的 React 特性。
缘由
Hook的初衷是为了解决原本无状态组建需要使用state, 必须改造为class这个痛点。
useState
import React, {
useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {
count} times</p>
<button onClick={
() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
这个是官方提供的最简单的例子,不难理解, 按钮每次点击都会调用一次setCount
, 从而改变count
的值和以下的例子等价。
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {
this.state.count} times</p>
<button onClick={
() => this.setState({
count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
我用注释来解释可能更好理解useState每个参数的意义,稍微改造一下第一个例子。
import React, {
useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [
count, // 在state里面的名字
setCount // 改变这个名字的函数
] = useState(
0 // 初值count的初值
);
return (
<div>
<p>You clicked {
count} times</p>
<button onClick={
() =>
setCount(
count + 1 // 准备把count该成什么样子
)}>
Click me
</button>
</div>
);
}
在以前class
的形式, 所有的可变数据都放在一个state
内部进行维护, 这样这个state
会越来越大…越来越臃肿…越来越难以维护…如果没有注释可能就难以理解…这样就诞生了Redux
。
我本人认为, useState
可以直接解决这样的一个痛点, 下面是我在新项目中使用hook
的例子。
// 表格loading
const [loading, setLoading] = useState(true);
// 表格数据
const [listData, setListData] = useState({
list: [], total: 0 });
// 当前页码
const [current, setCurrent] = useState(0);
// 搜索数据
const [searchData, setSearchData] = useState({
});
// 医生职称
const [jobTitle, setJobTitle] = useState([]);
// 科室
const [dept, setDept] = useState([]);
// 弹窗显隐
const [visible, setVisible] = useState(false);
// 弹窗数据
const [showData, setShowData] = useState({
});
可以很直观的看到基本上一个数据享受一个useState
…配合正确的注释, 调用正确的方法, 使代码可读性大大增强。
useEffect
如果你熟悉
React class
的生命周期函数,你可以把useEffect
Hook
看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。
这句话来自官网的原画,接下来我就为大家解释useEffect
。
useEffect
会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。
这个是官网的原话, 不难理解,这样就可以模拟出 componentDidUpdate
…你可以在hook里面写你想要逻辑,直接上官网代码。
import React, {
useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${
count} times`;
});
return (
<div>
<p>You clicked {
count} times</p>
<button onClick={
() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
可以看到每次点击按钮, 都重新set了Count
的值, 因为每次更新都会走到useEffect
(后面会说到怎么样不每次都进入useEffect
),他是useEffect
的逻辑是,每次都修改document.title
。
这样就模拟了componentDidUpdate
componentDidMount
componentWillUnmount
useEffect
其实有两个参数, 第一个是调用函数, 第二个是监听值。
useEffect(
() => {
// 我叫A函数
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
这段代码可以理解为, 程序一运行, 就调用了一次A函数,之后每次渲染虽然都会走到这个useEffect
,因为他有第二个参数,所以只有在 [props.source]
变化的时候,才会再次调用A函数。
我们可以灵活的调用起来, 这个值可以来自useState
控制.你想他变化的时候,你就用useState
改变一下他的值。
那我怎么样才可以优雅的让这个useEffect
只调用一次,像componentDidMount
呢?可以这样在第二个值传一个控制值。
useEffect(() => {
const firstGet = async () => {
const [z, x, c] = await Promise.all([
requestZ(),
requestX(),
requestZ(),
]);
// 做你想做的事情
};
firstGet();
}, []);
类推,那怎么样才可以模仿componentWillUnmount
呢?
useEffect(() => {
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, []);
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect? React 会在组件卸载的时候执行清除操作。effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
这些都是官网的原话,代码中的return
就是清除,同样的,在第二个值放入一个空.这样就会很优雅的清除了. 最明显就是短信倒计时的setInerval, clear
一下才不会一直占用资源。