React hooks使用笔记

325 阅读6分钟

image.png

Hook使用规则

Hook就是JavaScript函数,但有两个额外的规则:

  • 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用。
  • 只能在React的函数组件中调用,不能在其他JavaScript函数中调用。
  • 或者在自定义Hook中

useState: React组件管理自身的数据状态

参数:useState的参数是初始化state,只在初始化中起作用,后续渲染会被忽略。

返回值:useState会返回一对值,当前状态和一个让你更新它的函数。

执行时机:初始化渲染期间,state值为初始化值。重新渲染时,是更新后最新的值。

举个例子:声明一个state变量,并实现点击按钮自加。

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量  
  const [count, setCount] = useState<number>(0);
  return (
  <button onClick={() => setCount(count + 1)}>Click me</button>
  );
}

useEffect & useLayoutEffect: React副作用

大多数时候应该使用useEffect,当它出问题的时候再尝试使用 useLayoutEffectuseLayoutEffect用到的场景很少,比如状态更新导致屏幕闪烁,这时可以尝试使用useLayoutEffect

副作用:React组件中执行过数据获取、订阅或手动修改Dom,这些操作成为“副作用”。

参数:第一个参数,useEffect接受一个包含命令式、且可能有副作用代码的函数。第二个参数,是effect所依赖的值数组。

返回值:useEffect返回函数在组件卸载时执行,清除effect订阅或计时器等。

执行时机

  • 传给useEffect的函数在浏览器完渲染后调用,在任何新的渲染前执行,执行是异步的,非阻塞的。
  • 传给useLayoutEffect的函数在浏览器渲染完成前调用,执行是同步的,阻塞的。

useEffect 举个例子:组件初始化完成获取数据,并给数据的载体赋值。

import React, { useEffect, useState } from "react";
import axios from "axios";

export default function () {
    const [data, setData] = useState<string>("");

    const getData = async () => {
        const res: any = await axios("http://xxxx");
        setData(res);
    }
//初始化渲染时获取数据,第二个参数传递[],告诉effect不依赖于props或state任何值,不需要重新执行
    useEffect(() => {getData()},[])

    return (<>{data.msg}</>)
}

useContext: 结合React.createContext实现跨组件共享默认数据

参数:context接受一个context对象作为共享数据。当前的context值由上层组件中距离最近<MyContext.Provider> 的 value prop 决定。

返回值:context当前值。

执行时机:组件上层最近的<MyContext.Provider>更新时重新触发渲染,并使用最新的value。

举个例子:父组件穿过子组件,给孙子组件传递设定好的默认颜色值。

image.png

//Parent.tsx
import { createContext } from "react";
import SonPage from "./SonPage";

export interface ThemeValue {
    foreground: string;
    background: string;
}

export interface ThemeContextValue {
    dark: ThemeValue;
}

const themes = {
    dark: {
        foreground: "#ffffff",
        background: "#222222"
    }
};
//创建上下文的容器,设置共享默认数据
export const ThemeContext = createContext<ThemeValue>(themes.dark)
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
export default function () {
    return (<>
        <ThemeContext.Provider value={themes.dark}>
            <h2>父页面Parent.tsx</h2>
            <SonPage />
        </ThemeContext.Provider>
    </>)
}
//SonPage.tsx
import React from "react";
import GrandsonPage from "./GrandsonPage";
// 中间的组件再也不必指明往下传递
export default function SonPage() {
    return (<div><h3>子页面SonPage.tsx</h3><GrandsonPage/></div>)
}
//GrandsonPage.tsx
import React, { createContext, useContext } from "react";
import { ThemeContext } from "./Parent";

export default function GrandsonPage() {
//useContext获取context
    const theme = useContext(ThemeContext);
    return <div style={{ background: theme.background, color: theme.foreground }}>孙子页面GrandsonPage.tsx</div>
}

useReducer:处理复杂state对象局部数据流

参数:接收三个参数,

  • 第一个参数是更新状态的回调函数updateState(updateState接收两个参数,第一个参数preState前一次计算完成后的状态,第二个参数action通过dispatch调用传递过来的参数)。
  • 第二个参数stateInitValue初始化传入的参数值。
  • 第三个参数stateInitCallback对初始值进行首次计算的函数(stateInitCallback接收一个对象,返回一个state对象。)

返回值:返回一个数组,索引值为0的是更新后的state,索引值为1的是dispatch对象,用来在执行 updateStateCallback 的函数。

执行时机:组件初始化时。

举个例子:reducer中逻辑更加清晰。

const ComponentB = memo(function (props: { dispatch: any }) {
    const { dispatch } = props;
    const dateBegin = Date.now();
    while (Date.now() - dateBegin < 609) { }
    console.log("render done");
    return (<div onClick={() => { dispatch({ type: 'log' }) }}>wwwww</div>)
})

