React Hooks学习

React Hooks

useState

很常用的一个hook,可以保存函数组件中的状态

参数

useState(val)
传进去的是初始状态的值
useState(true) usestate(50) useState('aaa')
也可以是一个函数,会自动拿到函数的返回值

useState(() => {
    ...
    const res = a + b;
    return res;
})

返回

useState() 会返回一个数组,数组的第一项是当前的初始状态,第二项是个函数,用来设置这个状态

作用:

为函数提供状态,可以为保存状态,并提供最新的值
一个简单的计数例子

import { useState } from "react"
export default function HooksUseState() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <button onClick={() => setCount(count + 1)}>{count}</button>
        </div>
    );
}

执行过程:
useState会返回一个数组,数组的第一项是当前的状态,第二项是设置这个状态(设置一个新值,引用类型的值注意)
组件一开始加载的时候,会先把 count 的初始值展示在页面上
然后执行点击事件 onCLicksetCount更新了 count
count 一更新,整个函数组件就会重新执行一遍,但是 count 拿到的值是更新后的值,保存了之前的状态

特性:

  • 只能在函数的主体中使用 useState,不能在if/for 等其他函数中使用
    因为react按照hooks的调用顺序识别每一个hook
  • 每个useState之间相互不影响
    并且如果对个useState一起执行的话,函数会合并更新为一次
    下面是多个useState的例子
    export default function HooksUseState() {
        const [count, setCount] = useState(0);
        const [flag, setFlag] = useState(false);
        const [arrList, setArrList] = useState([]);
        const handleClick = () => {
            setCount(count + 1);
            setFlag(!flag);
            setArrList(['张三']);
        };
        console.log(111); // 只会打印一次,说明函数执行了一次
        return (
            <div>
                <button onClick={handleClick}>{count}</button>
                <div>count: {count}</div>
                <div>flag: {flag ? '开' : '关'}</div>
                <div>arrList: {arrList}</div>
            </div>
        );
    }
    
    如果 加个定时器
        const handleClick = () => {
            setCount(count + 1);
            setTimeout(() => {
                setFlag(!flag);
            });
            setArrList(['张三']);
        };
        // 会执行两次,说明函数执行了两次
        // setCount(count + 1) 和 setArrList(['张三']) 合并成了一次
        // setArrList(['张三']) 执行后,函数又执行了一次
        console.log(111);
    
  • 设置值的时候,可以是一个函数,这样就不用依赖函数中的状态了
    const handleClick1 = () => {
        // 还可以写成函数的形式,这样就能获取到上一次的状态,再进行操作,就不必依赖当前函数组件中的状态了
        setCount1(v => {
            return v + 1;
        });
    }
    return (
        <button onClick={handleClick1}>{count1}</button>
    )
    
    或者如下
    const handleClick1 = useCallback(() => {
        // setCount1(v => { // 这样写,就不用依赖函数中的状态了
        //     return v + 1;
        // });
        setCount1(count + 1); // 这样写还要依赖当前函数中的状态
    }, [count]); // 如果不写依赖项,count1 就一直获取到的是旧值
    

useEffect

作用

处理函数的副作用

函数的副作用

什么是函数的副作用,一个函数除了函数本身的功能外,其他的操作都是副作用
比如函数组件的功能就是把stateprops的值渲染在页面上,除此之外其他的操作,比如数据请求,修改dom等都是副作用
比如下面一个函数

    const [value, setValue] = useState('')
    const increment = (a, b) => {
        const res = a + b;
        setValue(res);
        return res;
    };

这个函数的主要作用就是两个值的相加,其中的副作用就是 setValue(res)

参数

useEffect(cb: () => () => any, arr ?: [])
第一个参数是一个回调函数(不要写成async函数,async返回值是一个Promise),返回值也是一个回调函数
第二个参数是一个可选的数组,表示依赖项,根据依赖的state,会重新执行第一个参数的回调

使用

一个简单的例子

import { useEffect, useState } from "react";
export default function HooksUseEffect() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        document.title = count; // 副作用操作
    })
    return (
        <div>
            <button onClick={() => setCount(count+1)}>{count}</button>
        </div>
    );
}

其中给 document.title 赋值就是一个 dom 操作,属于副作用

执行时机

