likes
comments
collection
share

React中这几个常用的自定义Hook,你学会了吗?

作者站长头像
站长
· 阅读数 462

useSetState

React还没有推出hooks之前,我们写React组件都是class组件,在class中,我们更新状态的方法是setState,而setState是会自动帮我们将多个状态进行合并处理的,什么意思呢?

当我们在某个方法中使用setState修改数据的时候,React会自动将其它不需要修改的值进行保存,后续我们用到的时候还是之前没有修改过的值,而现在在使用useState这个hook时,如果我们设置的状态是一个对象,我们还需要先将没有变化的数据重新赋值回来,这样后面才能用到,类似下面这样的:

// hook 组件
const [state, setState] = useState({
    name: 'zhangsan',
    age: 18,
});

// 当我们需要修改name,并且不改变age时,我们需要这么写
setState((prev) => {
    ...prev,
    name: 'xxxx'
});

上面的伪代码是在hook中需要更新多个状态时实现的代码,而class组件中只需如下这样实现即可:

// class 组件
// 初始化数据
this.state = {
    name: 'zhangsan',
    age: 18
};

// 修改数据时,我们只需要改变需要修改的值即可
this.setState({
    name: 'xxxx'
});

通过对比class组件和hook组件,我们发现如果还是class组件中操作数据的方式更简单一些,那么在hook中我们该如何实现类似class组件中的效果呢?

这时候就该useSetState登场了。useSetState顾名思义,就是更新组件的状态,官方的介绍如下:

管理 object 类型 state 的 Hooks,用法与 class 组件的 `this.setState` 基本一致。

具体的API地址在这里,下面我们就一起来用一下这个方法,看看是不是比直接使用useState要方便。

import { useSetState } from 'ahooks';

interface StateTypes {
    name: string;
    age: number;
}

const Demo = () => {
    const [state, setState] = useSetState<StateTypes>({
        name: 'zhangsan',
        age: 18
    });
    
    return (
        <div>
            <p>我的名字是{state.name},我今年{state.age}岁</p>
            <button onClick={() => setState({ name: 'lisi' })}>点击修改名字</button>
            <button onClick={() => setState({ age: 20 })}>点击修改年龄</button>
        </div>
    )
}

具体的效果可以狠戳这里

这样实现的方式是不是比用useState要简单很多呢?当然我们光知道怎么使用还不行,我们还需要知道它是如何实现的,下面我们就一起来看一下它的源码吧!

import { useCallback, useState } from 'react';
import { isFunction } from '../utils';

export type SetState<S extends Record<string, any>> = <K extends keyof S>(
    state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null),
) => void;

const useSetState = <S extends Record<string, any>>(
    initialState: S | (() => S),
): [S, SetState<S>] => {
    const [state, setState] = useState<S>(initialState);
    
    const setMergeState = useCallback((patch) => {
        setState((prevState) => {
            const newState = isFunction(patch) ? patch(prevState) : patch;
            return newState ? { ...prevState, ...newState } : prevState;
        })
    }, []);
    
    return [state, setMergeState];
};

export default useSetState;

上面的代码中,首先向外部导出了一个SetState,它的参数是一个泛型S,而S的类型其实就是key为string类型,value为any类型的对象,意思就是我们可以设定一个对象为它的初始值,而这个对象的key只能字符串,值可以是任意类型。

再来看一下内部的实现,其实也是用的useState,而我们在外面之所以可以直接修改某个状态,不需要自己手动去获取之前的状态,其实也是内部帮我们实现了一个setMergeState。在这个代码中,用到setState,它帮我们将对象中的所有数据都展开,然后帮我们做了数据的合并,这其实就是useSetState能够这么方便的原理,看到这里,你学会了吗?

useUpdateEffect

除了前面的useSetState,我们在实际的开发中使用useUpdateEffect的频次也很高,那么useUpdateEffect有什么用途呢?官方的介绍是这样的:

`useUpdateEffect` 用法等同于 `useEffect`,但是会忽略首次执行,只在依赖更新时执行。

具体的API地址在这里,下面我们就一起来用一下这个方法,然后对比一下使用useEffect的区别。

// ...other code

const [form] = Form.useForm();

const initData = useMemo(() => {
    if (!visible) {
        return {};
    }
    return record;
}, [record, visible]);

const handleSubmit = useCallback(() => {
    const data = form.getFieldsValue();
    onOk(data);
}, [onOk, form]);

useUpdateEffect(() => {
    if (visible) {
        form.resetFields();
    }
}, [visible]);
// ...other code

在上面的代码中,只有当弹窗组件的visible发生变化时我们才会去清理表单form中的数据,但是第一次这个组件执行的时候是不会执行useUpdateEffect里面的方法的,顾名思义只有当状态发生变化(更新)时才会执行相应的代码。

具体的执行效果可以狠戳这里,如下图所示:

React中这几个常用的自定义Hook,你学会了吗?

当点击修改信息按钮时:

React中这几个常用的自定义Hook,你学会了吗?

修改完成后页面显示的数据也会更新,具体可以自己点击尝试一下。

下面我们也一起来看一下官网的源码是如何实现的吧,具体的源码地址在这里,具体的代码如下:

// useUpdateEffect
import { useEffect } from 'react';
import { createUpdateEffect } from '../createUpdateEffect';

export default createUpdateEffect(useEffect);

useUpdateEffect内部的代码只有三行,但是它引入了一个createUpdateEffect来实现的,那createUpdateEffect内部是如何实现的呢?我们再一起来看一下吧,代码如下:

// createUpdateEffect
import { useRef } from 'react';
import type { useEffect, useLayoutEffect } from 'react';

type EffectHookType = typeof useEffect | typeof useLayoutEffect;

export const createUpdateEffect: (hook: EffectHookType) => EffectHookType = (hook) => (effect, deps) => {
    const isMounted = useRef(false);
    
    hook(() => {
        return () => {
            isMounted.current = false;
        };
    }, []);
    
    hook(() => {
        if (!isMounted.current) {
            isMounted.current = true;
        } else {
            return effect();
        }
    }, deps);
}

上面的代码我们主要看的就是内部的isMounted,通过useRef保存了一个状态,用于判断是否是初始化加载,这里用useRef为啥不用useState呢?在这段代码中,通过判断isMounted的状态,来决定是否需要去执行副作用effect,只有当依赖性deps发生改变时,才会重新去执行,但是第一次加载的时候不会运行,这就是这个函数的基本作用了。

最后

写到这里,关于ahooks中常用的方法就介绍完毕了,当然ahooks中还有很多平时很常用的方法,后续会继续分享给大家,如果这篇文章中有什么地方写的不对的地方,欢迎大家在留言区进行讨论。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

往期回顾

React中这几个好用的Hook你还不会吗?快来学习一下

React中自定义hook,提高开发效率,你学会了吗?