function reducer(state: any, action: { type: string; preload: string; }) {
    switch (action.type) {
        case 'update':
            return action.preload;
        case 'log':
            console.log(`Text: ${state}`);
            return state;
    }
}
export default function Index() { 
    const [text,dispatch] = useReducer(reducer,'init')

    return (<>
        <input value={text} onChange={ e=>dispatch({type:'update',preload:e.target.value})}/>
        <ComponentB dispatch={dispatch}/>
    </>)
}

useRef && useImperativeHandle:实现父子组件通信

useRef

参数:接收一个参数为.current属性默认初始值。

返回值:返回一个可变的ref对象,在组件整个生命周期内保持不变。

useImperativeHandle

参数

  • 第一个参数定义current对象的ref
  • 第二个参数是一个函数,返回值是一个ref的current对象
  • 第三个参数为依赖的状态,依赖变化,会重新调用

useImperativeHandle 应当与 forwardRef 一起使用,使父组件的ref穿透过来。

举个例子:实现父组件调用子组件的方法

/* 父组件 Parent.tsx */
import React, { useRef } from "react";
import { Button } from "antd";
import TableEdit from "./component/TableEdit";

export default function () {
   //此处useRef请注意,因为我的项目是ts的,后面必须要加类型约束,否则在使用的时候会报错
    const tableEditRef = useRef<{ setVisibleStat: (val: boolean) => void }>(null);
    const handleAdd = () => {
    //就是这就是这,上面如果没有类型约束,current.setVisibleStat就会报错
        tableEditRef.current!.setVisibleStat(true);
    };

    return (<>
        <Button type="link" onClick={handleAdd}>
            新增
        </Button>
        <TableEdit ref={tableEditRef} />
    </>)
}
/* 子组件 TableEdit.tsx */
import { Modal } from "antd";
import React, { forwardRef, useImperativeHandle, useState } from "react";

function TableEdit (props:object,ref: React.Ref<unknown>) {
    const [visible, setVisible] = useState<boolean>(false);
    useImperativeHandle(ref, () => ({
        setVisibleStat: (val: boolean) => {
            setVisible(val);
        }
    }))

    return (
      <Modal
        visible={visible}
        onOk={() => setVisible(false)}
        onCancel={() => setVisible(false)}
      >
        <p>内容区域</p>
      </Modal>
    );
}

export default forwardRef(TableEdit);

useCallback & useMemo:性能优化-缓存数据状态,依赖的数据变化,重新计算结果

参数:第一个参数为回调,第二个参数为要依赖的数据。

返回值

  • useCallback计算结果是一个函数,一般用于需要缓存函数式组件来提高性能,避免不需要的state变化导致整个组件刷新;
  • useMemo返回值为缓存计算结果的值,一般用于需要计算的场景。 useCallback举个例子:监听状态的改变,减少不必要的组件更新。一般结合React.memo进行优化。
function Button(props: { handleClick: any; children: any; }) {
    const { handleClick, children } = props;
    console.log('Button -> render');

    return (
        <button onClick={handleClick}>{children}</button>
    )
}

const MemoizedButton = React.memo(Button);

export default function Index() {
    const [clickCount, increaseCount] = useState(0);

    const handleClick = useCallback(() => {
        console.log('handleClick');
        increaseCount(clickCount + 1);
    }, [clickCount]);

    return (
        <div>
            <p>{clickCount}</p>
            <MemoizedButton handleClick={handleClick}>Click</MemoizedButton>
        </div>
    )
}

useMemo举个例子:监听状态的改变,减少不需要的函数调用。

function ComponentA({ name, children }: { name: string, children:string}) {
    function changeName(name:string) {
        console.log('11')
        return name + '改变name的方法'
    }

    const otherName = useMemo(() => changeName(name),[name])
    return (
        <>
            <div>{otherName}</div>
            <div>{children}</div>
        </>

    )
}

export default function ReduxDemo() {
    const [name, setName] = useState('名称');
    const [content, setContent] = useState('内容');
    return (
        <>
            <button onClick={() => setName(new Date().getTime()+"")}>name</button>
            <button onClick={() => setContent(new Date().getTime()+"")}>content</button>
            <ComponentA name={name}>{content}</ComponentA>
        </>
    )
}

useDebugValue:配合React 开发者工具使用

参数:第一个参数为监听的状态,第二个参数一个回调函数,将自身的value作为形参,处理后再return出去一个格式化后的值。

返回值:监听状态的值。

function useFriendStatus() {
    const [isOnline, setIsOnline] = React.useState(true);

    React.useEffect(() => {
        const interval = setInterval(() => {
            setIsOnline(isOnline => !isOnline);
        }, 1000);
        return () => clearInterval(interval);
    }, []);
    React.useDebugValue(isOnline ? "Online" : "Offline");
    return isOnline;
}

export default function () {
//调用下使用useDebugValue的hook
    const isOnline = useFriendStatus();
}

在开发者工具中查看效果

GIF 2022-2-10 11-20-34.gif