react常用hooks总结

React Hooks常用总结

react常用hooks总结_第1张图片

Hooks简介

概述

Hooks是React16.8的新增特性。它可以让你在不编写class的情况下使用state,以及其他的React特性,用简单的话来说,React
Hooks就是一些React提供的内置函数,这些函数可以让Function Component和Class
Component一样能够拥有组件状态(state)以及进行副作用(side effect)。

优势

  • a. 完全可选的,你无需重新写任何已有的代码就可以在一些组件中尝试Hooks。
  • b. 100%向后兼容,Hooks不包含任何破坏性改动。
  • c. 现在可用,b16.8版本已经发布。

虽然现在有了React Hooks,但是React中的class是没有计划移除的。Class组件之间难以服用状态逻辑,复杂组件难以理解,使用class类组件导致学习成本变高,生命周期逻辑难以维护,同一个生命周期中包含不同逻辑,同一类逻辑分散在不同生命周期。父组件给子组件传递函数时,必须绑定this。

react中的组件四种绑定this的方法区别:

<button onClick={this.handleClick2}>btn1</button>
<button onClick={this.handleClick1.bind(this)}>btn2</button>
<button onClick={() => this.handleClick1()}>btn3</button>
<button onClick={this.handleClick3}>btn4</button>
  • 第一种在构造函数中绑定this,那么每次父组件刷新的时候,如果传递给子组件其他的Props值不变,那么子组件就不会刷新。
  • 第二种是在render()函数里面绑定this,因为bind函数会返回一个新的函数,所以每次父组件刷新时,都睡重新生成一个函数,即使父组件传递给子组件其他的props值不变,子组件每次都会刷新。
  • 第三种是使用箭头函数,父组件刷新的时候,即使两个箭头函数的函数体都是一样的,都会生成一个新的箭头函数,所以子组件每次都会刷新
  • 第四种是使用类的静态属性,原理和第一种差不多,但是比第一种更简洁。

Hooks的优势

  1. 能优化类组件的三大问题。
  2. 能在无需修改组件结构的情况下复用状态逻辑(自定义hooks) 。
  3. 能将组件中相互关联的部分拆分成更小的函数。

副作用的关注点分离,副作用指那些没有发生在数据向视图转换过程中的逻辑,入Ajax请求,访问原生dom元素,本地持久化缓存,绑定/解绑时间,添加订阅,设置定时器,记录日志,以往这些副作用都是写在类组件生命周期函数中的,而useEffect在全部渲染完毕后才会执行,userLayoutEffect会在浏览器layout之后,painting之前执行。

Hooks使用注意事项

  1. 只能在函数内部的最外层调用hooks,不要再循环,条件判断或者子函数中调用。
  2. 只能在React的函数组件中调用Hook,不要再其他JS函数中调用。

Hooks功能概览

在 React 的世界中,不同的 hooks 用处也是不同的,对 React hooks 按照功能分类,分成了 数据更新驱动,状态获取与传递,执行副作用,状态派生与保存,和工具类型, 具体功能划分和使用场景如下图:
react常用hooks总结_第2张图片

常见的Hooks

1. userState

useState可以让函数组件像类组件那样拥有state,函数组件通过userState可以让组件重新渲染,更新视图。

const [xxx,setxxxx] = React.useState(initValue)

参数一、xxx,目的提供给UI,座位渲染视图的数据源。
参数二、setxxx,改变state的函数推动函数组件渲染的渲染函数。
参数三、initValue,两种类型,第一种情况是非函数,将作为state初始化的值,第二种情况是函数,函数返回值作为useState初始化的值。

使用示例

import React,{useState} from "react"
function User(){
  const [count,setCount] = useState(5) // 默认值为0
  return (
    <div>
      count:{count}
      <br/>
      {/* 使setCount让count+1操作 */}
      <button onClick={ ()=> {setCount(count+1)} } >改变count</button>
    </div>
  );
}
export default User
userState使用注意:
  • a.在函数组件执行一次上下文中,state的值不变。
  • b.如果两次setxxx传入的值相同,那么组件就不会更新。
  • c.当触发setxxx在当前执行上下文中获取不到最新的state,只有在下一次组件render中才能获取到。

2. useReducer

在使用React的时候,如果遇到了状态管理,一般会用到Redux,而React本身不提供状态管理的,但是useReducer提供了状态管理。userReducer是react-hooks提供的能够在无状态组件中运行的类似redux的功能api。

const [state,dispatch] = useReducer(reducer,initialState)

