ReactHooks(一)

一.使用规则

  • hook和生命周期函数一样,必须写在函数最顶层;
  • 而且hook不应该被嵌套进条件或者循环语句中;
  • hook只能在函数组件或者自定义hook中被使用,而不能用于普通函数或者class组件中。

二.useState:使用状态(只在初始化时调用一次)

2.1 赋初值方法

  • 直接赋值
    基本语法: const [count,setCount]=usestate(0)
    count:当前状态值 setcount允许你更新状态的函数。
    1、 当状态发生变化时,函数组件重新渲染一次。
    2、当函数式组件被重复执行时,不会重复调用 useState()给数据赋初始值,而是复用上次的 state 的值。

  • 利用函数的返回值赋值
    基本语法: const [count,setCount]=usestate(()=>初始值)

2.usestate()函数有一个参数,参数可以是任意值,用来进行数据的定义。并且返回一个数组,数组中有两个对象,第一个是state变量并且把参数值赋予它,第二个是设置state变量值得方法。
注意:
1、 我们可以通过次函数定义多个state数据,但我们应保证每一个state数据都具有意义。同种意义范围的state数据应该放在同一个state中。​​​​​​​
2、setXXX调用方式

  • setXXX(新值)
  • 解决数值更新不急时: setXXX(fn) / setXXX((prev)=>基于 prev 计算并 return 一个新值)

2.2 更新对象类型的值

setUser({…user})
setUser(Object.assign({},user))

2.3 使用 setState 模拟组件强制刷新

import React,{useState} from 'react'
// 强制刷新
const ForceUpdate:React.FC = () =>{
const [,forceUpdate] =  useState({})
const onFresh = () =>{
    forceUpdate({})
}
    return 
      <>
    <button onClick={onFresh}> 强制刷新————{Date.now()}</button>
    </>
}
export default ForceUpdate

三.useEffect:增加了在功能组件执行副作用的功能。

3.1 函数的副作用

函数除了返回值外,对外界环境造成的其他影响。即与组件渲染无关的操作。如:获取数据、修改全局变量、更新 DOM

3.2 参数

相当于组件中编写了componentDidMount钩子函数。
useEffect()函数每次渲染都会执行,useEffect是非阻塞的,所以是异步代码。
useEffect()有两个参数:
①第一个是副作用函数,用来编写可能具有副作用的代码,副作用函数可以返回一个函数对象,这个函数对象用来清除副作用,不用清除,则不用返回;
②第二个参数是数组(可选),里面传入副作用中使用到的数据变量,并且这个变量得具有变化的特性,这个参数主要用于优化,反之进行重复渲染。

3.3 执行时机

  • 如果没有指定依赖项数组,则会在函数组件每次渲染完成后执行。
  • useEffect 会在组件首次渲染完毕,默认执行一次。
  • 组件每次渲染完毕,会触发 useEffect 中的回调函数,如果给了依赖想项数组,则还要判断依赖项是否变化,再决定是否触发回调
    注意:
  • 不要在 useEffect 中改变依赖项的值,会造成死循环。
  • 多个不用功能的副作用尽量分开声明,不要写到一个 useEfffect 中。

3.4 如何清理副作用

1、基本语法

// react帮我们调用清理函数  当执行useEffect时,先去判断有无清理函数,若有则先执行清理函数。
useEffect(()=>{
  return ()=>{/* 在这里执行自己的清理操作*/}
},[依赖项])

2、执行时机:

  • 当 effect 副作用函数执行之前,会先执行清理函数

  • 卸载组件时候执行
    2、应用场景

  • 清除定时器 / 网络请求

import React, { useState, useEffect } from "react";
const ChildColor = () => {
  const [color, setColor] = useState(0);
  useEffect(() => {
    const controller = new AbortController();
    // { signal: controller.signal }   controller.signal:终止信号 需要和fetch进行绑定
    fetch("https://api.liulongbin.top/v1/color", { signal: controller.signal })
      .then((res) => res.json())
      .then((resp) => {
        console.log(resp, "resp");
        setColor(resp.data.color);
      })
      .catch((err) => {
        console.log(err.message, "err");
      });
    // return 一个清理函数 终止请求:当隐藏组件时候
    // controller.abort()终止请求
    return () => controller.abort();
  }, []);
  return (
    <div>
      <h1>颜色值:{color}</h1>
    </div>
  );
};
export const FatherColor: React.FC = () => {
  const [flag, setFlag] = useState(true);

  return (
    <div>
      <h3>父组件</h3>
      <button
        onClick={() => {
          setFlag((prev) => !prev);
        }}
      >
        Toggle
      </button>
      <hr />
      <h3>子组件</h3>
      {flag && <ChildColor />}
    </div>
  );
};
  • 解绑事件监听
