react hook基本使用

react hook基本使用

  • react 16.8版本出现的新特性,解决了类组件的一些问题,消除了繁琐的声明周期,使我们的开发更方便,bug出现的更少,代码更直观。【重点是前两个hook】

useState(转态管理,功能和类组件的state类似)

  • 在函数组件的函数体中调用这个函数(useState),参数为转态默认值,返回一个数组,数组的第一个元素为状态的值,第二个元素为一个函数,为用于改变状态的函数。

    import React, {useState} from "react";
    
    export default function UseStateTest() {
        const [n, setN] = useState(0);
        return <>
            <div>{n}</div>
            <button onClick={() => {
                setN(1);//将n变为1
            }
            }>点击改变
            </button> 
        </>
    }
    
  • 每次调用setN(新值)如果新值和旧的值不同,那么组件就会重新渲染,需要注意的是,如果setN()的参数直接传递一个新的值,那么这个改变是异步的,如果调用多次这个改变,拿到的值都是原始值。

    export default function UseStateTest() {
        const [n, setN] = useState(0);
        return <>
            <div>{n}</div>
            <button onClick={() => {
                setN(n + 1);//n由0变为1
                setN(n + 1);//n由0变为1
                console.log(n);//0
            }
            }>点击改变
            </button>
        </>
    }
    

    如果想要使用最新的值,就可以将一个回调函数作为参数传入setN,函数的第一个形参为上一个改变的新的状态值,回调函数的返回值为新的状态值。

    export default function Test() {
        const [n, setN] = useState(0);
        return <>
            <div>{n}</div>
            <button onClick={() => {
                setN(preValue => {
                    console.log(preValue);//0
                    return preValue + 1;
                })
                console.log("第一个同步打印", n);
                setN(preValue => {
                    console.log(preValue);//1
                    return preValue + 1;
                })
                console.log("第二个同步打印", n);
                setN(preValue => {
                    console.log(preValue);//2
                    return preValue + 1;
                })
                console.log("第三个同步打印", n);
            }
            }>点击改变
            </button>
        </>
    }
    

    打印结果:
    0
    ​ 第一个同步打印 0
    ​ 第二个同步打印 0
    ​ 第三个同步打印 0
    ​ 1
    ​ 2

    通过打印的结果和顺序可以得出结论,在react合成事件中,第一个setN()的回调函数先执行,接下来的setN()的回调函数放到队列异步执行,setN是异步执行的,每一个setN()中的回调函数的形参都是上一个setN()返回的新状态。

  • 常常我们需要使用多个状态,一个组件中不相干状态分离也是符合react的设计理念,所以我们就需要在一个函数组件中调用多次useState,

  • 需要注意的是,如果状态值是一个对象,那么每次改变函数的新状态值是直接替代旧的对象,而不是覆盖,这点和类组件的转态相同,所以如果需要改变状态对象的某个值时,应该先用展开运算符将之前的转态值展开,在用新的转态值覆盖之前的状态值,从而产生一个新的对象,包含原来其他状态和新的状态。

    export default function UseStateTest() {
        const [n, setN] = useState({
            content: 'hello',
            like:'java'
        });
        return <>
            <div>{n.content}</div>
            <div>{n.like}</div>
            <button onClick={() => {
                setN({
                    ...n,
                    content: 'world'
                })
            }
            }>点击改变
            </button>
        </>
    }
    
  • 很多人会有一个疑问,当一个函数组件使用了多个转态时,改变其中一个状态,组件重新渲染,那么其他状态为什么能够保持原来的转态数据,而不会重置。

    • 可以想象有一个附着在附件的记录表,记录所有hook的下标值和状态信息。每当使用一个useState的时候,就会增加一个记录,比如第一个记录下标值为0,转态值为默认状态,第二次使用useState增加一条记录下标值为1,转态值为默认状态,当使用setN()改变状态值后就会用新的状态值代替记录表中原有的状态值,每次重新渲染组件,也会从状态表中根据下标值获取对应转态值。
    • 这也是不能将hook放到判断语句和循环语句的原因,不仅仅useState,所有hook都不可以这样做,这样做可能会造成下标值和信息对应错乱。
  • 每个useState附着在组件节点上互不影响,所以就算调用了两次这个函数组件,他们的状态值也不会共享。

