• react hook---钩子函数的使用,useState,useReducer,useEffect,useRef,useContext,自定义hook


    什么是hook钩子函数

    hook(钩住),表示拦截操作,可以在某些操作执行时拦截住(钩住),执行hook函数的逻辑,

    react中,hook函数一般是use开头,然后提供一些方法api,也就是说你可以理解成react中的hook就是一套便于操作的api

    useState

    useState钩子是一个提供响应式状态的方法,

    const [show, setShow] = useState(false)

    它接受一个任意值,返回一个数组,里面包含一个由任意值生成的响应式变量,和这个响应式变量的修改方法,

    此处示例可以参考:react基础语法,模板语法,ui渲染,jsx,useState状态管理-CSDN博客

    useReducer

    useReducer钩子可以实现状态处理的统一管理,状态处理是指操作状态的方法,

    useReducer和usestate一样可以定义响应式状态和状态处理方法,不过相比useState,它可以提供逻辑更加明显,处理方式更多的方法,而不是单单一个修改覆盖的set

    useReducer,接受一个函数方法和一个作为状态的任意值,返回一个数组,包含了状态值和一个触发器函数,(在用法上和useState类似)

    1. const [state,dispatch] = useReducer(reducer,value)
    2. //接受一个状态处理函数reducer和一个初始值value
    3. //返回状态值state和触发器函数dispatch
    4. //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
    1. import { useReducer } from "react"
    2. function reducer(state: number, action: { type: string }) {
    3. // 接受两个参数,state状态值,触发对象(包含type属性)
    4. switch (action.type) {//根据type触发不同逻辑
    5. case 'increment':
    6. return state + 1;
    7. case 'decrement':
    8. return state - 1;
    9. default:
    10. return state;
    11. }
    12. }
    13. export default function home() {
    14. const [state, dispatch] = useReducer(reducer, 0);
    15. //接受一个状态处理函数和一个初始值
    16. //返回状态值state和触发器函数dispatch
    17. //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
    18. return (
    19. <div>
    20. <h1>homeh1>
    21. <div>
    22. <button onClick={() => dispatch({ type: 'decrement' })}>-button>
    23. <span>{state}span>
    24. <button onClick={() => dispatch({ type: 'increment' })}>+button>
    25. div>
    26. div>
    27. )
    28. }

     

    这样所有的逻辑都在集中在reducer中,并通过dispatch触发,相比于useState更好管理

    useEffect

    useEffect 副作用钩子函数,它接受两个参数,callback:回调函数 ,监听值变化时触发执行的副作用,若没有监听值,则会在每次组件渲染后触发(容易出现死循环),data:数组(可选),被监听的值,

    useEffect(()=>{},[])

    注意:当传入的 的值是一个空数组[ ]时,此时副作用函数只会在初始渲染时触发(开发环境下会触发两次,生产环境下触发一次,官方的说法是,便于开发是暴露出可能出现的渲染错误)

    1. useEffect(() => {
    2. console.log(state,'state变化了')
    3. },[state])//监听state变化,当state变化时触发回调函数

    当state的值变化是会触发回调打印结果,这里注意值还没有变化时回调就自动触发了两次,如果不想要它在初始化时执行,应该在逻辑种设置条件,例如

    1. const [done, setDone] = useState(false)
    2. useEffect(() => {
    3. if(done){
    4. console.log(state,'state变化了')
    5. }else{
    6. setDone(true)
    7. }
    8. },[state])//监听state变化,当state变化时触发回调函数

    useEffect钩子是跟随组件的;在组件挂载时,它会触发渲染的回调,在组件更新时,作为交互响应,在组件移除卸载后,它也会失效

    这个三个生命周期正好对应了3种效果

    1. 挂载时:一般用于网络请求,组件挂载后开始请求并渲染数据
    2. 更新时:触发响应式,引起页面变化
    3. 卸载时:清除副作用函数

    useRef 

    1.存放非响应的值

    useRef接受一个任意值,返回一个具有current属性的对象,current内存放了接受的任意值,

    1. const ref = useRef(value)
    2. const ref = useRef(0)

    可以看到ref就是一个普通的对象,它和useState不同,它可以任意修改current属性中的值,同时并不会触发页面的更新渲染,这是一个非响应的值,就像普通的变量一样;

    虽然不会触发页面的更新,但是ref会保留修改的值,当页面被更新时,ref也会变成最新的值,ref不会触发更新,但它可以被‘更新’

    1. const pre = useRef(0);
    2. // console.log(pre);
    3. useEffect(() => {
    4. if(done){
    5. pre.current = state
    6. console.log('页面变化了',state,pre.current)
    7. }else{
    8. setDone(true)
    9. }
    10. },[state])//监听state变化,当state变化时触发回调函数

            这里当state改变时触发页面渲染,将state的值赋值给pre,每次页面重新渲染后,ref就会变成上一次的赋值,而最近一次赋值是指页面更新后才触发的,所以pre虽然和state是相等的,但在页面上永远少更新一次,pre是被更新而不是触发更新

    2.获取DOM元素

    ref可以作为dom的属性,用来获取dom实例,如果你熟悉vue3的ref,那么它们是用法几乎是一致的

    1. const spanRef = useRef(null)
    2. console.log(spanRef);
    3. <span ref={spanRef}>上一次的值:{pre.current}span>

    spanRef的current属性存放的就是span标签实例,注意这里的spanRef是和span中ref属性的spanRef一致的

    3.获取子组件实例

    在react中useRef除了获取Dom,还可以由条件的获取子组件实例,并使用器内部的方法,这需要子组件将自身内部暴露出来,

    • forwardRef :
      • 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
    • useImperativeHandle
      • 接受两个参数,ref 父组件获取子组件实例的属性,回调函数,返回一个对象,内部是暴露出去的方法
    1. import { forwardRef, useEffect, useImperativeHandle, useReducer, useRef, useState } from "react"
    2. // 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
    3. const ChildRef = forwardRef( function(props,ref){
    4. useImperativeHandle(ref, () => ({
    5. // 暴露给子组件的方法
    6. fn:()=>{
    7. console.log('我是子组件暴露的方法')
    8. }
    9. }))
    10. return <span>我是子组件span>
    11. })
    1. const childRef = useRef(null)
    2. function useFn(){
    3. childRef.current.fn()
    4. console.log(childRef)
    5. }
    6. <ChildRef ref={childRef} />
    7. <button onClick={useFn}>调用子组件暴露的方法button>

    这样在父组件中就使用了子组件的方法,并且获取了子组件实例

    useContext

    useContext(上下文)提供了跨越中间组件传值的功能,

    在多级组件中,父子组件传值需要通过props一级一级向下传递,使用useContext钩子就可以略过中间的组件,直接将值传递给目标子组件

    使用useContext包括两个部分,创建和引用

    createContext: 接受一个任意值,并返回一个存放任意值的context参数

    useContext:接受context参数,返回context内的值

    1. // 创建值进行传递
    2. const context = createContext(value)
    3. // 在任意组件内使用useContext获取值
    4. const value = useContext(context);

    context.ts

    1. import { createContext } from "react";
    2. export const context = createContext('初始值')

     page.jsx

    1. import { useContext } from "react";
    2. import { context } from "../context"
    3. // 使用useContext获取值
    4. const val = useContext(context);
    5. retrun(
    6. //...
    7. <ChildRef ref={childRef} />
    8. <button onClick={useFn}>调用子组件暴露的方法button>
    9. <hr />
    10. <h3>{val}h3>
    11. )
    1. // 使用useContext获取值
    2. const val = useContext(context);
    3. return(
    4. <>
    5. <span>我是子组件span>
    6. <h3>{val}h3>
    7. )

     这里两个组件获取的都是直接从context中取到的值,子组件中的值并没有经过父组件,

    值得注意的是这个值没有被污染的,每次读取值都是从默认值开始传递(如果父组件的值被修改了,子组件获得的值不会受到影响,没有经过父组件,直接获得的默认值)

    如果要在父组件修改子组件接受的值就需要修改子组件获取的context,默认是不经父组件的,通过context.Provider,就可以截取子组件向上获取数据,让子组件从父组件获取context

    1. <context.Provider value = {val + '经过了父组件的处理'}>
    2. <ChildRef ref={childRef} />
    3. context.Provider>
    4. <ChildRef ref={childRef} />
    5. <ChildRef ref={childRef} />

    context.Provider接受一个value属性,它会代替context向下传递

    第一个组件经过了父组件获取值,而其他两个子组件是直接获取的默认值没有经过父组件

    自定义hook

    react官方提供了很多hook,但有时侯对于一些特殊的需求,可以使用自定义的hook;自定义hook 可以将逻辑复用,简化代码结构;自定义hook需要以use开头定义,

    不过,没必要对每段重复的代码都提取自定义 Hook,自定义Hook最大的作用应该是减少useEffect,

    提供一个页面渲染时触发的hook

    1. import { useEffect } from "react"
    2. // 提示页面渲染
    3. export const useTip = () => {
    4. useEffect(() => {
    5. console.log('页面渲染了')
    6. })
    7. }
    1. import { useTip } from "../useTip";
    2. // 使用自定义hook
    3. useTip()

     

    更多关于自定义组件的内容可以参考:使用自定义 Hook 复用逻辑 – React 中文文档

    主要代码展示

    page.tsx

    1. import { forwardRef, useContext, useEffect, useImperativeHandle, useReducer, useRef, useState } from "react"
    2. import { context } from "../context"
    3. import { useTip } from "../useTip";
    4. function reducer(state: number, action: { type: string }) {
    5. // 接受两个参数,state状态值,触发对象(包含type属性)
    6. switch (action.type) {//根据type触发不同逻辑
    7. case 'increment':
    8. return state + 1;
    9. case 'decrement':
    10. return state - 1;
    11. default:
    12. return state;
    13. }
    14. }
    15. // 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
    16. const ChildRef = forwardRef( function(props,ref){
    17. useImperativeHandle(ref, () => ({
    18. // 暴露给子组件的方法
    19. fn:()=>{
    20. console.log('我是子组件暴露的方法')
    21. }
    22. }))
    23. // 使用useContext获取值
    24. const val = useContext(context);
    25. return(
    26. <>
    27. <span>我是子组件span>
    28. <h3>{val}h3>
    29. )
    30. })
    31. export default function home() {
    32. const [done, setDone] = useState(false)
    33. const [state, dispatch] = useReducer(reducer, 0);
    34. //接受一个状态处理函数和一个初始值
    35. //返回状态值state和触发器函数dispatch
    36. //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
    37. const pre = useRef(0);
    38. // console.log(pre);
    39. const spanRef = useRef(null)
    40. // console.log(spanRef);
    41. const childRef = useRef(null) as any;
    42. function useFn(){
    43. childRef.current?.fn()
    44. console.log(childRef)
    45. }
    46. // 使用useContext获取值
    47. const val = useContext(context);
    48. // 使用自定义hook
    49. useTip()
    50. useEffect(() => {
    51. if(done){
    52. pre.current = state
    53. console.log('页面变化了',state,pre.current)
    54. }else{
    55. setDone(true)
    56. }
    57. },[state])//监听state变化,当state变化时触发回调函数
    58. return (
    59. <div>
    60. <h1>homeh1>
    61. <div>
    62. <button onClick={() => dispatch({ type: 'decrement' })}>-button>
    63. <span>当前值:{state}span>
    64. <button onClick={() => dispatch({ type: 'increment' })}>+button>
    65. div>
    66. <hr />
    67. <div>
    68. <span ref={spanRef}>上一次的值:{pre.current}span>
    69. div>
    70. <hr />
    71. <context.Provider value = {val + '经过了父组件的处理'}>
    72. <ChildRef ref={childRef} />
    73. context.Provider>
    74. <ChildRef ref={childRef} />
    75. <ChildRef ref={childRef} />
    76. <button onClick={useFn}>调用子组件暴露的方法button>
    77. <hr />
    78. <h3>{val}h3>
    79. div>
    80. )
    81. }

    content.ts

    1. import { createContext } from "react";
    2. export const context = createContext('初始值')

    useTip.ts

    1. import { useEffect } from "react"
    2. // 提示页面渲染
    3. export const useTip = () => {
    4. useEffect(() => {
    5. console.log('页面渲染了')
    6. })
    7. }

  • 相关阅读:
    【C++】搜索二叉树面试oj题
    【深入理解java虚拟机】 - 类加载器与双亲委派模型
    Python QGIS 3自动化教程
    云之道知识付费v1.5.4小程序+前端(含pc付费插件)
    Kafka - Kafka生产者|发送消息|分区策略|提高吞吐量|数据可靠性|数据去重|数据有序
    【yolo】yolov5-seg实例分割标签
    window.requestAnimationFrame Web3D渲染帧率控制
    项目优化之监听tab切出事件
    达梦数据库学习操作记录
    RabbitMQ学习笔记-rabbitMQ是什么
  • 原文地址:https://blog.csdn.net/I_am_shy/article/details/140371461