React中常用的hooks总结

54 阅读8分钟

前言

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,createContextContext.ProvideruseContext

  • 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 凭借其简洁易用的特性,仍备受青睐,在无特殊复杂需求的场景下,无需引入额外状态库即可高效完成状态管理。