React Hooks中常用Hooks的用法详解

一、教学讲解视频

教学讲解视频地址:视频地址

二、React Hooks介绍

React Hooks 是 React 16.8 新增的特性,丰富扩展了原有函数组件的功能,让函数组件也有了类组件的一些特性。
同时,React Hooks解决了以下问题:

  • this指向问题:例如每次声明函数都需要手动的去绑定 this ,想要获取 state 和 props 还需要通过 this.state.xxx 和 this.props.xxx 来获取,代码不够简洁。
  • 代码复杂,难以组织:需要事件监听时,需要在组件挂载 (componentDidMount) 时注册事件,在和组件卸载 (componentWillUnmount) 时卸载事件,这种分散的写法很容易遗漏导致忘记卸载事件。
  • 组件之间状态复用困难:类组件中的状态都是通过 state 定义在组件内部没办法抽离的,如果其他组件也需要用到相同的 state 则需要重定义一份。

三、useState用法

useState 是用来解决函数组件中不能定义自己的状态的问题,useState 可以传递一个参数,做为状态的初始值,返回一个数组,数组的第一个元素是返回的状态变量,第二个是修改状态变量的函数

const [state, setState] = useState(initalState); // 初始化,state可以任意命名
// ...
setState(newState); // 修改state的值

类组件中是通过 this.setState 来修改类组件中的状态值的,函数组件中则通过 useState 来修改。

// 代码示例
import {
    
     useState } from 'react';

function Demo() {
    
    
    const [count, setCount] = useState(0);

    const add = () => {
    
    
        setCount(count + 1);
    };

    return (
        <div>
            <button onClick={
    
    add}>+1</button>
            <p>{
    
    `count: ${
     
     count}`}</p>
        </div>
    );
}

export default Demo;

注意:在类组件中 this.setState 是异步执行的,同样 useState 修改状态也是异步的。也就是每次修改状态不是立马生效的。那如何在每次修改状态后可以拿到最新的数据呢?就可以用我们下面讲的useEffect实现。

四、useEffect用法

类组件中放在 componentDidMount,componentDidUpdate 等执行的请求获取数据的操作,在React Hooks中都可以用 useEffect 来处理。

useEffect(() => {
    
    
  // 此处编写 组件挂载之后和组件重新渲染之后执行的代码
  ...

  return () => {
    
    
    // 此处编写 组件即将被卸载前执行的代码
    ...
  }
}, [dep1, dep2 ...]); // 依赖数组

useEffect 可以传入2个参数,第1个参数为我们定义的执行函数,第2个参数是依赖关系(可选参数)。若一个函数组件中定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。

  • 第一个参数中的代码是组件挂载和更新就会执行的代码。
  • return 出去的代码会在组件卸载时才会执行。
  • 依赖数组不是必填项,如果不传则每次渲染都会去执行,传值的话在依赖项发生改变时函数中的代码才会执行,如果传空数组则会在组件第一次挂载才会执行
// 代码示例
import {
    
     useState, useEffect } from 'react';

function Demo() {
    
    
    const [a, setA] = useState(0);

    useEffect(() => {
    
    
    	// 点击一次按钮就会触发一次useEffect
        console.log("执行了useEffect");
        return () => {
    
    
            console.log("组件卸载");
        }
    }, [a]);

    const add = () => {
    
    
        setA(a + 1);
    };

    return (
        <div>
            <button onClick={
    
    add}>add</button>
            <p>{
    
    `a: ${
     
     a}`}</p>
        </div>
    );
}

export default Demo;

五、useLayoutEffect用法

useLayoutEffect 使用方法、所传参数和 useEffect 完全相同。大多数情况下将 useEffect 替换成 useLayoutEffect 完全看不出区别。
唯一区别就是:使用 useEffect 时,页面挂载会出现闪烁。而使用 useLayoutEffect 时页面没有闪烁,是因为 useEffect 是在页面渲染完成后再去更新数据的,所以会出现短暂的闪烁,而 useLayoutEffect 是在页面还没有渲染时就将数据给更新了,所以没有出现闪烁。
注意:大部分情况用useEffect就足够了,useLayoutEffect 会阻塞渲染,所以需要小心的使用。

六、useMemo用法

useMemo 是为了减少组件重新渲染时不必要的函数计算,可以用来做性能优化

const memoizedValue = useMemo(() => {
    
    
  // 计算逻辑
  ...
  // return res;
}, [a, b]);

useMemo 可以传入2个参数,第1个参数为函数,用来进行一些计算,第2个参数是依赖关系(可选参数),返回值为第一个函数 return 出去的值,只有在依赖项发生变化时才会重新执行计算函数进行计算,如果不传依赖项,每次组件渲染都会重新进行计算

// 代码示例
import {
    
     useState, useMemo } from 'react'

function Demo() {
    
    
    const [num, setNum] = useState(0);


    const addNum = () => {
    
    
        setNum(num + 100);
    };

    const total = useMemo(() => {
    
    
        console.log('---求和---');
        // 求和计算
        let temp = 0;
        for(let i = num; i > 0; i--) {
    
    
            temp += i;
        }
        return temp;
    }, [num]);

    return (
        <div>
            <button onClick={
    
    addNum}>addNum</button>
            <p>{
    
    `num: ${
     
     num}`}</p>
            <p>{
    
    `total: ${
     
     total}`}</p>
        </div>
    )
}

export default Demo;

点击修改num的值,total 对应的计算函数会重新执行一遍,因为num是该计算函数的依赖项

七、useCallback用法

返回一个缓存的回调函数

const memoizedCallback = useCallback(
  () => {
    
    
    doSomething(a, b);
  },
  [a, b]
);

useCallback 的用法和 useMemo 完全一样,useMemo 返回的是计算函数 return 出去的,而 useCallback 可以理解成返回的是那个计算函数

// 代码示例
import {
    
     useState, useCallback } from 'react'

function Demo() {
    
    
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const funcHang = useCallback(function() {
    
    
        console.log("function run:", count1, count2);
    }, [count1]);

    return (
        <div>
            <h2>params: {
    
     count1 } {
    
     count2 }</h2>
            <button onClick={
    
     funcHang }>触发</button>
            <button onClick={
    
     e => {
    
     setCount1(pre => pre + 1);} }>update Count1</button>
            <button onClick={
    
     e => {
    
     setCount2(pre => pre + 1);} }>update Count2</button>
        </div>
    )
}

export default Demo;
  • 当count1变化,useCallback内部依赖比较发生变化,返回当前执行Demo上下文包裹的函数。
  • 当count2变化,useCallback内部依赖比较没有变化,返回之前Demo上下文包裹的函数。

八、React.memo()语法

  • 当父组件发生改变时,默认情况下它的子孙组件也会重新渲染,当某些子组件不需要更新时,也会被强制更新,为了避免这种情况,我们可以使用 React.memo
  • 类组件中有 shouldComponentUpdate 和 PureComponent 来避免子组件做不必要的渲染。函数组件中的 React.memo() 也有类似的功能,它和 PureComponent 类似,但是只适用于函数组件,默认情况下仅对 props 进行一个浅比较来决定要不要更新,复杂情况下支持自己手写对比的逻辑
function Demo(props) {
    
    
  // ...
}

function compare(prevProps, nextProps) {
    
    
  // 自己写对比逻辑,返回 true 更新,false 跳过更新
  // return false
}

export default React.memo(Demo, compare)
  • 参数1:组件要渲染的内容。
  • 参数2:写的回调函数,一般情况下都在props传过来的数据为引用类型,才需要手动来判断,如果是基本类型则不需要写参数2来手动判断。
    如果是引用类型,可以用下面方法进行比较
// 使用lodash库来完成对象的值的比较,从而用来完成减少组件的无用的重复渲染
(prevProps, nextProps) => _.isEqual(prevProps, nextProps)

代码示例

import {
    
     useState, useCallback } from 'react'
import * as React from "react";
import _ from 'lodash'