参数一、state更新之后的state值。
参数二、派发更新的dispatchAction函数,本质上和useState的dispatchAction是一样的。
参数三、一个函数reducer,我们可以认为他就是一个redux中的reducer,reducer的参数就是常规的reducer里面的state和action,返回改变后的state,这里需要注意的是,如果返回的state和之前的state,内存指向相同,那么组件将不会更新。

userReducer基本使用1:

import { useReducer } from "react";
/* 当state需要维护多个数据且它们互相依赖时,推荐使用useReducer
组件内部只是写dispatch({...})
处理逻辑的在useReducer函数中。获取action传过来的值,进行逻辑操作
*/

// reducer计算并返回新的state
function reducer(state, action) {
  const { type, nextName } = action;
  switch (type) {
    case "ADD":
      return {
        ...state,
        age: state.age + 1
      };
    case "NAME":
      return {
        ...state,
        name: nextName
      };
  }
  throw Error("Unknown action: " + action.type);
}

export default function ReducerTest() {
  const [state, dispatch] = useReducer(reducer, { name: "wuyong", age: 12 });
  function handleInputChange(e) {
    dispatch({
      type: "NAME",
      nextName: e.target.value
    });
  }
  function handleAdd() {
    dispatch({
      type: "ADD"
    });
  }
  const { name, age } = state;
  return (
    <>
      <input value={name} onChange={handleInputChange} />
      <br />
      <button onClick={handleAdd}>添加1</button>
      <p>
        Hello,{name}, your age is {age}
      </p>
    </>
  );
}

效果:
react常用hooks总结_第3张图片

userReducer基本使用2:

import React,{useReducer} from "react"
const User = ()=> {
  const [num,dispatchNum] = useReducer((state,action)=>{
    const { resetNum , name } = action
    switch(name){
      case "add":
        return state+1        
      case "sub":
        return state-1        
      case "reset":
        return  resetNum
    }
    return state
  },0)
  return (
    <div>
      count:{num}
      <br/>
      <button onClick={()=>dispatchNum({ name:'add' })} >增加</button>
      <button onClick={()=>dispatchNum({ name:'sub' })} >减少</button>
      <button onClick={()=>dispatchNum({ name:'reset' ,resetNum:0 })} >重置</button>

    </div>
  );
}
export default User

效果:
react的userReducer

3. useSyncExternalStore

useSyncExternalStore 能够让React组件在concurrent模式下安全地有效地读取外界数据源,在组件渲染过程中能够检测到变化,并且在数据源发生变化时侯,能够调度更新。当读取到外部状态发生了变化,会触发一个强制更新,来保证结果的一致性。

useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot)

参数一、subscribe为订阅数,当数据发生改变的时候,会触发subscribe,在useSyncExternalStore会通过带有记忆性的getSnapshot来判别数据是否发生变化,如果发生变化,那么会强制更新数据。
参数二、getSnapshot可以理解成一个带有记忆功能的选折起,当store变化的时候,会通过getSnapshot生成新的状态值,这个状态值可提供给组件数据源使用,getSnapshot可以检查订阅的值是否发生改变,改变的话那么会触发更新。
参数三、getServerSnapshot用于hydration模式下的getSnapshot。

4. useEffect

当组件init,dom render完成操纵dom,请求数据(如componentDidMount)等;不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate与componentWillreceiveprops;

useEffect(()=>{return destory},dep)

参数一、第一个参数callback回调,返回的desotry作为下一次callback执行之前调用,用于清楚上一次callback产生的副作用。第一个参数为处理事件,第二个参数为接受数组,为限定条件,当数组发生变化时,触发事件,为数组只在组件初始化时触发。
参数二、作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次回调返回的destory,和执行新的effect第一个参数callback。

对于useEffect执行,react处理逻辑是采用异步调用,面对每一个effect的callback,react会向setTimeout回调函数一样,放入任务列队,等到主线程任务完成,DOM更新,js执行完成,绘图绘制完成才执行,所以effect回调函数不会阻塞浏览器绘制视图。
注意:useEffect⽆法直接使⽤async await

import React, { useState, useRef } from "react"
import { useEffect } from "react"
/* 模拟数据交互 */
function getUserInfo(a) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: a,
        age: 16
      })
    }, 500)
  })
}
const SwitchPage = ({ a }) => {
  const [userMessage, setUserMessage] = useState({})
  const div = useRef()
  const [number, setNumber] = useState(0)
  // 模拟事件处理函数
  const handleResize = () => { }
  // useEffect使用
  useEffect(() => {
    // 请求数据
    getUserInfo(a).then(res => {
      console.log("请求数据")
      setUserMessage(res)
    })
    // 定时器,延时器
    const timer = setInterval(() => console.log("定时器", 666), 1000)
    // 操作dom
    console.log(div.current)
    // 事件监听
    window.addEventListener('resize', handleResize)
    // 消除副作用,定时器,延时器
    return () => {
      /*
          只有当props->a和state->number改变的时候 ,useEffect副作⽤函数重新执⾏ ,
          如果此时数组为空[],证明函数只有在初始化的时候执⾏⼀次相当于componentDidMount
      */
      clearInterval(timer)
      window.removeEventListener('resize', handleResize)
    }
  }, [a, number])
  return (<div ref={div} >
    <span>{userMessage.name}</span>
    <span>{userMessage.age}</span>
    <div onClick={() => setNumber(1)} >{number}</div>
  </div>)
}
export default SwitchPage

