随便写写之React Hooks用法实践

前言

react 官方16.8版本提供了包括 useStateuseEffectuseContext等等在内的常用的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 会在组件卸载时执行,通常用于监听 addEventListenerremoveEventListener 上。

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>&nbsp;&nbsp;&nbsp;
      <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 对象需要获取的内容

使用:

  1. 用来缓存数据,保证获取的数据是最新值
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>
  )
}
  1. 用来获取当前元素,操作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>
  )
}
相关推荐
咖啡の猫2 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲4 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5815 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路5 小时前
GeoTools 读取影像元数据
前端
ssshooter5 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry6 小时前
Jetpack Compose 中的状态
前端
dae bal7 小时前
关于RSA和AES加密
前端·vue.js
柳杉7 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog7 小时前
低端设备加载webp ANR
前端·算法
LKAI.7 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi