Hook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。
为什么需要Hook?
我们先来看一下类组件对比函数式组件有什么优势:
-
类组件可以定义自己的状态(state),而函数式组件不可以,因为函数式组件每次调用都会产生新的临时变量;
-
类组件有自己的生命周期函数,我们可以在对应的周期函数中完成不同的逻辑;
- 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;
- 函数式组件没有生命周期函数
-
类组件可以在状态改变是只会重新执行render函数,而函数式组件在重新渲染时,整个函数都会被执行。
但是类组件也存在问题:
-
复杂组件变得难以理解:
- 我们在刚开始编写类组件时,组件起初很简单,但是随着业务的增多,组件会越来越复杂。
- 例如组件经常在componentDidmount中获取数据,设置事件监听等,又要在componentWillUnmount中清除。
- 对于这样的类组件非常难拆分,因为他们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码复杂度。
-
难以理解的 class
- 我们还发现ES6的class是学习React的一个障碍。
- 我们必须要搞清楚this到底指向谁,所以要花很多精力去学习this。
-
在组件之间复用状态逻辑很难
- 我们也许熟悉React中一些代码复用的解决方案,比如高阶组件。
- 但是这些方案会很麻烦,使代码难以理解。
Hook的出现可以解决上面提到的这些问题。他可以让我们在编写class的情况下使用state以及一些其他的React特性。
通俗一点讲: Hook的出现,把类组件的优势吸收进函数式组件里面来,对函数式组件做了一些增强,又保留函数式组件的简洁性。
使用state Hook
我们通过一个计数器的案列,来对比一下函数式组件结合hook与类组件的对比:
// hook
import React, { useState } from 'react';
export default function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<h1>当前计数: {count}</h1>
<button onClick={() => setCount(count + 1)}>
+1
</button>
</div>
);
}
// 类组件
export default class Example extends PureComponent {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment(){
this.setState({ count: this.state.count + 1 })
}
render() {
const { count } = this.state;
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => this.increment()}>
+1
</button>
</div>
);
}
}
通过上面的一个简单的案例,是不是觉得使用hook让代码简洁多了。
Hook是什么
Hook是一个特殊的函数。它可以让我们"钩入" React state以及生命周期等特性。
useState做了什么
- useState帮助我们定义了一个”state变量“,useState是一种新方法,他与class里面的this.state提供的功能完全相同。
- 一般来说,在函数退出后变量就会”消失“,而state中的变量会被React保留。
useState需要哪些参数
useState接受唯一一个参数,在组件第一次被调用时来作为初始值。(如果没有传递参数,那么初始值为undefined)。
useState方法的返回值是什么
useState的返回值是一个数组,数组中是当前的state以及更新state的函数。const [count, setCount] = useState() 这与 class 里面 this.state.count 和 this.setState 类似。
使用Effect Hook
- Effect Hook可以让你在函数式组件中执行”副作用“操作;
- 网络请求,设置订阅,手动更新DOM都属于”副作用“;
下面我们来看这个例子,为计数器增加了一个小功能:将 document 的 title 设置为包含了点击次数的消息。
import React, { useState, useEffect } from 'react';
export default function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `当前计数:${count}`;
});
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect分析:
- useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数。
- 默认情况下,组件第一次渲染和每次更新之后,都会执行这个函数。
- 如果熟悉类组件的生命周期函数,可以把
useEffectHook 看做componentDidMount,componentDidUpdate和componentWillUnmount这三个函数的组合。
需要清除的Effect
我们在编写组件的时候,有些”副作用“是需要被清除的。例如:订阅外部数据源
使用useEffect清除副作用的案例:
import React, { useState, useEffect } from 'react';
export default function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("订阅数据行为");
return function clean(){ //如果effect 返回一个函数,React 将会在执行清除操作时调用它
console.log("取消订阅")
}
});
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
为什么要返回一个函数
- 这是 effect 可选的清除机制。
- 每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。
- 它们都属于 effect 的一部分。
React何时清除effect
- React 会在组件卸载的时候执行清除操作。
- React 会在执行当前 effect 之前对上一个 effect 进行清除操作。
使用多个Effect
前面说过,网络请求,设置订阅,手动修改DOM都属于”副作用“。Hook允许我们按照代码的用途分离它们,来实现简化代码逻辑的目的,而不是写在一起。
export default function Example(){
const [counter, setCounter] = useState(10);
// 副作用代码
useEffect(()=>{
// 修改dom
})
useEffect(()=>{
// 设置订阅行为
console.log("开始订阅数据");
return ()=>{
console.log("取消订阅");
// 取消监听
}
})
useEffect(()=>{
// 发起网络请求
})
return (
<div>
<h1>App Counter:{counter}</h1>
<button onClick={() => setCounter(counter + 1)}>+1</button>
</div>
);
}
React将按照effect声明的顺序依次调用组件中的每一个effect。
Effect性能优化
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但这会导致两个问题:
- 某些代码我们只希望执行一次即可,类似于类组件中componentDidMount和componentWillUnmount中完成的事情;
- 多次执行也会导致一定的性能问题;
useEffect实际上接受两个参数,参数二是一个数组,判断该useEffect在哪些state发生变化时,才重新执行;
useEffect(() => {
document.title = `当前计数: ${count}`;
}, [count]); // 仅在 count 更改时更新
注意:如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。
Hook规则
- 只在函数最顶层使用,不要在循环、条件或者子函数中调用。
- 只在React函数中调用,不要在普通的js函数中调用。