const Child = React.memo(
    ({
    
     count }) => {
    
    
        console.log('child');
        return (
            <div>
                <h3>child组件 -- {
    
    count.n}</h3>
            </div>
        )
    },
    // 使用lodash库来完成对象的值的比较,从而用来完成减少组件的无用的重复渲染
    (prevProps, nextProps) => _.isEqual(prevProps, nextProps)
);

function Demo() {
    
    
    let [count, setCount] = useState({
    
     n: 100 });
    let [name, setName] = useState('张三');

    return (
        <div>
            <h3>App -- {
    
    count.n}</h3>
            <input type="text" value={
    
    name} onChange={
    
    e => setName(e.target.value)} />
            <button
                onClick={
    
    () => {
    
    
                    setCount({
    
     n: count.n + 1 })
                }}
            >
                添加
            </button>
            <Child count={
    
    count} />
        </div>
    )

}

export default Demo;

上面代码中,每次点击添加按钮,子组件props传来的数据发生变化,导致子组件进行重新渲染。而输入框输入内容,子组件props传来的数据未发生变化,子组件不会重新渲染。

九、useRef用法

useRef 可以帮助我们获取 dom 和 react 组件实例,类组件中的 React.createRef() 也有相同的功能。

const xxxRef = useRef(initialValue);
// 使用 xxxRef.current 获取引用的值
// 代码示例
import {
    
     useRef } from 'react'

function Demo() {
    
    
    const inputRef = useRef();

    const handleFocus = () => {
    
    
        // document.getElementById('my-input').focus();
        inputRef.current.value = 'focus';
        inputRef.current.focus();
    }

    const handleBlur = () => {
    
    
        // document.getElementById('my-input').blur();
        inputRef.current.value = 'blur';
        inputRef.current.blur();
    }

    return (
        <div>
            <input ref={
    
    inputRef} id="my-input" />
            <button onClick={
    
    handleFocus}>focus</button>
            <button onClick={
    
    handleBlur}>blur</button>
        </div>
    )
}

export default Demo;

除了用 useRef 获取组件实例,还可以用来存储变量的值,但是需要注意的一点是,修改 .current 的值不会触发组件的重新渲染,请看下面示例:

import {
    
     useState, useRef } from 'react'

function Demo() {
    
    
    const countRef = useRef(0);
    const [num, setNum] = useState(0);

    const addCount = () => {
    
    
        // 使用 useRef 去更新值并不会出发组件渲染
        countRef.current = countRef.current + 1;
    }

    const addNum = () => {
    
    
        // 使用 useState 去更新会触发组件渲染
        setNum(num + 1);
    }

    return (
        <div>
            <button onClick={
    
    addCount}>addCount</button>
            <button onClick={
    
    addNum}>addNum</button>
            <p>{
    
    `count: ${
     
     countRef.current}`}</p>
            <p>{
    
    `num: ${
     
     num}`}</p>
        </div>
    )
}

export default Demo

十、forwardRef用法

forwardRef 可以在父组件中操作子组件的 ref 对象,并且将 ref 对象作为一个参数传递给了子组件。

// 代码示例
import {
    
     useRef, forwardRef } from 'react';

function Child(props, ref) {
    
    
    return (
        <input ref={
    
    ref} />
    );
}

function Demo() {
    
    
    const childRef = useRef();

    const onFocus = () => {
    
    
        childRef.current.focus();
    }

    return (
        <div>
            <Child ref={
    
    childRef} />
            <button onClick={
    
    onFocus}>focus</button>
        </div>
    );
}

Child = forwardRef(Child);

export default Demo;

这行代码是关键,加上后子组件就多了个入参ref,来绑定对应子组件的dom元素。

Child = forwardRef(Child);

十一、useImperativeHandle用法

React 中的数据流是单向的,父组件可以将函数和状态传递给子组件,这样子组件内就可以使用父组件中的变量和函数了,如果我们想在父组件中使用子组件内定义的函数和状态呢? 这就需要用到 useImperativeHandle 这个 hook 了,使用 useImperativeHandle 的时候还需要结合上面讲的 useRef 和 forwardRef 来结合使用,具体使用步骤如下:

  • 在父组件中使用 useRef 创建 ref 引用变量.
  • 使用 forwardRef 将创建的 ref 引用传递到子组件中去.
  • 将子组件中的函数和状态通过 useImperativeHandle 挂载到传递过来的 ref 对象上。
