随便写写之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>
  )
}
相关推荐
疯狂的沙粒9 分钟前
Vue 前端大屏做多端屏幕适配时,如何让其自动适配多种不同尺寸的屏幕?
前端·javascript·vue.js
范小多13 分钟前
24小时学会Python Visual code +Python Playwright通过谷歌浏览器取控件元素(连载、十一)
服务器·前端·python
ooolmf13 分钟前
matlab2024读取温度01
java·前端·javascript
打工人小夏15 分钟前
前端vue3项目使用nprogress动画组件,实现页面加载动画
前端
一颗宁檬不酸16 分钟前
前端农业商城中产品产地溯源功能的实现
前端
李少兄24 分钟前
深入理解前端中的透视(Perspective)
前端·css
江公望33 分钟前
HTML5 History 模式 5分钟讲清楚
前端·html·html5
云和数据.ChenGuang40 分钟前
Zabbix Web 界面安装时**无法自动创建配置文件 `zabbix.conf.php`** 的问题
前端·zabbix·运维技术·数据库运维工程师·运维教程
码界奇点42 分钟前
Java Web学习 第15篇jQuery万字长文详解从入门到实战解锁前端交互新境界
java·前端·学习·jquery
前端老曹1 小时前
vue3 三级路由无法缓存的终极解决方案
前端·javascript·vue.js·vue