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]
);