import { useState, useEffect } from 'react'

export const useMousePosition = () => {
  // 记录鼠标的位置
  const [position, setPosition] = useState({ x: 0, y: 0 })

  // 副作用函数
  useEffect(() => {
    // 1. 要绑定或解绑的 mousemove 事件处理函数
    const mouseMoveHandler = (e: MouseEvent) => {
      setPosition({ x: e.clientX, y: e.clientY })
    }

    // 2. 组件首次渲染完毕后,为 window 对象绑定 mousemove 事件
    window.addEventListener('mousemove', mouseMoveHandler)

    // 3. 返回一个清理的函数,在每次组件卸载时,为 window 对象解绑 mousemove 事件
    return () => window.removeEventListener('mousemove', mouseMoveHandler)
  }, [])
  
  return position
} 

3.5 自定义hook

注意:自定义hoku要以use开头。

import { useMousePosition } from '@/hooks/index.ts'

export const TestMouseInfo: React.FC = () => {
  const [flag, setFlag] = useState(true)
  // 调用自定义的 hook,获取鼠标的位置信息
  const position = useMousePosition()

  return (
    <>
      <!-- 输出鼠标的位置信息 -->
      <h3>父组件 {position.x + position.y}</h3>
      <button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
      <hr />
      {flag && <MouseInfo />}
    </>
  )
}

四.useLayoutEffect

4.1 useLayoutEffect和useEffect的对比

hook名称执行时机执行过程
useEffect浏览器重新绘制屏幕之后触发【组件渲染我那成之后执行】异步执行,不阻塞浏览器绘制
useLayoutEffect浏览器重新绘制屏幕之前触发【组件渲染我那成之前执行】同步执行,阻塞浏览器绘制

五.useRef用来保存对象

1、使用方法:const refObj = useRef(初始值)
2、解决两个问题

  • 获取 DOM 元素 或子组件的实例对象
import React,{useRef} from 'react'
//点击按钮获取input元素  并贴是的input获取焦点
const inputRef:React.FC = () =>{
// 添加范型定义 有了类型定义,则会自动提示
const iptRef=  useRef<HTMLInputElement>(null)
const getFocus = () =>{
    iptRef.current?.focus()
}
    return <>
    <input type="text" ref={iptRef}/>
    <button onClick={getFocus}> 获取焦点</button>
    </>
}
export default inputRef
  • 存储渲染周期之间共享的数据
import React, { useRef, useState } from "react";
// 方法一:全局定义的preCount 可能会造成变量全局污染
let preCount:number;
const LifeCycle: React.FC = () => {
  const [count, setCount] = useState(0);
  const add = () => {
    setCount((state) => state + 1);
    preCount = count;
  };
  return (
    <>
      <h1>
        新值是:{count} 旧值是:{preCount}
      </h1>
      <button onClick={add}> +1</button>
    </>
  );
};
export default LifeCycle;

// 方法二:全局定义的preCount 可能会造成变量全局污染
const LifeCycle: React.FC = () => {
  const [count, setCount] = useState(0);
  const preRef =  useRef<number>()
  const add = () => {
    setCount((state) => state + 1);
    preRef.current = count;
  };
  return (
    <>
      <h1>
        新值是:{count} 旧值是:{preRef.current}
      </h1>
      <button onClick={add}> +1</button>
    </>
  );
};
export default LifeCycle;

注意:

  • useRef()只在组件首次渲染的时候被创建
  • 如果组件是 rerender 的时候,不会重复创建 ref 对象
  • ref.current 变化时不会造成组件的 rerender
  • ref.current 不能作为其他 Hooks 的依赖项

六.forwardRef

6.1 解决父组件中不能直接引用子组件的 ref。