useEffect(处理副作用操作函数)

  • 副作用操作指的是会对组件外部产生影响的操作。
  • 如ajax请求,计时器,更改真实dom等等。
  • useEffect接受一个回调函数作为参数,接收的回调函数用来处理副作用操作,这个回调函数就是副作用函数。
    1. 副作用函数运行的时间是在页面完成ui渲染之后,他的执行是异步的不会阻塞页面。
    2. 与componentDidMount和componentDidUpDate的区别在于这两个生命周期函数运行在更新了真实dom数但用户还没有看到页面的时候,useEffect发生在用户看到页面后。
  • 副作用函数可以返回一个函数,这个函数是清理函数,清理函数首次加载不会运行,更新副作用函数时运行,运行时间在副作用函数之前。
  • useEffect函数可以传第二个参数,是一个数组,数据中每一个元素记录副作用函数依赖的数据,组件重新渲染后,只有依赖数据改变后,副作用函数才会重新执行。
    • 所以当传递了第二个参数依赖数据,当依赖数据不变时,
    1. 副作用函数仅在第一次渲染后执行。
    2. 清理函数仅在卸载组件后执行。
  • 在副作用函数中,如果使用了函数上下文的变量,由于闭包的影响,会导致副作用函数不会实时变化。

useContext(更优秀简洁的上下文解决方案)

import React, {useContext} from "react";
const ctx = React.createContext();
function Son() {
  const value = useContext(ctx);//直接拿到父组件提供的value值。
  return <div>{value.name}</div>//渲染react
}
export default function UseStateTest() {
    return <>
        <ctx.Provider value={{name:'react'}}>
            <Son/>
        </ctx.Provider>
    </>
}

自定义hook(将一些常用的,跨越多个组件的hook功能,抽离成一个函数,这个函数就是自定义hook)

  • 自定义hook更新组件,使用它的组件也会更新,可以用来封装异步请求方法,api接口。

useCallback(固定函数地址,用于优化)

import React, {useState} from "react";

class Son extends React.PureComponent {
    render() {
        console.log("Test渲染");
        return <button onClick={this.props.myClick}>点击子组件{this.props.num}</button>
    }
}

export default function Test() {
    const [n, setN] = useState(0);
    const [s, setS] = useState(0);
    console.log("父组件渲染");
    return <>
        <Son num={s} myClick={() => {
           setS(10);
        }
        } />
        <button onClick={() => {
            setN(n+1);
        }
        }>点击父组件
        </button>
    </>
}
  • 有可能遇到上面的场景,Son为纯组件,父组件刷新后,当传入props和自身state不变时,组件不重新渲染,但是每次点击父组件按钮,传入Son的props看起来没有改变,他自身也没有state,应给不会刷新才对,但是他却刷新了,原因在于Son传入的myClick函数每次刷新父组件都会产生一个新的函数赋值给myClick,也就是props变了,所以组件会刷新。
  • 如果将函数写在外面的话,setS就没办法传过去了,这样的需求useCallback就可以很好地处理了。看看下面的代码。
import React, {useCallback, useState} from "react";

class Son extends React.PureComponent {
    render() {
        console.log("Test渲染");
        return <button onClick={this.props.myClick}>{this.props.num}点击子组件</button>
    }
}

export default function Test() {
    const [n, setN] = useState(0);
    const [s, setS] = useState(0);
    const handleClick = useCallback(()=>{
        setS(10);
    },[s]);
    console.log("父组件渲染");
    return <>
        <Son num={s} myClick={handleClick} />
        <button onClick={() => {
            setN(n+1);
        }
        }>点击父组件
        </button>
    </>
}
  • useCallback()第一个参数是一个回调函数,第二个参数是一个依赖数组,返回一个函数,返回的函数就是回调函数的引用,如果依赖数组的数据不发生变化,那么返回的就永远是同一个函数的引用,这样就可以完美解决上面的问题。

useMemo(保持不需要重新计算的稳定数据,用于性能优化)

  • 用法类型于useCallback,不同的是回调函数里返回的内容是需要记忆的内容
    const handleClick = useMemo(()=>{
        return ()=>{
            setS(10);
        }
    },[s]);

