前言
React创建组件的方式有三种,分别是函数式组件、类组件,还有createElement组件。react v16.8版本之前函数式组件是没有状态的。但是,自16.8以后得版本有个hook函数,函数式组件也有了状态,反而类组件没有多少人写了,原因在于生命周期很麻烦,也难记。笔者近几年写React项目已经很少使用类组件了。接下来,详细探讨下hook函数,为什么会有那么神奇的效果?
一 、常用的Hook有哪些?
React Hooks 是 React v16.8 之后推出的函数式组件状态管理方案。它是为了解决状态复用、类组件写法麻烦等原因而提出的,Hook函数本质是闭包。Hooks 主要是利用闭包来保存状态,使用链表保存一系列 Hooks,将链表中的第一个 Hook 与 Fiber 关联。在 Fiber 树更新时,就能从 Hooks 中计算出最终输出的状态和执行相关的副作用。Hook函数带来便利有逻辑复用、业务代码更聚合、写法更简洁。
常用的Hook函数useState、useEffect、useRef、useCallback、useMemo、useReducer、useLayoutEffect
二、useState详解
useState是React自带的一个Hook函数,使用useState可声明内部状态变量。useState接收的参数为状态初始值或状态初始化方法,它返回一个数组。数组的第一项是当前状态值,每次渲染其状态值可能都会不同;第二项是可改变对应状态值的set函数,在useState初始化后该函数不会变化。
useState的类型为:
function useState<S>(initialState:S|(() => S )): [S,Dispatch <SetStateAction <S>>];
initialState仅在组件初始化时生效,后续的渲染将忽略initialState:
const [value, setValue] = useState("");
const [count, setCount] = useState(value);
如上例中的value,当初始值传入另一个状态并初始化后,另一个状态函数将不再依赖value的值。
在 class 中,我们需要调用 this.setState()
来更新 count
值,在函数式组件中,我们已经有了 setCount
和 count
变量,所以我们不需要 this:
import {useState} from "react";
const Example = () => {const [count, setCount] = useState(0);return (<div><button onClick={() => {setCount(count+1)}}>点击更新count:{count} </button></div>)
}
类似于setState,单击按钮时调用setCount更新了状态值count。当调用setCount后,组件会重新渲染,count的值会得到更新。
当传入初始状态为函数时,其仅执行一次,类似于类组件中的构造函数:
useState返回的更新函数也可使用函数式更新:
setCount(preCount => preCount + 1)
如果新的state需要依赖先前的 state 计算得出,那么可以将回调函数当作参数传递给setState。该回调函数将接收先前的state,并将返回的值作为新的state进行更新。
在组件生命周期或React合成事件中,setState是异步;在setTimeout或者原生dom事件中,setState是同步。
为什么react大部分情况setState是异步的呢?假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。
三、useEffect详解
useEffect函数会在组件渲染完毕后,执行和渲染无关的副作用,如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等。
有了useEffect,我们可以在函数组件中实现 像类组件中的生命周期那样某个阶段做某件事情,具有:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
基本用法:
useEffect(() => {console.log('这是一个不含依赖数组的useEffect,每次render都会执行!')
})
useEffect接受一个回调函数和一个可选的依赖项数组。回调函数会在组件挂载、更新和卸载时执行,根据依赖项的不同情况选择执行。如果依赖项为空数组,那么回调函数只会在组件挂载和卸载时执行一次。如果依赖项中包含某个状态或属性,那么只有在这个状态或属性发生变化时才会执行回调函数。
useEffect的规则
- 没有传第二个参数时,在每次 render 之后都会执行 useEffect中的内容
- useEffect接受第二个参数来控制跳过执行,下次 render 后如果指定的值没有变化就不会执行
- useEffect 是在 render 之后浏览器已经渲染结束才执行
- useEffect 的第二个参数是可选的,类型是一个数组
- 根据第二个参数的不同情况,useEffect具有不同作用
下面是useEffect的一些常见用法:
1、获取数据和执行网络请求:
import React, { useState, useEffect } from 'react';
import axios from 'axios';function MyComponent() {const [data, setData] = useState([]);useEffect(() => {const fetchData = async () => {const result = await axios.get('/api/data');setData(result.data);}fetchData();}, []);return (<div>{data.map((item) => <div key={item.id}>{item.name}</div>)}</div>);
}export default MyComponent;
2、订阅事件:
import { useEffect } from 'react';function MyComponent() {useEffect(() => {const subscription = myEventEmitter.subscribe('event', () => {// 处理事件逻辑});return () => {subscription.unsubscribe();};}, []);return <div>My Component</div>;
}
3、执行清理操作:
import { useEffect, useState } from 'react';function MyComponent() {const [timer, setTimer] = useState(null);useEffect(() => {const id = setInterval(() => {// 处理定时器逻辑}, 1000);setTimer(id);return () => {clearInterval(timer);};}, []);return <div>My Component</div>;
}
4、使用第三方库:
import { useEffect } from 'react';
import moment from 'moment';function MyComponent() {useEffect(() => {moment.locale('zh-cn');}, []);return <div>{moment().format('LLLL')}</div>;
}
需要注意的是,useEffect回调函数中的操作可能会对应用程序的性能产生影响。如果回调函数执行的操作很耗费资源,那么可能会导致应用程序变慢。因此,需要谨慎使用useEffect,并在需要时进行性能优化。
在使用React Hooks时,需要遵守以下准则及特性要求。
-
只在顶层使用Hooks。不要在循环、条件或嵌套函数中调用Hooks,确保总是在React函数组件的顶层调用它们。
-
不要在普通的JavaScript函数中调用Hooks。仅在React的函数组件中调用Hooks,以及在自定义Hook中调用其他Hooks。