react常用hooks总结_第4张图片

执行功能:

1.操作DOM
2.请求数据
3.注册事件监听器, 事件绑定
4.清除定时器,事件解绑

5.useLayoutEffect

渲染更新之前的,如果你希望界面必须在执行完一些业务代码后才进行渲染,那么可以把代码放到useLayoutEffect的effect中。首先useLayoutEffect在DOM更新之后,浏览器绘制之前,这样可以方便修改DOM,获取DOM信息,这样浏览器智慧绘制一次,如果修改DOM布局放在useEffect,那么useEffect执行是在浏览器绘制视图之后,接下来又更改DOM,这样会导致浏览器再次回流和重绘,而且由于这两次重绘,视图上可能会照成闪现突兀的效果。

  • useEffect : 组件更新挂在完成->浏览器绘制完成->执行useEffect回调。
  • useLayoutEffect : 组件更新挂载完成 -> 执⾏useLayoutEffect回调-> 浏览器dom 绘制完成;callback中代码执行会阻塞浏览器绘制。
import React, { useState, useRef } from "react"
import { useEffect,useLayoutEffect } from "react"
const SwitchPage = () => {
  const [count,setCount] = useState(0)
  useEffect(()=>{
    if(count===0){
      const randomNum = 111
      setCount(111)
    }
  },[count])
  useLayoutEffect(()=>{
    if(count === 0){
      const randomNum = 111
      setCount(111)
    }
  },[count])
  return (
  <div>
    <div onClick={()=>setCount(0)}>{count}</div>
  </div>)
}
export default SwitchPage

6. useRef

用来获取元素,缓存数据,入参可以作为初始值。

import React, { useState, useRef } from "react"
import { useEffect,useLayoutEffect } from "react"
const SwitchPage = () => {
  const dom = useRef(null)
  const handerSubmit = () =>{
    console.log(dom.current)
  }
  return (
  <div>
    <div ref={dom} >表单组件</div>
    <button onClick={()=>handerSubmit()}>提交</button>
  </div>)
}
export default SwitchPage

7. useContext

const contextValue = useContext(context)

用来获取父级组件传递过来的context值,这个当前值就是最近的父级组件Provider的value;可以使用useContext来获取父级组件传递过来的context值,这个当前值就是最近的父级组件,Provider设置的value值,useContext参数一般是由createContext方式创建的,也可以父级上下文context传递的【参数为context】。useContext可以替代context.Consumer来获取Provider中保存的value值。
当数据进行(父子或者爷爷传递孙子时)多成绩数据传递的时候,在函数组件中我们可以运用useContext进行数据传输。

举个简单的demo例子,子组件通过按钮修改爷爷组件中的num值:
目录结构:
react常用hooks总结_第5张图片

ContextDemo代码:

import React , {createContext} from 'react'

const DemoContext = createContext()

export default DemoContext

GrandDad代码:

import React,{useState} from "react"
import Father from './Father'
import DemoContext from "./ContextDemo"
function GrandDad(){
  const[num,setNum] = useState(0)
  const handleAddClick=()=>{
    setNum(num=>num+1)
  }
  const handleSubClick=()=>{
    setNum(num=>num-1)
  }
  return <div style={{background:"yellow"}}>
    爷爷组件
    <DemoContext.Provider value={{num,handleAddClick,handleSubClick}}>
      {num}<br/>
      <Father></Father>
    </DemoContext.Provider>
  </div>
}
export default GrandDad

Father代码:

import React  from "react"
import Son from './Son'
function Father(){
  return <div style={{background:"red"}}>
    爸爸组件<br />
  <Son></Son>
  </div>
}
export default Father

son代码:

import React,{useContext}  from "react"
import DemoContext from './ContextDemo'
function Son(){
  const variable = useContext(DemoContext)
  console.log("儿子组件",variable)
  return <div style={{background:"pink"}}>
    儿子组件<br />
    <div>
      <button onClick={variable.handleSubClick}>-</button>
      {variable.num}
      <button onClick={variable.handleAddClick}>+</button>
    </div>
  </div>
}
export default Son

