这是我参与「第四届青训营 」笔记创作活动的的第7天 ,整理简单介绍了 React 中常见的 6 个 hooks,每个hook都是为了解决函数组件中的特定问题,因此从解决问题的角度去理解 Hooks 才能事半功倍。
React Class 组件和 Hooks 是完全不同的两套开发模式,因此,不用再去可以学一遍Class组件的知识,使用 函数组件 + Hooks 能满足你的所有需求。
React 提供的 Hooks 其实非常少,一共只有 10 个,比如 useState、useEffect、 useCallback、useMemo、useRef、useContext 等等。
通常只需要灵活运用 4 个常见的 Hooks就能进行 90% 的 React 开发了
useState:让函数组件具有维持状态的能力
useState 这个 Hook 就是用来管理 state 的,它可以让函数组件具有维持状态的能力。也就是说,在一个 函数组件的多次渲染之间,这个 state 是共享的。
import React, { useState } from 'react';
function Example() {
// 创建一个保存 count 的 state,并给初始值 0
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
);
}
整个流程是我声明了一个 count 的 state,通过 setCount 来修改 count 的值。当 count 的值改变就又回触发组件的刷新。
- useState(initialState) 的参数 initialState 是创建 state 的初始值,它可以是任意类 型,比如数字、对象、数组等等。
- useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的 值,第二个则是用来设置这个 state 的值。在这里要注意的是,state 的变量(例子中的 count)是只读的,所以我们必须通过第二个数组元素 setCount 来设置它的值。
- 如果要创建多个 state,那么我们就需要多次调用 useState。
原则:state中永远不要保存可以通过计算得到的值
state 虽然便于维护状态,但也有自己的弊端。一旦组件有自己状态,意味着组件如 果重新创建,就需要有恢复状态的过程,这通常会让组件变得更复杂。
所以引入Redux等一些第三方库来便于管理状态
useEffect:执行副作用
一定不要按照组件生命周期方法去类比 useEffect。useEffect是每次组件render完后判断依赖并执行就可以了。
什么是副作用:函数式编程将那些跟数据计算无关的操作都称为副作用。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副作用。
基本用法
import React, { useEffect } from 'react';
function Welcome(props) {
useEffect(() => {
document.title = '加载完成';
});
return <h1>Hello, {props.name}</h1>;
}
我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副效应,必须通过useEffect()来实现。
第二个参数
useEffect()的第二个参数可以不指定,或者指定一个依赖数组,依赖数组可以为空或者不为空。
- 如果不提供第二个依赖参数,这个函数会在每次render后执行。
useEffect(() => {}) - 如果提供一个空的依赖数组,仅会在第一次render后执行。
useEffect(() => {},[]) - 如果依赖数组中添加了依赖项,则会在依赖项发生变化后执行。
useEffect(() => {},[deps]) - 此外,在回调函数中 return 一个方法,则会在组件unmount时自动调用,完成一些清理操作。
注意事项
- 依赖项中定义的变量一定是会在回调函数中用到的 ,否则声明依赖项其实是没有意义的。
- 依赖项一般是一个常量数组,而不是一个变量。因为一般在创建 callback 的时候,你其实非常清楚其中要用到哪些依赖项了。
- React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类 型。如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。
useCallback:缓存回调函数
为什么需要缓存?
在组件状态发生变化时,函数组件会重新执行一遍。也会重新创建一个事件处理函数,这样就导致接收了这个事件处理函数的组件即使没有发生变化也需要重新渲染。
(如果时间处理函数依赖的 sate 没有发生变化的话完全可以不用重新创建,造成不必要的性能开销)
怎么解决?
只有当时间处理函数中依赖的 state 发生变化才重新定义新的函数,这正是 useCallback 的作用。
怎么使用?
把原本要定义的函数作为useCallback的回调函数,添加相应的依赖项即可。
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(
()=> setCount(count),
[count], // 只有count发生变化时,才会重新创建回调函数
)
}
useMemo:缓存计算结果
理解了useCallback后再理解useMemo就很容易了,一个是用来缓存函数,另一个是用来缓存计算的结果。 为什么需要缓存值:如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖发生变化的时候,才需要重新计算。
//使用 userMemo 缓存计算的结果
cost usersToshow = useMemo (() => f
if (!users) return null;
return users.data.filter ((user) => {
return user.first_name.includes (searchKey));
}
}, [users,searchkey]);
使用useMemo的好处
- 避免重复计算
- 避免子组件的重复渲染
useRef:在多次渲染之间共享数据
我们现在的函数组件相比类组件还缺少了一个很重要的能力:在多次渲染之间共享数据。
我们可以把useRef 看作是在组件之外创建的一个容器空间,通过唯一的current属性设置一个值,从而在函数组件的多次渲染之间共享这个值。
还有一个重要的功能是保存某个DOM节点的引用
useContext:定义全局状态
我们知道可以通过 props 来跨组件传递数据,但是只能局限于父子节点之间进行
。
useContext就是帮助我们可以垮层次传递,即在某个组件上创建一个Context,使得组件树上所有的子节点都可以访问和修改这个Context。
为什么要实现使用这个 Hook 而不是直接使用全局变量呢?,答案很简单,为了能够进行数据绑定,当Context发生了改变,使用这个Context的组件就能自动刷新。
Context提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。
但因此也带来了,两个缺点:
- 让调试变得麻烦,很难跟踪某个 Context 的变化是如何产生的
- 让组件复用变得困难,因为如果一个组件使用了某个Context,它就必须确保被用到的地方一定有这个Context的Provider在其父组件的路径上。
总结
整理简单介绍了 React 中常见的 6 个 hooks,每个hook都是为了解决函数组件中的特定问题,因此从解决问题的角度去理解 Hooks 才能事半功倍。