useEffect 有两个参数,第一个是个回调函数,第二个是一个依赖数组

  • 不传第二个参数
    useEffect 会在每次state有变化的时候,就执行第一个参数回调函数
    如下例子,会在每次count改变的时候,执行一次useEffect回调函数的第一个参数
    import { useEffect, useState } from "react";
    export default function HooksUseEffect() {
        const [count, setCount] = useState(0);
        useEffect(() => {
            document.title = count; // 副作用操作
            console.log(111);
        })
        return (
            <div>
                <button onClick={() => setCount(count+1)}>{count}</button>
            </div>
        );
    }
    
  • 第二个参数是一个 空数组
    useEffect 只会在组件第一次加载的时候,执行一次
    下面的例子,document.title一直保持,count的初始值0
    useEffect(() => {
        document.title = count; // 副作用操作
    }, [])
    
  • 添加一个特定的依赖项
    第二个参数数组中 添加一个特定的依赖向,只有依赖改变的时候,才会执行useEffect的第一个参数
    下面的例子,当触发count改变的时候,不会改变document.title,当触发flag改变的时候,才会给documen.title赋值
    const [count, setCount] = useState(0);
    const [flag, setFlag] = useState(false);
    useEffect(() => {
        document.title = count; // 副作用操作
        console.log(111);
    }, [flag])
    return (
        <div>
            <button onClick={() => setCount(count+1)}>{count}</button>
            <button onClick={() => setFlag(!flag)}>{flag ? '开' : '关'}</button>
        </div>
    );
    

清除副作用

比如说一个定时器,在组件销毁的时候,没有去取消定时器,定时器还是存在的,还是会继续执行的,就会在成一些错误
useEffect的第一个参数回调函数,会返回一个函数,这个函数会在组件销毁前执行,因此处理一些副作用可以在这个函数里操作
如下例子

import { useEffect, useState } from "react";
export default function HooksUseEffect() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const timer = setInterval(() => {
            setCount(v => v+1);
        }, 1000);
        return () => {
            clearInterval(timer); // 清除副作用
        }
    }, [])
    return (
        <div>
            <button>{count}</button>
        </div>
    );
}

useRef

受控和不受控组件

都是指表单组件

  • 受控组件
    表单控件的value值需要state去提供,然后通过事件去修改这个state
    import {useState} from 'react';
    const [val, setVal] = useState('');
    <input 
        type="text"
        value={val}
        onInput={e => setVal(e.target.value)}
    />
    <p>{val}</p>
    
  • 不受控组件
    不通过state去管理表单组件的value
    import {useRef} from 'react';
    const iptRef = useRef(null);
    <input type="text" ref={iptRef} />
    <button onClick={() => console.log(iptRef.current.value)}>点击</button>
    

其他组件

  • 如果是类组件也可以直接拿到该组件的实例
    import React, {useRef} from 'react';
    class Component2 extends React.Component {
        state = { // state属性
            message: '我是类组件',
            show: true
        }
        onToggle() { // 方法
            const {show} = this.state;
            this.setState({show: !show});
        }
        render() {
            if (this.state.show) {
                return <div>{this.state.message}</div>
            }
            return (
                <div>...</div>
            )
        }
    }
    export default function HooksRef() {
        const component2Ref = useRef(null);
        return (
            <div>
                <Component2 ref={component2Ref} />
                <button onClick={() => component2Ref.current.onToggle()}>点击</button> // 调用子组件的方法
            </div>
        )
    }
    
  • 如果是函数组件就不能直接拿了,而且也只能拿到组件下面的某个元素,组件上的方法需要 通过useImperativeHandle方法想父组件传递出去
    如下:
    import {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react';
    const Component1 = forwardRef((props, ref) => {
        const [show, setShow] = useState(true);
        const ipt = useRef(null);
        const onToggle = useCallback(() => setShow(v => !v), []); // 事件
        useImperativeHandle(ref, () => { // 将子组件中的方法传递给父组件
            return {
                onToggle,
                ipt: ipt.current
            }
        })
        if (show) {
            return <div><input ref={ipt} type="text" placeholder="我是子组件" /></div>
        }
        return <div>...</div>
    })
    export default function HooksRef() {
        const component1Ref = useRef(null);
        return (
            <div>
                <Component1 ref={component1Ref} />
                <button onClick={() => component1Ref.current.onToggle()}>点击</button> {/* 调用子组件的方法 */}
            </div>
        )
    }
    

useContext

作用

跨组件传值,顶级组件 -> 孙组件,传值

用法1

创建一个createContext()

import {createContext} from 'react';
export const context = createContext();

顶级组件
createContext()身上有两个组件,一个是提供者Provider

import { useState } from "react";
import AComponent from "./AComponent";
import { context } from "./constants";
export default function HooksContext() {
    const [count, setCount] = useState(0)
    return (
        // 将需要提供给子孙组件的状态通过value属性传递
        <context.Provider value={{count, setCount}}>
            <div>
                <h2>顶级组件</h2>
                <div>
                    <button onClick={() => setCount(v => v+1)}>count: {count}</button>
                </div>
                <AComponent />
            </div>
        </context.Provider>
    )
}

子孙组件
另一个是消费者Consumer, Consumer里面有一个函数,通过函数,可以拿到顶级组件传过来的变量状态和方法

import { context } from "./constants"
export default function DComponent() {
    return (
        // 接收顶级组件传过来的值
        <context.Consumer>
            {
                ({count, setCount}) => {
                    return (
                        <div style={{padding: '10px', border: '1px solid black'}}>
                            <h2>子组件D</h2>
                            <button onClick={() => setCount(v => v+1)}>count: {count}</button>
                        </div>
                    )
                }
            }
        </context.Consumer>
    )
}

用法2

在顶级组件中使用是一样的
在子孙级组件中,可以通过uesContext()拿到顶级组件传过来的值,不再需要Consumer组件了

import { useContext } from "react"
import { context } from "./constants"
export default function DComponent() {
    // 通过 useContext 拿到顶级组件传过来的值
    const {count, setCount} = useContext(context);
    return (
        <div style={{padding: '10px', border: '1px solid black'}}>
            <h2>子组件D</h2>
            <button onClick={() => setCount(v => v+1)}>count: {count}</button>
        </div>
    )
}

memo

作用

避免不必要的更新,当父组件中有状态变化的时候,跟子组件没有关系,子组件也进行了执行

// 子组件
const Component1 = (props) => {
    console.log(111);
    return (
        <div><div>子组件</div></div>
    )
}
// 父组件
export default function HooksMemo() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <button onClick={() => setCount(v => v+1)}>count: {count}</button>
            <Component1 />
        </div>
    )
}

