react Hooks 钩子函数

什么是Hooks?

首先:React的组件创建方式,一种是类组件,一种是纯函数组件。

React团队认为组件的最佳写法应该是函数,而不是类。

但是纯函数组件有着类组件不具备的特点:

  • 纯函数组件没有状态
  • 纯函数组件没有生命周期
  • 纯函数组件没有this

这就注定,纯函数组件只能做UI展示的功能,如果涉及到状态的管理与切换,我们就必须得用类组件或者redux,但是在简单的页面中使用类组件或者redux会使代码显得很重。

因此,React团队设计了React hooks(钩子)。

React Hooks的意思是:组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。

UseState()

我们知道,纯函数组件没有状态,useState()用于为函数组件引入状态。

举例:
state是一个普通变量:

//引入状态钩子useState()
import React,{useState} from 'react'
import './App.css';

function App() {
  //useState创造一个状态,赋值一个初始值,当前赋值的初始值为0
  //count是一个变量,此变量的值指向当前状态的值 相当于this.state
  //setcount是一个函数,此函数可以修改状态的值 相当于this.Setstate
  const [count,setCount] = useState(0)
  const addCount = ()=>{
    setCount(count+1)
  }
  return (
    <div className="App">
       <div>{count}</div>
       <button onClick = {addCount}>点击加1</button>
    </div>
  );
}

export default App;

state是一个对象:

setState()不会局部更新

意思是,如果state是一个对象,不能部分setState,所以我们使用…user将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖。

import React,{useState} from 'react'
import './App.css'

function App(){
  const [user,setUser]=useState({age:'11',name:'Bob'})
  const handerClick=()=>{
      setUser({
          ...user,
          name:'jack'
      })
  }
  return (
    <div className='App'>
        <h1>{user.name}</h1>
          <h1>{user.age}</h1>
          <button onClick={handerClick}>
            Click
          </button>
    </div>
  )
 }
 
 export default App;

useEffect()

useEffect()可以检测数据更新 ,可以用来更好的处理副作用(状态更新,组件重新渲染),比如异步请求等。

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect()的依赖项。

只要数组发生改变,useEffect()就会执行。

当第二项省略不填时,useEffect()会在每次组件渲染时执行,这一点类似于componentDidMount。

useEffect回调在dom渲染完毕之后执行 和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了
举例:

第二个参数省略时:

import React,{useState,useEffect} from 'react'
import './App.css';
function App(){
  const [loading,setLoading] = useState(true)
  //相当于componentDidMount
  //useEffect()第二个参数未填
  useEffect(()=>{
    setTimeout(()=>{
      setLoading(false)
    },3000)
  })
  //loadling为true时显示Loading... 3秒后loading变成了false,显示内容加载完毕
  return (
    loading?<div>Loading</div>:<div>内容加载完毕</div>
  )
}
export default App;

第二个参数存在时:
name改变时会触发useEffect()函数,

import React,{useState,useEffect} from 'react'
import './App.css';
function AsyncPage({name}){
    const [loading,setLoading] = useState()
    const [person,setPerson] = useState({})
    //useEffect()函数在组件初始化执行一次,之后,name改变时才会执行
    //组件渲染时,两秒后从Loading变为Bob
    //name改变时,先从Bob变为Loading,两秒后变为指定名字
    useEffect(()=>{
      setTimeout(()=>{
        setLoading(false)
        setPerson({name})
      },2000)
    },[name])
    return(
      loading?<div>Loading...</div>:<div>{person.name}</div>
    )
  }
  
function App(){
  const [name,setName] = useState('Bob')
  const changeName = (name)=>{
    setName(name)
  }
  return (
    <div>
      <AsyncPage name = {name}/>
      <button onClick = {()=>changeName('Jack')}>将名字改为jack</button>
      <button onClick = {()=>changeName('Tom')}>将名字改为Tom</button>
    </div>
  )
}
export default App;

useEffect()返回一个函数:

当useEffect()返回一个函数时,该函数会在组件卸载时执行。

举例:

当点击switch时,组件被卸载,定时器被清除,控制台不再打印。

import React,{useEffect,useState} from 'react'
import './App.css';

function Test (){
  useEffect(()=>{
    let timer = setInterval(()=>{
      console.log('定时器正在执行')
    },1000)
    return ()=>{
      //清除定时器
      clearInterval(timer)
    }
  },[])
  return(
    <div>this is test</div>
  )
}

function App(){
  const [flag,setFlag] = useState(true)
  return (
    <div>
      {flag?<Test/>:null}
      <button onClick={()=>{setFlag(false)}}>switch</button>
    </div>
  )
}

export default App;

自定义hook

我们在写 React 函数组件时,如果想要复用组件的部分逻辑,可以考虑写自定义 Hook。

Hook 的规则

在此之前,我们先了解一下 Hook 的使用规则。

首先 Hook 只能在函数组件的顶层使用,不能在循环、条件、嵌套函数中执行。

这和 React Hook 的实现原理有关。当第一次执行函数组件时,React 会分配一个对象,然后一个个调用 Hook 时,将传入的参数或得到的结果依次放入到有序的表中缓存起来,然后保存在该对象中。

之后再次执行函数组件时,必须要保证这些 Hook 的执行顺序相同,才能做依赖项的对比,以及和前一次渲染的状态的对比等对比逻辑。

如果能 hook 可以出现循环、条件、嵌套函数中,就不能保证 Hook 执行顺序不变。

此外Hook 只能在函数组件内工作,不要在非组件函数中使用 Hook。

如果你在普通函数内使用了 Hook,React 会报错。

当然如果是自定义 Hook(一个使用了 React 内置 Hook 的普通函数),然后放到函数组件内,那也是合法的。

为防止开发者不小心写岔,React 官方还写了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,用于检测不合法的 Hook 调用位置,实在是太贴心了。强烈建议使用。

写自定义 Hook

自定义 Hook,就是使用了内置 Hook 的普通函数。它是对函数组件可复用的部分逻辑的做了一层封装,然后再被函数组件使用。

自定义的 Hook 必须使用 use 开头。因为 React 的官方 ESLint 插件认为 use 开头是自定义 Hook。

如果你不用 use 开头,ESLint 插件就会认为这是一个普通函数,调用时不符合 Hook 规则时不会报错,你就可能写出有问题的代码。

另外,自定义 Hook 下使用的 Hook,也必须位于顶层,这也是为了保证 hook 的顺序多次执行能保持一致。

import React, { useEffect, useState } from 'react'
import zrequest from '../service'

// 自定义钩子函数
export const useUtiles = () => {

  let { role } = JSON.parse(localStorage.getItem('token')) // 个人具备的权限

  // 路由懒加载
  const lazyLoad = (path) => {
    const Comp = React.lazy(() => import(`../views/sendBox/${path}`))
    return (
      <React.Suspense fallback={<>加载中...</>}>
        <Comp></Comp>
      </React.Suspense>
    )
  }

  let routRelation = [
    {
      path: 'home',
      element: lazyLoad('home/Home')
    },
    {
      path: 'user-manage/list',
      element: lazyLoad('user-manager/UserList')
    },
    {
      path: 'right-manage/role/list',
      element: lazyLoad('right-manager/RoleList')
    },
  ]

  let arr = []
  routRelation.map((item) => {
    if (role.rights.includes('/' + item.path)) {
      return arr.push(item)
    }
  })
  return arr
}

posted @ 2022-10-27 20:20  yunChuans  阅读(58)  评论(0编辑  收藏  举报