随便写写之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>
  )
}
相关推荐
麒麟而非淇淋27 分钟前
AJAX 入门 day1
前端·javascript·ajax
2401_8581205329 分钟前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢33 分钟前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写2 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.2 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html
快乐牌刀片882 小时前
web - JavaScript
开发语言·前端·javascript
miao_zz3 小时前
基于HTML5的下拉刷新效果
前端·html·html5
Zd083 小时前
14.其他流(下篇)
java·前端·数据库
藤原拓远3 小时前
JAVAWeb-XML-Tomcat(纯小白下载安装调试教程)-HTTP
前端·firefox
重生之我在20年代敲代码3 小时前
HTML讲解(一)body部分
服务器·前端·html