当父组件中的count状态发生变化的时候,父组件的html部分会重新执行一次,也就是说Component1组件明明是不需要更新的,但是更新了
可以使用memo函数包裹子组件,当依赖的状态发生变化的时候,子组件才会重新执行

import {useState} from 'react';
const Component1 = memo((props) => {
    console.log(111);
    return (
        <div>
            <div>子组件</div>
            <div>{props.flag ? 'on' : 'off'}</div>
        </div>
    )
})
export default function HooksMemo() {
    const [count, setCount] = useState(0);
    const [flag, setFlag] = useState(true);
    return (
        <div>
            <button onClick={() => setCount(v => v+1)}>count: {count}</button>
            <button onClick={() => setFlag(v => !v)}>切换</button>
            <Component1 flag={flag} />
        </div>
    )
}

count发生变化的时候,子组件没有更新,当子组件依赖的flag发生变化的时候,组件才重新执行了

useCallback

作用

会将一个函数保存起来,当依赖项发生变化的时候,才会更新函数

参数

useCallback(回调函数, [依赖项])

例如:

import { useCallback, useState } from "react"
export default function HooksCallback() {
    const [count, setCount] = useState(0);
    const handleCountChange = useCallback(() => {
        console.log(count); // 此时 一致打印的是 count的初始值 0
        setCount(count + 1);
    }, []) // 因为依赖项数组为空数组,表明这个函数不以来任何的状态,每次获取函数都是内存中拿到的旧函数
    return (
        <div>
            <button onClick={handleCountChange}>count: {count}</button>
        </div>
    )
}
    const handleCountChange = useCallback(() => {
        console.log(count); // 这个时候每次执行的结果就是最新的count
        setCount(count + 1);
    }, [count]) // 改成这样,当count发生变化的时候,函数才会更新

useMemo

作用

当依赖的状态发生变化的时候,才会重新执行函数

参数

useMemo(回调函数, [])

例子,这是一个两个数求和的计算,当两个依赖的值其中一个发生变化的时候,才会重新计算结果

import { useMemo, useState } from "react"
export default function HooksUseMemo() {
    const [val1, setVal1] = useState(0);
    const [val2, setVal2] = useState(0);
    const res =  useMemo(() => {
        return +val1 + +val2;
    }, [val1, val2]) // 当依赖的变量发生变化的时候,才会重新执行第一个参数的回调函数
    return (
        <div>
            <div>
                <input
                    type="number"
                    value={val1}
                    onChange={(e) => setVal1(e.target.value)}
                />
                <span>+</span>
                <input
                    type="number"
                    value={val2}
                    onChange={(e) => setVal2(e.target.value)}
                />
                <span>=</span>
                <span>{res}</span>
            </div>
        </div>
    )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值