上面的固定函数可以写成如上代码,效果相同,由于useMemo返回值记忆的是return返回的值,所以这个hook有更多的应用场景,固定各种类型的变量。只要一赖数据不变,返回的值的引用就不变,可以将不需要的计算放到useMemo的第一个参数回调函数当中。(有点像vue的computed计算属性)

useRef(多个对象同一个引用,性能优化)

  • 调用这个函数,返回一个对象{current:undefined},如果调用useRef时传一个参数,则返回对象的current的值为那个参数。
  • 如果使用React.creatRef由于每一次函数组件重新渲染都会重新创建一个新的ref,对性能有一些影响,使用useRef无论重新调用函数多少次,使用的都是同一个返回值ref
  • 利用useRef同一引用值这个特性写个倒计时
import React, {useEffect, useRef, useState} from "react";

export default function UseStateTest() {
    const [n, setN] = useState(10);
    const val = useRef(n);
    useEffect(() => {
        const timer = setInterval(() => {
            setN(val.current--);
            if (val.current === 0) {
                clearInterval(timer);
            }
        }, 500);
    }, [])
    return <div>
        {n}
    </div>
}
  • 由于每一次都使用同一个val引用,所以每次重新渲染函数都使用同一个引用。

useLayoutEffect(作用和useEffect相同)。【不常用

  • 差别在于副作用函数运行的时间点,useLayoutEffect在dom构建完成,但还没有渲染ui页面的时候运行,和生命周期函数componentDidMount和componentDidUpdate函数运行的时间点差不多。应该尽量使用useEffect,因为这个hook不会阻塞ui页面,如果有特殊需求,再考虑使用useLayoutEffect。

useImperativeHandle这个hook用于使用ref调用函数组件的方法。【不常用

useDebugValue 用于自定义hook的关联数据显示到调试栏。如果创建的自定义hook通用性比较高,可以使用这个hook方便调试。【不常用

React中,使用上下文(context)可以方便地在组件之间共享数据。通过使用ReactHooks中的useContext钩子,我们可以更简单地使用上下文。 首先,我们需要创建一个上下文对象,可以使用React的createContext函数来创建。这个上下文对象可以包含我们想要共享的任何类型的数据。 然后,在函数组件中使用useContext钩子来读取上下文的值。使用useContext时,我们需要将上下文对象作为参数传递给它,它将返回上下文的当值。 需要注意的是,尽管可以使用useContext读取上下文的值,但我们仍然需要在上层组件树中使用<MyContext.Provider>来为下层组件提供上下文。这个<MyContext.Provider>组件接收一个value属性,用于传递上下文的值给子组件。 下面是一个使用原始写法和使用context hook写法的例子: 原始写法: ``` import React from 'react'; // 创建一个上下文,初始化值 const MyContext = React.createContext({name: 'twinkle', age: 18}); // 文本组件 function TextComp() { return ( <> <MyContext.Consumer> {value => ( <> <p>名字:{value.name}</p> <p>年龄:{value.age}</p> </> )} </MyContext.Consumer> </> ); } // 测试组件 export default function TestContextComp() { return ( <div> <MyContext.Provider value={{name: 'cll', age: 18}}> <TextComp/> </MyContext.Provider> </div> ); } ``` 使用context hook写法: ``` import React, { useContext } from 'react'; // 上下文数据的结构 interface ICtxP { name: string; age: number; } // 创建一个上下文 const MyContext = React.createContext<ICtxP>({ name: 'twinkle', age: 18 }); // 文本组件 function TextComp() { const value = useContext(MyContext); // 使用上下文hook return ( <> <p>名字: {value.name}</p> <p>年龄: {value.age}</p> </> ); } export default function TextContextHook() { return ( <div> <MyContext.Provider value={{ name: 'cll', age: 18 }}> <TextComp/> </MyContext.Provider> </div> ); } ``` 无论是原始写法还是使用context hook写法,它们都可以实现在React使用上下文来共享数据。使用context hook写法可以减少代码量并简化组件的结构,使其更加简洁和易于理解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [react 进阶hook 之 context Hook](https://blog.csdn.net/qq_41499782/article/details/115493691)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值