// ref 为父组件传过来的 ref 引用
useImperativeHandle(ref, () => {
    
    
	// return出去的属性都挂载在了父组件传过来的ref对象上,若父组件需要调用子组件内的 xxx函数,则通过 ref.current.xxx()调用
  return {
    
    
		xxx: xxx
	}
}, [deps]) // deps 为依赖数组,可选项
// 代码示例
import {
    
     forwardRef, useState, useImperativeHandle, useRef } from 'react';

function Child(props, ref) {
    
    
    const [count, setCount] = useState(0);

    useImperativeHandle(ref, () => {
    
    
        return {
    
    
            addCount: () => {
    
    
                setCount(count + 1);
            },
            value: 2
        }
    });

    return (
        <div>
            <p>{
    
    `count: ${
     
     count}`}</p>
        </div>
    );
}

Child = forwardRef(Child);

function Demo() {
    
    
    const childRef = useRef();

    return (
        <div>
            <Child ref={
    
    childRef} />
            <button onClick={
    
    () => {
    
    
             	// 调用子组件的addCount方法和获取子组件的value值
                childRef.current.addCount();
                console.log(childRef.current.value);
            }}>add</button>

        </div>
    );
}


export default Demo;

十二、useContext用法

在 React 中传递属性只能一层一层传,如果组件结构比较复杂,层级比较深的时候,数据传递起来就比较麻烦,可能会经过很多次的传递才能将属性传递到目标组件中,那么有没有一种可以在全局进行状态共享的实现方法呢?useContext 就是为了解决这个问题的,可以实现不必层层传递就能共享状态的功能。具体用法看下面步骤:
先封装context.js

import React from 'react';
// React.createContext()中的参数是默认值,可填可不填
const UserContext = React.createContext( {
    
     name: '张三' });
export default UserContext;

在代码中引用上面封装好的context.js

import React, {
    
     useContext } from 'react'
import UserContext from './context';

// const UserContext = React.createContext();

function Demo() {
    
    
	// 如果React.createContext没有指定默认值,也可以在对应子组件上套上UserContext.Provider来指定值
    return (
        // <UserContext.Provider value={
    
    {
    
     name: '张三' }}>
            <Child />
        // </UserContext.Provider>
    )
}



function Child() {
    
    

    const user = useContext(UserContext);
    return (
        <div>
            <p>{
    
    `name: ${
     
     user.name}`}</p>
        </div>
    )
}

export default Demo;

十三、useReducer用法

useReducer 也是用来实现状态管理的 hook,useState 就是基于 useReducer 实现的,useReducer 可以实现比 useState 更复杂的状态管理逻辑。

// 代码示例
import React, {
    
     useReducer } from 'react'

// 1.需要有一个 reducer 函数,第一个参数为之前的状态,第二个参数为行为信息
function reducer(state, action) {
    
    
    switch (action) {
    
    
        case 'add':
            return state + 1;
        case 'minus':
            return state - 1;
        default:
            return 0;
    }
}



function Demo() {
    
    

    // 2.引入useReducer,第一个参数时上面定义的reducer,第二个参数时初始值
    // 3.返回为一个数组,第一项为状态值,第二项为一个 dispatch 函数,用来修改状态值
    const [count, dispatch] = useReducer(reducer, 0);
    return (
        <div>
            <button onClick={
    
    () => {
    
     dispatch('add') }} >add</button>
            <button onClick={
    
    () => {
    
     dispatch('minus') }} >minus</button>
            <button onClick={
    
    () => {
    
     dispatch('unknown') }} >unknown</button>
            <p>{
    
    `count: ${
     
     count}`}</p>
        </div>
    );
}

export default Demo;

猜你喜欢

转载自blog.csdn.net/dgfdhgghd/article/details/128761192
今日推荐