前言
react函数组件目前已经成为react组件的主流,类组件react官方已经明确回复后续只会维护,新功能不再更新。而在react函数组件中,react hooks可以说是其核心和基石。在hooks没有之前,react函数组件只能称为无状态函数组件,当时只能当做ui渲染来使用,是hooks赋予了函数组件灵活多变的状态能力。使其在日常业务开发中能满足各种场景的需求。下面记录总结下自己这些年react hooks中常用的几个API。
React中提供的hooks
useState(组件的状态管理)
最常用没有之一,没有任何一个有他地位高,只要有状态的组件都需要。最基本用法如下:
- 参数:参数用来设置状态的初期值,例如useState(初始值)
- 返回:返回一个数组,【当前值,更新状态的函数】
- 更新方式:更新状态只能调用更新状态的函数,不能直接赋值状态来更新值,更新函数调用的方式有以下两种
- 直接更新:直接设置新值,覆盖当前状态
- 函数式更新:函数式更新能有效的避免闭包问题,因为函数的参数始终都是最新的状态,不会取闭包函数中状态值
```js
const CountNumber = ()=>{
const [count,setCount] = useState(0)
//直接更新
const handleClick = ()=>{
setCount(count+1)
}
//函数式更新
const handleClick2 = ()=>{
setCount((prevCount)=>prevCount+1)
}
return (
<div>
<div>{count}</div>
<button onClick={handleClick}>+1</button>
<button onClick={handleClick2}>+1</button>
</div>
)
}
useState是react hooks中最常用逻辑最容易理解,但是在使用时仍有些需要注意的
- 多次调用不会合并赋值,例如
const [countObj,setCountObj] = useState({name:"grade",number:1})
//此种操作后countObj会变成{number:3}
setCountObj({number:3})
- 函数执行不是同步更新状态,而是异步更新。setState之后立马取值是不会获取最新的值
- 初始值赋值操作只在页面渲染时执行一次。
useEffect
useEffect也是日常开发中比较常用的hooks,这个hooks很考验使用者的水平,是把双刃剑用好利人利己,用坏则伤人伤己。官方说法是允许开发者在组件中执行副作用(个人理解是除组件UI在浏览器渲染以外的事情),最基本用法如下:
- 参数:useEffect(fun,arr),fun是执行的函数,arr是执行依赖项目非必传
- 调用:
- arr不传递:当arr不传时,此时fun这个函数会在组件的每次渲染是执行
- arr为空数组[]: 当arr为[]时,此时的fun这个函数会在组件的第一次渲染时执行
- 此种使用一般有以下几种情况:数据初始化时、设置监听事件
- arr有值:arr中的值变化时才会执行fun函数,另外arr中的依赖项不同的数据类型触发时机有很大的不同。
- 此种使用情况较多,如:监听父组件传递的props变化来进行一些操作、监听组件内部的一些状态来执行其他操作
- 监听:
- 普通数据类型:正常在arr中加入需要监听的state即可。
- 引用类型:不能直接监听整个引用对象,会导致每次渲染都执行fun,因为对象是引用类型,每次渲染都会生成一个新的引导,导致直接监听对象时每次渲染都执行。解决方法有两种,一种是限制arr中为对应属性,另一种是引入useMemo来限制对象。
- 限制属性:监听依赖直接定位到对象中对应的key,Object[key],此种方式来监听
- useMemo:使用react另一hooks useMemo来稳定对象引用,避免无必要更新。使用方式如下:
const stableObj = useMemo(() => obj, [obj.key1, obj.key2]);
- 清理:
const CountNumber = ()=>{
const [count,setCount] = useState(0)
useEffect(()=>{
console.log("每次渲染我都会执行!")
})
useEffect(()=>{
console.log("第一次渲染我会执行!")
},[])
useEffect(()=>{
console.log("count值变化我会执行!")
return ()=>{
console.log("clear!")
}
},[count])
return <div>{count}<div>
}
useEffect在使用时有些需要注意的地方,如果没有正确使用可能会导致组件重复渲染和陷入死循环。另外使用好了可以方便我们很快的实现组件的许多功能,以及提升日后的可维护性。以下记录useEffect使用容易出错的地方:
- 依赖数组未传:上传说了不传递数组是useEffect的执行方式,此种一般很少会用到。尽量也不要使用,这样用每次hooks渲染都会执行一次函数,如果函数中操作较多会带来很大的性能问题和不稳定性。
- 避免死循环:在useEffect不要再次做setState的操作,监听的依赖项中包含该setState对应的状态,那组件就会进入无限循环执行fun。
useMemo
useMemo在我们习惯了react函数组件的使用后,用来提升我们的组件性能。初学时可能经常会忘记使用该hooks,这个hooks对应简单ui渲染可能很难分辨其使用和不使用的区别。但在一些性能要求较高的业务中useMemo就是必不可少的了,使用与否在页面很容易感知到。 基本语法:
const value = useMemo(()=>{},[])
实际使用场景:
- 避免重复昂贵的计算操作:这是useMemo最基础用法,用来缓存复杂的计算返回值。
- 缓存引用类型:在介绍useEffect时,使用useMemo缓存了引用类型,可以避免无效引用类型的监听。
function MemoFun(){
//避免重复昂贵的计算
const value = useMemo(()=>{
console.log("复杂的计算*****")
},[])
//缓存引用类型
const user = useMemo(()=> user,[user.sex,user.name])
return <div>{value}</div>
}
注意:
- 不要滥用:useMemo本身就存在性能开销,简单的计算,获取常量赋值不应该使用useMemo来缓存,这样会导致比不用useMemo带来更大的性能开销问题。
- 正确使用,不要漏掉监听:useMemo的依赖数组中,如果不完全会导致不能正确执行赋值操作,获取不到正确的值。
useCallback
useCallback和useMemo类似,也是react中一个性能优化提升的hooks,useCallback主要是用来缓存函数方法的,依赖不变时返回相同的函数引用。 基本语法:
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
实际使用场景:
- 缓存传入子组件的函数,避免子组件无效的重复渲染。
- 函数作为其他 Hook 的依赖时,例如函数作为useEffect依赖项时,如果不用useCallback那每次组件渲染都会导致useEffect执行一遍。
const UserName = ({ userId }) => {
const [user, setUser] = useState(null);
//错误函数使用:此种做法setUser会导致组件重新渲染,useEffect监听getUserInfo函数变化重新调用,形成死循环。
const getUserInfo = () => {
fetch(`/api/users/info`)
.then(res => res.json())
.then(setUser);
};
//正确函数使用:此种做法setUser会导致组件重新渲染,useEffect监听getUserInfo函数未变化,仅仅会执行一次调用
const getUserInfoCache = useCallback(() => {
fetch(`/api/users/info`)
.then(res => res.json())
.then(setUser);
},[])
useEffect(() => {
getUserInfoCache();
getUserInfo();
}, [fetchUser]); // fetchUser 每次都会创建新的引用,导致无限循环
return <div>{user?.name}</div>;
}
useContex
随着业务开发越来越复杂,组件越来越多且嵌套越来越深,许多状态多个组件中都要使用,仅仅通过props传递已经无法满足我们的需求。此时如果使用useContex能解决我们的问题,通过useContex可以管理应用级的全局状态及共享全局数据。 基础介绍:useContex的使用需要配置另外三个React API,createContext、Context.Provider和useContext
- createContext: 用来创建一个上下文容器,容器中可以存储全局状态及方法。(底层数据层面看,createContext创建的其实是个标识)
- Context.Provider:用来包裹需要共享状态的组件树,通过value属性传递全局状态及方法。(实际数据存储在react的Fiber组件数节点)
- useContex:在需要使用全局状态的组件中,通过useContext hook获取上下文容器中的状态及方法。(通过createContext创建的标识取Fiber上的数据) 基本用法:
- 第一步:创建上下文容器
- 第二步:创建Provider,并且项目需要的节点包裹Provier并提供提供全局状态
- 第三步:在子组件或者兄弟组件中使用useContex
//创建上下文容器
const MyContext = createContext();
//在组件树的某个节点提供全局状态
<MyContext.Provider value={{ count: 0, increment: () => {} }}>
<ChildComponent />
</MyContext.Provider>
//在子组件中使用全局状态
const ChildComponent = () => {
const { count, increment } = useContext(MyContext);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
使用场景:当应用中存在多个组件需要共享状态时,使用useContext可以避免通过props层层传递状态,直接在上下文容器中存储状态,在需要使用状态的组件中直接获取。实际的应用场景比较多,以下 举几个常用的业务
- 用户信息:应用入口获取用户信息,通过createContext存储用户信息,在需要使用用户信息的组件中直接获取。
- 主题切换:应用入口获取用户主题偏好,通过createContext存储主题信息,在需要切换主题的组件中直接获取。
- 表单数据:在有多步骤表单操作或者表单数据非常复杂时,通过createContext存储表单数据,在每个表单步骤组件获取子组件中直接获取并更新表单数据。
总结
react hooks中状态管理、副作用处理这两个hook应该是日常开发中最为常用的了,一些小型项目其他的hook基本用不到,但随着项目复杂度提升,性能优化成为刚需,useMemo 和 useCallback 的价值便会凸显。另外当组件越来越多组件间状态传递仅仅使用props已经满足不了我们需求,此时useContext可以登场了。虽然有很多封装好的状态管理库,但 useContext 凭借其简洁易用的特性,仍备受青睐,在无特殊复杂需求的场景下,无需引入额外状态库即可高效完成状态管理。