效果:
react常用hooks总结_第6张图片
react常用hooks总结_第7张图片

8. useMemo

useMemo它是用来作为一个缓存使用,只有当一个依赖项改变的时候才会发生变化,否则拿缓存的值,就不用在每次渲染的时候在做计算,这种有助于避免在每次渲染的时候进行高开销计算,uerMemo的函数在函数渲染期间执行,所以不该在此期间做的操作请去除,如果没有提供依赖项数据,每次都会重新计算值,相当于没有优化了。

  • 使用场景一、当登录之后,你的个人信息是不会变的,当你退出登录,重新输入另外一个人的账号密码之后,这个时候个人信息就会改变了,那这样我就可以把账号和密码作为两个依赖项,当他们改变的时候,就更新个人信息,否则拿缓存的值,宠儿达到优化的目的。
  • 使用场景二、有时候需要从A页面跳转到B页面,并携带一些参数,那么就会从路由下手,http://localhost:3000/home/:id/:name或者http://localhost:3000//home?id=123&name=wy,然后B页面基于这些参数做一些查询操作,那路由不变,其实这些查询出来的数据应该是不变的(当然其中包含增删改操作除外),那就可以把这些路由参数当作依赖项,只有依赖项变了,该页面的数据才会变。

简单demo例子

import React,{useState,useMemo} from "react"
function Demo2(){
  const [count,setCount] = useState(10)
  const [value,setValue] = useState(10)
  let memoMessage = useMemo(()=>{
    console.log("useMemo....")
    return `memoMessage will change with ${count}`
  },[count])
  function hangleClick(name){
    if(name==="count"){
      setCount(count + 1)
      setValue(value + 1)
    }else{
      setValue(value + 1)
    }
  }
  return <div>
    <p>{memoMessage}</p>
    <h3>count:{count}</h3>
    <h3>value:{value}</h3>  
    <br/>
    <button onClick={()=>{hangleClick("count")}}>count</button>
    <button onClick={()=>{hangleClick("value")}}>value</button>
  </div>
}
export default Demo2

react常用hooks总结_第8张图片
react常用hooks总结_第9张图片

9. useCallback

函数相等性检查,使用useCallback优化代码,是对传过来的回调函数优化,返回的是一个函数,useMemo可以返回任何值,函数,对象等都可以,简单的来说就是返回一个函数,只有在依赖项发生变化的时候才会更新返回一个函数。

React.useCallback(function,array)

使用场景:当父组件传入子组件函数时,由于React.memo进行的是浅比较,重新渲染时,函数的引用是发生改变的,所以会导致子组件重新渲染,而用useCallback后只要依赖的变量未发生改变将始终返回同一个函数引用,不会导致子组件重新渲染。

注意区别于useMemo 缓存的是函数的返回结果。useCallback缓存的是函数。
使用案例:

import React, { useCallback, useState, useEffect } from 'react';
function Demo_useCallback() {
  const [query, setQuery] = useState(1);
  const [queryOther, setQueryOther] = useState(1);
  const fecthData = useCallback(() => {
    console.log('新的fetch');
    return query;
  }, [query])
  const add = () => {
    console.log('点击add');
    setQuery(query + 1);
  }
  const addOther = () => {
    console.log('点击addOther');
    setQueryOther(queryOther + 1);
  }
  return (
    <div>
      <Child fecthData={fecthData} />
      <button onClick={add}>+1</button>
      <button onClick={addOther}>other+1</button>
      father
      <div>{ query }</div>
    </div>
  );
}
const Child = React.memo((props)=>{
  console.log('子组件相关内容');
  useEffect(() => {
    const querN = props.fecthData();
    console.log('子组件调用该函数获取到相关内容', querN);
  }, [props.fecthData])
  return <div>
    Child
  </div>
}
)
export default Demo_useCallback;

react常用hooks总结_第10张图片

React.memo检测的是props中数据的栈地址是否改变。而父组件重新构建的时候,会重新构建父组件中的所有函数(旧函数销毁,新函数创建,等于更新了函数地址),新的函数地址传入到子组件中被props检测到栈地址更新。也就引发了子组件的重新渲染。所以,在上面的代码示例里面,子组件是要被重新渲染的。上文中的fetchData因为失去了useCallback的保护使得子组件的props发生了变化,从而React.memo也失去了作用,而且因为fetchData因为失去了useCallback的保护,使得点击other+1按钮改变无关的变量时,子组件也调用了请求函数。

以上就是对react常用hooks进行的总结。

你可能感兴趣的:(React,react.js,前端,前端框架,javascript)