一.使用规则
- 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><buttononClick={() => {setFlag((prev) => !prev);}}>Toggle</button><hr /><h3>子组件</h3>{flag && <ChildColor />}</div>);
};
- 解绑事件监听
四.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
5.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 中的对象才是父组件的获取到的 refuseImperativeHandle(ref, () => ({name: "nihao ",}));return (<div><h1>count的值:{count}</h1><buttononClick={() => {add(1);}}>+1</button><buttononClick={() => {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>);
};
5.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><buttononClick={() => {add(1);}}>+1</button><buttononClick={() => {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>);
};
5.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><buttononClick={() => {add(1);}}>+1</button><buttononClick={() => {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>);
};
5.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]);