import React,{ useRef } from "react";
const Child: React.FC = () => {
  return <div>Child</div>;
};
export const Father: React.FC = () => {
    const childRef = useRef()
  return (
    <div>
      Father
      {/* 父组件中不能直接引用子组件的 ref。 */}
      <Child ref={childRef}/>
    </div>
  );
};
import React, { useRef, useState, useImperativeHandle } from "react";
// const Child: React.FC = () => {
//   return <div>Child</div>;
// };
// 使用这个 React.forwardRef() 进行包裹子组件 并且类型声明去掉React.FC
// React.forwardRef() 第一个参数是props,第二个参数是 ref[这里的ref是父组件传过来的ref]
const Child = React.forwardRef((_, ref) => { // props没有使用,则用_占位
  const [count, setCount] = useState(0);
  const add = (step: number) => {
    setCount((state) => (state += step));
  };
  // useImperativeHandle 中的对象才是父组件的获取到的 ref
  useImperativeHandle(ref, () => ({
    name: "nihao ",
  }));
  return (
    <div>
      <h1>count的值:{count}</h1>
      <button
        onClick={() => {
          add(1);
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          add(-1);
        }}
      >
        -1
      </button>
    </div>
  );
});
export const Father: React.FC = () => {
  const childRef = useRef();
  const showRef = () => {
    console.log(childRef.current, "childRef");
  };
  return (
    <div>
      <h3>父组件</h3>
      <button onClick={showRef}>获取子组件的 Ref</button>
      <hr />
      <h3>子组件</h3>
      <Child ref={childRef} />
    </div>
  );
};

6.2 基于useImperativeHandle 按需向外暴露成员

import React, { useRef, useState, useImperativeHandle } from "react";
const Child = React.forwardRef((_, ref) => {
  const [count, setCount] = useState(0);
  const add = (step: number) => {
    setCount((state) => (state += step));
  };
  // 基于 useImperativeHandle 按需向外暴露成员
  useImperativeHandle(ref, () => ({
    count,
    setCount,
  }));
  return (
    <div>
      <h1>count的值:{count}</h1>
      <button
        onClick={() => {
          add(1);
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          add(-1);
        }}
      >
        -1
      </button>
    </div>
  );
});
export const Father: React.FC = () => {
  const childRef = useRef<{count:number;setCount:(value:number)=>void}>();
  const reset = ()=>{
    childRef.current?.setCount(0)
  }
  return (
    <div>
      <h3>父组件</h3>
      <button onClick={reset}>重置子组件的count</button>
      <hr />
      <h3>子组件</h3>
      <Child ref={childRef} />
    </div>
  );
};

6.3 控制成员暴露的粒度

import React, { useRef, useState, useImperativeHandle } from "react";
const Child = React.forwardRef((_, ref) => {
  // props没有使用,则用_占位
  const [count, setCount] = useState(0);
  const add = (step: number) => {
    setCount((state) => (state += step));
  };
  useImperativeHandle(ref, () => ({
    count,
    // setCount,
    // 为了安全 减少成员暴漏粒度
    reset: () => {
      setCount(0);
    },
  }));
  return (
    <div>
      <h1>count的值:{count}</h1>
      <button
        onClick={() => {
          add(1);
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          add(-1);
        }}
      >
        -1
      </button>
    </div>
  );
});
export const Father: React.FC = () => {
  const childRef = useRef<{count:number;reset:()=>void}>();

  const reset = ()=>{
    childRef.current?.reset()
  }
  return (
    <div>
      <h3>父组件</h3>
      {/* 通过useImperativeHandle 重置子组件的count */}
      <button onClick={reset}>重置子组件的count</button>
      <hr />
      <h3>子组件</h3>
    </div>
  );
};

6.4 useImperativeHandle 的第三个参数

  //useImperativeHandle的第三个参数
  // 如果不传,则只要有useState定义的值发生变化,则这个回调函数就会执行。性能不友好
  useImperativeHandle(ref, () => {
    console.log("执行了 useImperativeHandle");
    return {
      count,
      reset: () => {
        setCount(0);
      },
    };
  });
  // 如果只传一个空数组,在组件第一次渲染的时候执行
  useImperativeHandle(
    ref,
    () => ({
      count,
      reset: () => {
        setCount(0);
      },
    }),
    []
  );
  // 如果回调函数返回的seate依赖变量,则只要该变量发生变化,则回调函数执行一遍
  useImperativeHandle(
    ref,
    () => ({
      count,
      reset: () => {
        setCount(0);
      },
    }),
    [count]
  );
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值