目录
1.useEffect
副作用是相对于纯函数概念来说的, 除事件回调处理副作用,其他副作用尽量放在useEffect中;
- 组件首次渲染、有依赖项更新(Object.is方法判断)时,该useEffect触发
- jsx渲染完成后立马触发useEffect,也是当前作用域下最后执行的内容
- 若不传依赖项,每次都触发;若依赖为[]则只首次渲染触发
const App = () => {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('msg')
// 除事件回调处理副作用,其他副作用尽量放在useEffect
const handclick = () => {
setCount(count + 1)
}
// jsx渲染完成后立马触发useEffect,也是当前作用域下最后执行的内容
// 组件首次渲染、有依赖项更新(Object.is方法判断)时,该useEffect触发
// 若不传依赖项,每次都触发;若依赖为[]则只首次渲染触发
useEffect(() => {
const logCount = () => {
console.log(count);
}
logCount()
}, [count])
useEffect(() => {
console.log(msg);
}, [msg])
useEffect(() => {
console.log(123);
},)
return <>
hello useEffect
<button onClick={handclick}> 点击count加1 </button>
</>
}
useEffect清理工作:return在卸载组件时;下一次更新前清理作用域
import { useState } from "react"
import { useEffect } from "react"
function FetchChat(title) {
const delay = title === '情感聊天室' ? 2000 : 200
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: 1, text: title + '1' },
{ id: 12, text: title + '12' },
{ id: 13, text: title + '13 ' },
])
}, delay)
})
}
function Chat({ title }) {
const [list, setList] = useState([])
let ignore = false
useEffect(() => {
console.log('进入', title);
FetchChat(title).then((data) => {
if (!ignore) {
setList(data)
}
})
// useEffect的清理工作 在本次最后组件卸载时触发
// 防止上次操作的作用残留
return () => {
console.log('退出', title);
ignore = true
}
}, [title])
return (<>
hello Chat
<ul>
{list.map((item) => <li key={item.id}>{item.text}</li>)}
</ul>
</>)
}
// 同上Chat
// function Chat({ title }) {
// const [list, setList] = useState([])
// useEffect(() => {
// console.log('进入', title);
// let ignore = false
// async function fetchData() {
// const data = await FetchChat(title)
// if (!ignore)
// setList(data)
// }
// fetchData()
// // useEffect的清理工作 在本次最后组件卸载时触发
// // 防止上次操作的作用残留
// return () => {
// console.log('退出', title);
// ignore = true
// }
// }, [title])
// return (<>
// hello Chat
// <ul>
// {list.map((item) => <li key={item.id}>{item.text}</li>)}
// </ul>
// </>)
// }
const App = () => {
const [show, setShow] = useState(true)
const [title, setTitle] = useState('情感聊天室')
const handclick = () => {
setShow(!show)
}
const handChange = (e) => {
setTitle(e.target.value)
}
return (<>
hello App...........
{show && <Chat title={title} />}
<button onClick={handclick}>开关聊天室</button>
<select value={title} onChange={handChange}>
<option value="情感聊天室">情感聊天室</option>
<option value="体育聊天室">体育聊天室</option>
</select>
</>)
}
export default App
2. useEffectEvent
useEffectEvent:实验钩子,作用是从副作用中提取非反应性逻辑(变量改变但不触发页面渲染)
react18.2中还没该钩子 需单独引 react@experimental 、 react-dom@experimental
const {useState,useEffect,experimental_useEffectEvent} = React
function ChatRoom({title,theme}){
const themeEffectEvent = experimental_useEffectEvent(()=>{
console.log('主题:',theme)
})
useEffect(()=>{
console.log('进入:',title)
themeEffectEvent()
return ()=>{
console.log('退出:',title)
}
},[title])
return <>聊天室</>
}
3. useLayoutEffect
useLayoutEffect :组件第一次进行渲染时并不会立马完成,程序走到 useLayoutEffect 会触发第二次件加载,第一次useLayoutEffect 中的setState已经完成,会在第二次触发组件渲染时合并第一次的state,从而呈现最终的状态(不会闪屏)。
在当前某次渲染时,看着是useLayoutEffect先于useEffect,但是在多次渲染时useLayoutEffect同步会导致页面渲染效率变低(在某个操作很耗时的情况下特别明显),而useEffect异步会把耗时操作执行结果放在下次渲染,本次会把同步操作的结果先展示在页面上。
import { useState, useEffect, useLayoutEffect } from "react";
function App() {
const [msg, setMsg] = useState(' hello App')
// useEffect(() => {
// console.log('useEffect');
// })
// // useLayoutEffect 先于 useEffect触发
// useLayoutEffect(() => {
// console.log('useLayoutEffect');
// })
// useEffect(() => {
// // 页面会出现闪屏 hello App => hello useEffect
// for (let i = 0; i < 10000000; i++) {
// setMsg('hello useEffect')
// }
// })
// 不会闪屏 直接显示hello useLayoutEffect
useLayoutEffect(() => {
for (let i = 0; i < 10000000; i++) {
setMsg('hello useLayoutEffect')
}
})
return <>{msg}</>
}
export default App
结论:useEffect 是异步的,在组件渲染绘制到屏幕之后执行,而 useLayoutEffect 生效于渲染之后但在屏幕更新之前,是同步执行状态更新。
大部分情况用 useEffect 性能更好,但当 useEffect 里面需要操作dom并改变样式,需要用useLayoutEffect避免闪屏。
4.useInsertionEffect
useInsertionEffect: 触发时机在渲染之前,拿不到dom,用于css-in-js且采用运行时style标签注入的情况
import { useRef, useInsertionEffect, useLayoutEffect, useEffect } from "react";
function App() {
const myRef = useRef(null)
useEffect(() => {
console.log(3, myRef.current);
})
useLayoutEffect(() => {
console.log(2, myRef.current);
})
useInsertionEffect(() => {
// 拿不到dom 说明触发时机在渲染之前
console.log(1, myRef.current);
const style = document.createElement('style')
style.innerHTML = `
.box{
bcakground:'red';
width:500px;
heigth:100px;
}`
document.head.appendChild(style)
})
return (<div>
hello app
<div className="box" ref={myRef}>aaaa</div>
</div>)
}
export default App