文章目录
React项目中经常用的hooks,有这几种!包括: 基本hooks & 优化hooks & 写组件常用hooks !我忘记的时候就来这里看一看!!
1、所有钩子函数,只能在组件顶层或自定义钩子函数顶层使用!🈲止出现在条件判断、循环、函数中。
2、自定义钩子函数格式为 useXXX
推荐:ahooks 3.0 钩子函数,引入到项目中使用
useEffect
定义生命周期
第一种:第二个参数「没有依赖数组」
1、 所在组件「初始化」时或「所有变量状态更新」时,执行包裹的函数。
2、「所有变量状态更新」前 或「页面卸载」前,执行返回值函数。
useEffect(()=>{
//do something...
return ()=>{
// destroy something...
}
})
第二种:第二个参数「依赖空数组」
1、所在组件「初始化」时,执行包裹的函数。
2、「页面卸载」前,执行返回值函数。
useEffect(()=>{
//do something...
return ()=>{
// destroy something...
}
},[])
第三种:第二个参数「依赖数组1个或多个状态变量」
1、所在组件「初始化」或「依赖数组变量状态更新」时,执行包裹的函数。
2、「依赖数组变量状态更新」前 或「页面卸载」前,执行返回值函数。
useEffect(()=>{
//do something...
return ()=>{
// destroy something...
}
},[count1,count2])
小小的useEffect 其实还有很多问题~
1、useEffect 函数中设置依赖项,如何避免更新依赖状态死循环?可以设置控制条件。如下:
conset [count,setCount] = useState(5);
useEffect(()=>{
//设置控制条件
if(count < 10){
//do somethine...
}
},[count])
# 好像有点问题~ 其实一个普通函数就可以满足。上述只是做示例!
const handleCount = ()=>setCount(count+1);
2、依赖项可以是对象吗?
可以,但不推荐!若对象层级较深,必然会引发更新性能问题,得不偿失!
1、useEffect依赖项可以是「对象类型」吗?
答:可以!
底层deps使用 Object.is() 做浅比较,更新时「即使是同一对象」也刷新!
方案1: JSON.stringify 格式化。需注意此方式风险!
方案2: 使用 ahooks库中 useDeepCompareEffect() 平替!deps用 react-fast-compare实现
方案3: 自定义支持深度依赖的hooks「https://www.jb51.net/article/277883.htm 」
⚠️依赖复杂结构的数据,易导致频繁渲染,慎用!!!
补充:JSON.stringify 格式化的问题。
- 无法处理循环引用:如果被拷贝的对象中存在循环引用,即对象A中的某个属性引用了对象B,而对象B又引用了对象A,JSON.stringify会抛出异常。
- 无法拷贝特殊对象:JSON.stringify无法正确地拷贝一些特殊对象,例如函数、正则表达式、Date对象等。这些特殊对象在转换过程中会被转换成其他类型的值,导致拷贝结果不准确。
- 无法拷贝非枚举属性:JSON.stringify只能拷贝对象的可枚举属性,对于不可枚举属性和Symbol类型的属性,它会被忽略。
- 对象属性丢失:在拷贝过程中,JSON.stringify会忽略undefined、函数以及symbol类型的属性,导致拷贝后的对象丢失这些属性。
- 性能问题:JSON.stringify的性能相对较低,特别是在拷贝大型对象或嵌套层次较深的对象时,会消耗大量的时间和内存。
3、依赖项可以是父组件props吗?
不要这样做!依赖props的操作,可以用常量代替!如下:
const component = ({props1,props2})=>{
//处理props参数
const state1 = props1.toString().split(',');
return (
<div>
{state1}
</div>
)
}
4、父子组件useEffect的执行顺序?
子组件useEffect先执行和先销毁,后父组件。
useState状态变量
conset [**state**,setState] = useState();
1、React是单向数据流的,改变state需要用到不改变原数据的方法,如下:
数组:js函数 map、filter、find、every、some、reduce、silce、join等等不改变原对象的方法。
对象: … 扩展
2、什么时候定义state?
参考 React哲学 观点! 抽离,共用state。
3、setState 改变state注意!
#是异步进行的,React中设计了批处理,同一操作,将多个setState合并执行!16版本用异步Promise和定时器包裹可实现同步,17、18版本中用flushAsync() 包裹实现同步执行!
一般不会那样操作,当有state和js操作需要同步执行时,首先请检查你的代码是否健康(可维护性、高内聚、低耦合)~
useRef 不引起刷新
用法1、获取实例对象或DOM对象
import React, { useState, useEffect, useMemo, useRef } from 'react';
export default function App(props){
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
const couterRef = useRef();
useEffect(() => {
document.title = `The value is ${count}`;
// 打印 DOM 元素
console.log(couterRef.current);
}, [count]);
return (
<>
<button ref={couterRef}
onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}
</button>
</>
);
}
用法2 :使用useRef
来期存储数据,对它修改也不会引起组件渲染。
在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?
1、第一个想到的应该是state
。没错,一个组件的state
可以在多次渲染之后依旧不变。但是,state
的问题在于一旦修改了它就会造成组件的重新渲染。
2、用Ref存储数据,改变值时,不会引起组件重新渲染。
import React, { useState, useEffect, useMemo, useRef } from 'react';
//在下面的例子中
//我用ref对象的current属性来存储定时器的ID,这样便可以在多次渲染之后依旧保存定时器ID,从而能正常清除定时器。
export default function App(props){
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
const timerID = useRef();
useEffect(() => {
timerID.current = setInterval(()=>{
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(()=>{
if(count > 10){
clearInterval(timerID.current);
}
});
return (
<>
<button ref={couterRef} onClick={() => {setCount(count + 1)}}>
Count: {count}, double: {doubleCount}
</button>
</>
);
}
useLayOutEffect
这个钩子 会影响性能,尽可能的选择useEffect
useLayoutEffect
是 useEffect
的一个版本,它在浏览器重绘屏幕之前触发。
场景: 测量布局。
有时,这还不够。想象一下悬停时出现在某个元素旁边的工具提示。如果有足够的空间,工具提示应该出现在元素上方,但如果空间不够,它应该出现在元素下方。为了在正确的最终位置渲染工具提示,你需要知道它的高度(即它是否适合顶部)。
- 在任何地方渲染工具提示(即使位置错误)。英Render the tooltip anywhere (even with a wrong position).
- 测量它的高度并决定放置工具提示的位置。英Measure its height and decide where to place the tooltip.
- 在正确的位置再次渲染工具提示。英Render the tooltip again in the correct place.
所有这些都需要在浏览器重新绘制屏幕之前发生。你不希望用户看到工具提示在移动。在浏览器重绘屏幕之前调用 useLayoutEffect
执行布局测量:
可以的,写提示框组件的时候,用这个。
useMemo
1 组件内部的state发生改变的时候,便会重新渲染
2 父组件传递的props发生变化也会重新渲染
为了避免组件内部 没必要高额的计算开销,用useMemo优化变量数据
import React,{useState,useMemo} from 'react'
function UseMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// 未性能优化前,数据每次更新时,均会触发
// const expensive = () => {
// console.log('compute');
// let sum = 0;
// for (let i = 0; i < count * 100; i++) {
// sum += i;
// }
// return sum;
// }
// 使用useMemo 性能优化,仅在count更新时触发 返回的是结果,避免了高额的计算,达到优化效果
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count])
return (
<div>
{/* <h4>{count}-{val}-{expensive()}</h4> */}
<h4>{count}-{val}-{expensive}</h4>
<p><button onClick={() => setCount(count + 1)}>+c1</button></p>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
)
}
export default UseMemo;
useCallBack
不是每个函数都需要使用它
useCallBack是一个缓存工具没错。但实际上他并不能阻止函数都重现构建。 返回的是一个函数
//Com组件
const Com = () => {
//示例1包裹了useCallBack的函数
const fun1 = useCallBack(() => {
console.log('示例一函数');
...
},[])
//示例2没有包裹useCallBack的函数
const fun2 = () => {
console.log('示例二函数');
...
}
return <div></div>
}
useCallBack的本质工作不是在依赖不变的情况下阻止函数创建,而是在依赖不变的情况下不返回新的函数地址而返回旧的函数地址。
注意:不论是否使用useCallBack都无法阻止组件render时函数的重新创建!!
useCallBack在什么情况下使用?
根据实际情况来用
在往子组件传入了一个函数并且子组件被React.memo缓存了的时候使用
React.memo()是通过校验props中的数据是否改变的来决定组件是否需要重新渲染的一种缓存技术,
具体点说React.memo()其实是通过校验Props中的数据的内存地址是否改变来决定组件是否重新渲染组件的一种技术。
只是浅比较。当传入函数时,优化失效
并不会阻止生成新的函数,只是返回旧的函数地址
import {useCallBack,memo} from 'react';
/**父组件**/
const Parent = () => {
const [parentState,setParentState] = useState(0); //父组件的state
//需要传入子组件的函数 未用useCallBack优化
// const toChildFun = () => {
// console.log("需要传入子组件的函数");
// ...
//}
//需要传入子组件的函数 useCallBack优化
//使用useCallBack包一下需要传入子组件的那个函数。那样的话,父组件重新渲染,子组件中的函数就会因为被useCallBack保护而返回旧的函数地址,子组件就不会检测成地址变化,也就不会重选渲染。
//fn :一个函数最终会返回该回调函数,该回调函数仅仅只在 deps 参数发生改变时才会更新~
//deps :数组;用于触发 fn 回调函数改变的参数列表。 没有依赖项,就传一个空数组就行
const toChildFun = useCallBack(() => {
console.log("需要传入子组件的函数");
...
},[deps])
return (<div>
<Button onClick={() => setParentState(val => val+1)}>
点击我改变父组件中与Child组件无关的state
</Button>
//将父组件的函数传入子组件
<Child fun={toChildFun}></Child>
<div>)
}
/**被memo保护的子组件**/
const Child = memo(() => {
consolo.log("我被打印了就说明子组件重新构建了")
return <div><div>
})
复制代码
总结
- useCallBack不要每个函数都包一下,否则就会变成反向优化,useCallBack本身就是需要一定性能的
- useCallBack并不能阻止函数重新创建,它只能通过依赖决定返回新的函数还是旧的函数,从而在依赖不变的情况下保证函数地址不变
- 注意: useCallBack需要配合React.memo使用
useImperativeHandle
新钩子,用于调用子组件内部的属性和方法,代替forword转发,非常方便。
具体例子如下:
import React, { useImperativeHandle, useRef } from "react";
const Imperative = () => {
const mRef = useRef();
const handleC = ()=>{
mRef.current?.handlerConsole()
console.log(111, mRef)
}
return (
<div>
测试useImperativeHandle
<button onClick={handleC}>调用子组件方法</button>
<br />
<Child mRef={mRef}/>
</div>
);
};
const Child = props => {
// 引用子组件方式 useImperativeHandle
useImperativeHandle(props.mRef,
() => {
return {
handlerConsole
}
}
)
const handlerConsole = ()=>{
console.log('子组件相关操作');
}
return <div>
这是子组件,需要调用方法
<button onClick={handlerConsole}>get</button>
</div>
};
export default Imperative;
useDeferredValue
作用: 让你推迟更新UI的一部分。
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
当 useDeferredValue
收到不同的值(与 Object.is
相比)时,除了当前渲染(当它仍然使用以前的值时)之外,它还会安排在后台使用新值重新渲染。
后台重新渲染是可中断的:
如果 value
有另一个更新,React 将从头开始重新启动后台重新渲染。例如,如果用户键入输入的速度快于接收延迟值的图表可以重新渲染的速度,则图表只会在用户停止输入后重新渲染。
非常nice,相当于防抖了~~~
useDeferredValue
与 集成 (意思是不用suppense)如果新值引起的后台更新暂停了 UI,**用户将看不到回退。**在数据加载之前,它们将看到旧的延迟值。如末尾的 — 代码1
由 useDeferredValue
引起的背景重新渲染**在提交到屏幕之前不会触发副作用。**如果后台重新渲染挂起,其副作用将在数据加载和 UI 更新后运行。
//无使用
//代码0
import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';
//Suspense 显示loadding 更佳
export default function App() {
const [query, setQuery] = useState('');
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={query} />
</Suspense>
</>
);
}
//使用 代码1
在下例中输入 "a"
,等待结果加载,然后编辑输入为 "ab"
。请注意,在加载新结果之前,你现在看到的不是 Suspense 回退,而是旧的结果列表:
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}> //supsense为了做对比,而不是正在的渲染
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
可以加一个视觉提示,表示,值在更新。
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = ;
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
//加了视觉提示,表示值是更新了,只是没显示。非常nice~
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
</Suspense>
</>
);
}
也可以优化组件渲染:
在此示例中,SlowList
组件中的每个项目都被人为减慢,以便你可以看到 useDeferredValue
如何让你保持输入响应。键入输入并注意在列表 “滞后” 时键入感觉很活泼。
注意:SlowList 需要加memo优化
import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';
export default function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}