前言
react 官方16.8版本提供了包括 useState
、useEffect
、useContext
等等在内的常用的10个hooks的使用方法,后面又在v18版本中新增的几个,本文将围绕官方给定的这几个重要hooks展开实战demo的使用讲解。
1. useState
功能: 定义变量,使其具备类组件的 state
,让函数式组件拥有更新视图的能力。
js
const [state, setState] = useState(initData)
入参:
- initData:默认初始值,有两种情况:函数和非函数,如果是函数,则函数的返回值作为初始值。
出参:
- state:数据源,用于渲染
UI 层
的数据源; - setState:改变数据源的函数,可以理解为类组件的
this.setState
使用: useState
会进行一个比较浅的比较,所以如果是对象直接传入的时候,并不会实时更新。如果要实时更新,注意其写法(如下第三种)
js
import { useState} from "react";
import { Button } from "antd";
const Index = () => {
const [count, setCount] = useState(0);
const [info, setInfo] = useState({num: 0})
const [msg, setMsg] = useState({num: 0})
return (
<div>
<h3>useState</h3>
<div>1.数字形式:{count}</div>
<Button type="primary" onClick={() => setCount(count + 1)}> 第一种方式+1 </Button>
<div>2.对象形式传值不会实时更新:{info.num}</div>
<Button type="primary" onClick={() => {
info.num++;
setInfo(info)
}}
> 第二种方式+1 </Button>
<div>3.对象形式传值能够实时更新:{msg.num}</div>
<Button type="primary" onClick={() => {
msg.num++;
setMsg({...msg})
}}
> 第二种方式+1 </Button>
</div>
)
}
export default Index
2. useEffect
功能: 副作用,弥补了函数式组件没有生命周期的缺陷,是我们最常用的钩子之一。
js
useEffect(()=>{
......
return destory
}, deps)
入参:
- callback:useEffect 的第一个入参,最终返回
destory
,它会在下一次 callback 执行之前调用,其作用是清除上次的 callback 产生的副作用; - deps:依赖项,可选参数,是一个数组,可以有多个依赖项,通过依赖去改变,执行上一次的 callback 返回的 destory 和新的 effect 第一个参数 callback。
使用: deps
的个数决定callback
什么时候执行,当 deps 不存在时,只要数据源发生变化(不限于自身中),该函数都会执行(请不要这么做,当依赖的数据源较多时候,会出现一些失控的情况)。destory
会在组件卸载时执行,通常用于监听 addEventListener
和 removeEventListener
上。
js
import { useState, useEffect } from "react";
import { Button } from "antd";
function Child(){
const [number, setNumber] = useState(0);
useEffect(() => {
console.log("挂载");
return () => {
console.log("卸载");
};
}, []);
return (
<div>
<h4>i am a child</h4>
<Button type="primary" onClick={() => setNumber(number + 1)}> 点我+1 </Button>
<p>{number}</p>
</div>
);
};
function Index(){
const [flag, setFlag] = useState(false);
return (
<div>
<h3>useEffect</h3>
<h4>parent</h4>
<Button type="primary" onClick={() => setFlag(!flag)}> {flag ? "卸载" : "挂载"} </Button>
{flag && <Child />}
</div>
);
};
export default Index;
3. useContext
功能: 设置全局共享数据,使所有组件可跨多层级实现数据共享。useContext 的参数一般是由 createContext
创建,通过 CountContext.Provider
包裹的组件,才能通过 useContext
获取对应的值。我们可以简单理解为 useContext
代替 context.Consumer
来获取 Provider
中保存的 value
值。
js
const contextValue = useContext(context)
入参:
- context:一般而言保存的是 context 对象。
出参:
- contextValue:返回的数据,也就是
context
对象内保存的value
值。
使用:
js
import { useState, createContext, useContext } from "react";
import { Button } from "antd";
// 创建context 对象
const CountContext = createContext(0);
// 孙层级
const Son = () => {
const count = useContext(CountContext);
return <div style={{ marginTop: 10 }}>孙组件获取到的count: {count}</div>;
}
// 子层级
const Child = () => {
const count = useContext(CountContext);
return (
<div style={{ marginTop: 10 }}>
子组件获取到的count: {count}
<Son />
</div>
);
}
// 父层级
const Index = () => {
let [count, setCount] = useState(0);
return (
<div>
<h3>useContext</h3>
<Button type="primary" onClick={() => setCount((v) => v + 1)}> 点击+1 </Button>
<div>父组件中的count:{count}</div>
<CountContext.Provider value={count}>
<Child></Child>
</CountContext.Provider>
</div>
)
}
export default Index
4. useReducer
功能: 类似于 redux
,但是它是单个组件的状态管理,组件通讯还是要通过 props。简单地说,useReducer 相当于是 useState 的升级版,用来处理复杂的 state 变化。
js
const [state, dispatch] = useReducer(
(state, action) => {},
initialArg
);
入参:
- reducer:函数,可以理解为 redux 中的 reducer,最终返回的值就是新的数据源 state;
- initialArg:初始默认值;
出参:
- state:更新之后的数据源;
- dispatch:用于派发更新的
dispatchAction
,可以认为是useState
中的setState
。
使用:
js
import { useReducer } from "react";
import { Button } from "antd";
const Index = () => {
const [count, dispatch] = useReducer(
(state, action) => {
if(action.type === 'plus') {
return state + 1;
} else if(action.type === 'minus') {
return state - 1;
} else {
return state;
}
},0
)
return (
<div>
<h3>useReducer</h3>
<div>count值为:{count}</div>
<Button type="primary" onClick={() => dispatch({type: 'plus'})}> 点击+1 </Button>
<Button type="primary" onClick={() => dispatch({type: 'minus'})}> 点击-1 </Button>
</div>
)
}
export default Index;
5. useMemo
功能: 每一次的状态更新都会让组件重新绘制,而重新绘制必然会带来不必要的性能开销。useMemo 函数之所以能带来性能提升,是因为在依赖不变的情况下,会返回相同的引用,避免子组件进行无意义的重复渲染。
js
const cacheData = useMemo(fn, deps)
入参:
- fn:函数,函数的返回值会作为缓存值;
- deps:依赖项,数组,会通过数组里的值来判断是否进行 fn 的调用,如果发生了改变,则会得到新的缓存值。
出参:
- cacheData:fn 函数的返回值,如果 deps 中的依赖值发生改变,将重新执行fn取新返回值,否则取上一次的缓存值。
使用:
js
import { useState, useMemo } from 'react';
import { Button } from 'antd';
function getList(list){
return list.map((item) => {
console.log('获取数据:', item);
return item;
})
}
// 用 useMemo 处理数据
const useMemoData = (list) => {
return useMemo(() => {
return getList(list);
}, [])
}
const Index = () => {
let [flag, setFlag] = useState(true);
const list01 = [1,2,3,4,5,6];
const list02 = [11,22,33,44,55,66];
// 不处理,直接取数
const data01 = getList(list01);
// 通过 useMemo 取数
const data02 = useMemoData(list02);
return (
<div>
<h3>useMemo</h3>
<div>不处理,直接取数: {JSON.stringify(data01)}</div>
<div>用useMemo处理数据: {JSON.stringify(data02)}</div>
<Button type="primary" onClick={() => setFlag((v) => !v)}>
状态切换{JSON.stringify(flag)}
</Button>
</div>
);
}
export default Index;
6. useCallback
功能: 与useMemo类似,功能上可以说一模一样,唯一不同的点是 useMemo 返回的是值,而 useCallback 返回的是函数。
js
const resFn = useCallback(fn, deps)
入参:
- fn:函数,函数的返回值会作为缓存值;
- deps:依赖项,数组,会通过数组里的值来判断是否进行 fn 的调用,如果发生了改变,则会得到新的缓存值。
出参:
- resfn:即 fn 函数,如果 deps 中的依赖值发生改变,将重新执行 fn,否则取上一次的函数(从这里可以看出实际上 useCallback 就是依赖缓存看是否执行 fn 函数)
使用: useCallback 必须配合 React.memo 进行优化,如果不配合使用,性能不但不会提升,还有可能降低。其中 memo 的作用是在组件重新渲染前确认内部传入的组件是否需要重新渲染。
js
import React from "react"
import { useState, useCallback }from 'react'
import { Button } from 'antd'
const ChildDom = React.memo(({ children, onClick = () => {} }) => {
console.log(children);
return (
<Button
type="primary"
onClick={onClick}
style={children === "useCallback点击" ? { marginLeft: 10 } : undefined}
>
{children}
</Button>
);
});
function Index() {
let [count, setCount] = useState(0);
let [flag, setFlag] = useState(true);
// add 用useCallback 处理
const add = useCallback(() => {
setCount(count + 1)
}, [count]);
return (
<div>
<h3>useCallback</h3>
<div>数字变化: {count}</div>
<ChildDom onClick={() => setCount((v) => v + 1)}>普通点击</ChildDom>
<ChildDom onClick={add}>useCallback点击</ChildDom>
<Button type="primary" onClick={() => setFlag((v) => !v)}>
切换{JSON.stringify(flag)}
</Button>
</div>
)
}
export default Index
7. useRef
功能: 1.用于获取当前元素的所有属性; 2.还有一个高级用法:缓存数据, useRef可以用来定义变量,这些变量更改之后不会引起页面重新渲染,比如分页获取数据时,存储页码
js
const ref = useRef(initialValue);
入参:
- initialValue:初始值,默认值。
出参:
- ref:返回的一个 current 对象,这个 current 属性就是 ref 对象需要获取的内容
使用:
- 用来缓存数据,保证获取的数据是最新值
js
import React, { useRef } from 'react'
import { Button } from 'antd'
export default function SubIndex() {
const count = useRef(0);
const handleAlertClick = () => {
setTimeout(() => {
alert(`you clicked on ${count.current}`);
}, 3000);
}
return (
<div>
<h3>subIndex</h3>
<Button onClick={() => {
count.current = count.current + 1;
console.log(count.current);
}}>
点我+1
</Button>
{/* 在 count 为6的时候, 点击 alert , 再继续增加 count 到10, 弹出的值为10 */}
{/* 采用useRef,作为组件实例的变量,保证获取到的数据肯定是最新的。也说明ref更改不会re-render */}
<Button onClick={handleAlertClick}>alert</Button>
</div>
)
}
- 用来获取当前元素,操作dom节点
js
import React from 'react'
import { useState, useRef, useEffect } from "react";
function Index() {
const [clientHeight, setClientHeight] = useState(0);
const [scrollTop, setScrollTop] = useState(0);
const [scrollHeight, setScrollHeight] = useState(0);
const scrollRef = useRef(null);
useEffect(() => {
if(scrollRef?.current) {
console.log('scrollRef?.current', scrollRef?.current)
let clientHeight = scrollRef?.current.clientHeight; //可视区域高度
let scrollTop = scrollRef?.current.scrollTop; //滚动条滚动高度
let scrollHeight = scrollRef?.current.scrollHeight; //滚动内容高度
setClientHeight(clientHeight);
setScrollTop(scrollTop);
setScrollHeight(scrollHeight);
}
console.log('useEffect');
}, [])
const onScroll = () => {
if(scrollRef?.current) {
let clientHeight = scrollRef?.current.clientHeight; //可视区域高度
let scrollTop = scrollRef?.current.scrollTop; //滚动条滚动高度
let scrollHeight = scrollRef?.current.scrollHeight; //滚动内容高度
setClientHeight(clientHeight);
setScrollTop(scrollTop);
setScrollHeight(scrollHeight);
}
}
return (
<div>
<h3>useRef</h3>
<div>
<p>可视区域高度:{clientHeight}</p>
<p>滚动条滚动高度:{scrollTop}</p>
<p>滚动内容高度:{scrollHeight}</p>
</div>
<div
style={{ height: 300, width: "80%", border: "1px solid #000", overflowY: "scroll" }}
ref={scrollRef}
onScroll={onScroll}
>
<div style={{ height: 1000 }}></div>
</div>
</div>
)
}
export default Index
8. useImperativeHandle
功能: 这个hook可以让不同的模块关联起来,让父组件调用子组件的方法。例如在一个复杂页面,我们会将这个页面进行模块化,这样会分成很多个模块,我们需要在最外层的组件上
控制其他子组件
的方法,这时就需要 useImperativeHandle 的帮助(在不用redux
等状态管理的情况下)。
js
useImperativeHandle(ref, createHandle, deps)
入参:
- ref:接受 useRef 或 forwardRef 传递过来的 ref;
- createHandle:处理函数,返回值作为暴露给父组件的 ref 对象;
- deps:依赖项,依赖项如果更改,会形成新的 ref 对象。
使用:
js
import React from 'react'
import { useState, useRef, useImperativeHandle } from "react";
import { Button } from "antd";
const Child = (props) => {
const [count, setCount] = useState(0);
const addCount = ()=> {
setCount(count + 1);
}
// useImperativeHandle 钩子通过将子组件的 addCount 方法绑定在父组件上,供父组件调用
useImperativeHandle(props.parentRef, () => {
return {
addCount
}
})
return <div>
<p>点击次数:{count}</p>
<Button onClick={() => addCount()}> 子组件的按钮,点击+1</Button>
</div>
}
function Index() {
const ref = useRef(null);
return (
<div>
<h3>useImperativeHandle</h3>
<span>父组件: </span>
<Button
type="primary"
onClick={() => ref.current.addCount()}
>
父组件上的按钮调用子组件上的方法
</Button>
<span>子组件: </span>
<Child parentRef={ref} />
</div>
)
}
export default Index
9. useLayoutEffect
功能: 与 useEffect 用法基本一致,不同点在于它是同步执行
:
- useLayoutEffect 在 DOM 更新之后,浏览器绘制之前的操作,这样可以更加方便地修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,所以 useLayoutEffect 的执行顺序在 useEffect 之前
- useLayoutEffect 相当于有一层防抖效果;
- useLayoutEffect 的 callback 中会阻塞浏览器绘制。
js
useLayoutEffect(callback,deps)
使用:
- useEffect 执行顺序:setCount 设置 => 在 DOM 上渲染 => useEffect 回调 => setCount 设置 => 在 DOM 上渲染。
- useLayoutEffect 执行顺序:setCount1 设置 => useLayoutEffect 回调 => setCount1 设置 => 在 DOM 上渲染。
js
import React from 'react'
import { useState, useEffect, useLayoutEffect } from "react";
export default function Index() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
useEffect(() => {
if(count === 0) {
setCount(10 + Math.random() * 100)
}
}, [])
useLayoutEffect(() => {
if(count1 === 0) {
setCount1(10 + Math.random() * 100)
}
}, [])
return (
<div>
<h3>useLayoutEffect</h3>
<div>useEffect的count: {count}</div>
<div>useLayoutEffect的count: {count1}</div>
</div>
)
}