useLayoutEffect
useLayoutEffect在useEffect之前触发
这样会闪屏,因为是异步的,两次都渲染了
import {useEffect,useState } from 'react';
function App() {
const [msg,setMsg] = useState('hello App')
useEffect(() => {
setMsg('hello useEffect')
});
return (
<div >
{msg}
</div>
);
}
export default App;
换上useLayoutEffect不闪屏,在异步的情况是等两者都执行完以后渲染
import {useLayoutEffect,useState } from 'react';
function App() {
const [msg,setMsg] = useState('hello App')
useLayoutEffect(() => {
setMsg('hello useEffect')
});
return (
<div >
{msg}
</div>
);
}
export default App;
触发多次的时候还是useEffect好,解决闪屏问题用useLayoutEffect
useInsertionEffect Dom
触发顺序是123
import { useEffect, useLayoutEffect, useState } from 'react'
function App() {
//触发顺序:3->2->1
useEffect(() => {
console.log(1)
})
useLayoutEffect(() => {
console.log(2)
})
useInsertionEffect(() => {
console.log(3)
})
return <div></div>
}
export default App
有些团队喜欢在js里写样式,这叫CSS-in-Js
有三种实现方法:
useEffect和useLayoutEffect是在jsx渲染后走的样式,会造成浏览器的频繁计算,有性能影响,一般都用useInsertionEffect
import { useInsertionEffect, useRef } from 'react'
function App() {
const ref = useRef(null)
useInsertionEffect(() => {
const style = document.createElement('style')
style.innerHTML = `
.box{
background:red;
width:100px;
height:100px;
}`
document.head.appendChild(style)
})
return <div className='box' ref={ref}>哈哈哈</div>
}
export default App
Reducer和Context
Reducer的统一状态管理集合
Reducer是整合状态的工具,我们之前学过useState来组织状态,Reducer可以规划复杂的组件逻辑
内里的逻辑其实就是switch
import { useActionState, useReducer, useState } from 'react'
//由外部函数来完成逻辑操作
function listReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, { id: 4, text: 'ddd' }]
case 'edit':
return state.map((item) => {
if (action.id === item.id) {
return { ...state, id:action.id,text: 'new ' + item.text }
} else {
return item
}
})
case 'remove':
return state.filter((item) => {
if (action.id === item.id) {
return false
} else return true
})
}
}
function App() {
const [list, dispatch] = useReducer(listReducer, [
{ id: 1, text: 'aaa' },
{ id: 2, text: 'bbb' },
{ id: 3, text: 'ccc' },
])
return (
<div>
<input type='text' />
<button
onClick={() => {
dispatch({ type: 'add' })
}}
>
添加
</button>
<ul>
{list.map((item) => {
return (
<li key={item.id}>
{item.text}
<button
onClick={() => {
dispatch({ type: 'edit', id: item.id })
}}
>
编辑
</button>
<button
onClick={() => {
dispatch({ type: 'remove', id: item.id })
}}
>
删除
</button>
</li>
)
})}
</ul>
</div>
)
}
export default App
之前我们学了Immer可以整合对状态变量的修改,其实useImmerReducer可以实现整合状态变量的修改+起到多个状态管理的作用
这是用immer改写的代码,效果也是一样的
import { useImmerReducer } from 'use-immer'
//由外部函数来完成逻辑操作
function listReducer(draft, action) {
switch (action.type) {
case 'add':
draft.push({ id: 4, text: 'ddd' })
break
case 'edit':
const value = draft.find((item) => item.id === action.id)
value.text='new '+value.text
break
case 'remove':
const index = draft.findIndex((item) => item.id === action.id)
draft.splice(index,1)
}
}
function App() {
const [list, dispatch] = useImmerReducer(listReducer, [
{ id: 1, text: 'aaa' },
{ id: 2, text: 'bbb' },
{ id: 3, text: 'ccc' },
])
return (
<div>
<input type='text' />
<button
onClick={() => {
dispatch({ type: 'add' })
}}
>
添加
</button>
<ul>
{list.map((item) => {
return (
<li key={item.id}>
{item.text}
<button
onClick={() => {
dispatch({ type: 'edit', id: item.id })
}}
>
编辑
</button>
<button
onClick={() => {
dispatch({ type: 'remove', id: item.id })
}}
>
删除
</button>
</li>
)
})}
</ul>
</div>
)
}
export default App
Context向组件深层传递数据
props通常只能实现父子间的信息传递,Context是跨组件通信的一种方案
比如我们在这里三个组件,实现祖孙级别的通信👇
function Tittle({ count }) {
return (
<div>
Hello Tittle
{'我是Tittle,这是我接收的参数:'}
{count}
</div>
)
}
function Head({ count }) {
return (
<div>
Hello Head
<Tittle count={count} />
</div>
)
}
function App() {
return (
<div>
Hello App
<Head count={123} />
</div>
)
}
export default App
count通过App->Head,最后传给Tittle
如果使用Context就可以跨组件通信了
import { createContext, useContext } from 'react'
const Context = createContext()//定义钩子,尽量大写
function Tittle() {
const value = useContext(Context)//定义接收的参数
return (
<div>
Hello Tittle
{'我是Tittle,这是我接收的参数:'}
{value}
</div>
)
}
function Head() {
return (
<div>
Hello Head
<Tittle />
</div>
)
}
function App() {
return (
<div>
Hello App
<Context.Provider value={123}>'{提供数据}'
<Head />
</Context.Provider>
</div>
)
}
export default App
这里传递的参数只有一个值,如果传递多个可以写成数组或者对象:
function App() {
return (
<div>
Hello App
<Context.Provider value={[1,2,3,4,5]}>
<Head />
</Context.Provider>
</div>
)
}
import { createContext, useContext } from 'react'
const Context = createContext()
function Tittle() {
const value = useContext(Context)
return (
<div>
Hello Tittle
{'我是Tittle,这是我接收的参数:'}
{'id:' + value.id}
{'text:' + value.text}
</div>
)
}
function Head() {
return (
<div>
Hello Head
<Tittle />
</div>
)
}
function App() {
return (
<div>
Hello App
<Context.Provider value={{id:1,text:'aaa'}}>
<Head />
</Context.Provider>
</div>
)
}
export default App
但是不管传递多少个数据,只能通过value来传递
跨组件传递的状态变量,也会在渲染的时候重新传递、重新渲染
Reducer配合Context实现共享状态管理
实现父子通信用props,实现深层通信用context,实现兄弟间通信怎么做?
一个方法是使用状态提升,把状态提到两个兄弟的公共区域,再从公共区域通过父子通信的方式传回去,但是不够灵活
太复杂的可以用第三方库
但是React里,把这俩玩意一起用,Context可以跨组件通信,Reducer可以整合组件更新逻辑,也可以实现兄弟通信
六月份学长说建议以后使用redux,反正我也看不懂,就这样吧
memo
我们在父组件里调用子组件,父组件有改变,子组件内部无需渲染的情况下,其实还是会渲染的👇
可以看见子组件里面的随机数在改变
为了节省性能,可以使用memo来优化效率
import { useState,memo } from 'react'
const Head = memo(function Head() {
return (
<div>
Hello Head,{ Math.random()}
</div>
)
}
)
function App() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
Hello App
<button onClick={handleClick}></button>
<Head />
</div>
)
}
export default App
可以看见随机数并没有改变
useMemo对计算结果进行缓存
import { useState,memo } from 'react'
const Head = memo(function Head() {
return (
<div>
Hello Head,{ Math.random()}
</div>
)
}
)
function App() {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('hello React')
const list=[msg.toLocaleLowerCase(),msg.toLocaleUpperCase()]
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
Hello App
<button onClick={handleClick}></button>
<Head list={list} />
</div>
)
}
export default App
按理来说,Head加了memo应该是不会重新刷新的对吧
但是其实刷新两次,随机数还是不同的
因为引用类型的判别(之前说过)
这时候要用useMemo来解决引用类型在Object.Is()方法被判定为不同导致重新渲染耗费性能的问题
useMemo是靠缓存上次结果,拿上次结果做对比的
import { useState,memo,useMemo } from 'react'
const Head = memo(function Head() {
return (
<div>
Hello Head,{ Math.random()}
</div>
)
}
)
function App() {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('hello React')
const list = useMemo(() => [msg.toLocaleLowerCase(),msg.toLocaleUpperCase()])//只有msg被修改时,list才会重新计算,其他情况都是拿上次缓存的值
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
Hello App
<button onClick={handleClick}></button>
<Head list={list} />
</div>
)
}
export default App
useCallback对函数进行缓存
上面的代码我们引用的是数组,这里写一个函数:
import { useState,memo,useMemo } from 'react'
const Head = memo(function Head() {
return (
<div>
Hello Head,{ Math.random()}
</div>
)
}
)
function App() {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('hello React')
const fn = () => {
console.log(msg)
}
//const list = useMemo(() => [msg.toLowerCase(),msg.toUpperCase()],[msg])//只有msg被修改时,list才会重新计算,其他情况都是拿上次缓存的值
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
Hello App
<button onClick={handleClick}>点我</button>
<Head fn={fn} />
</div>
)
}
export default App
还是会多渲染,所以我们用useCallback,useCallback其实就是省略了useMemo里面套两个回调的写法:
import { useState,memo,useMemo, useCallback } from 'react'
const Head = memo(function Head() {
return (
<div>
Hello Head,{ Math.random()}
</div>
)
}
)
function App() {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('hello React')
const fn = useCallback(() => {
console.log(msg)
},[msg])
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
Hello App
<button onClick={handleClick}>点我</button>
<Head fn={fn} />
</div>
)
}
export default App
startTransition方法及并发模式
我们react居然还有并发
import { useState,memo,useMemo, useCallback, startTransition } from 'react'
function List({ query}) {
const items = []
const word = 'hello World'
if (query !== '' && word.includes(query)) {
const arr = word.split(query)
for (let i = 0; i < 1000; i++) {
items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
}
} else {
for(let i=0;i<1000;i++){
items.push(<li key={i}>{ word}</li>)
}
}
return (
<ul>
{items}
</ul>
)
}
function App() {
const [search, setSearch] = useState('')
const [query,setQuery]=useState('')
const handleChange = (e) => {
//紧急
setSearch(e.target.value)
//将这个任务设置为非紧急
startTransition(() => {
setQuery(e.target.value)})
}
return (
<div>
Hello App
<input type="text" value={search} onChange={handleChange} />
<List query={ query} />
</div>
)
}
export default App
设计成非紧急任务以后,就先在输入框流畅的显示输入的字符串,再染色
如果两个都是紧急任务的话,就得等两个都完成
useTransition与useDeferredValue
在加载的时候显示loading
import { useState,memo,useMemo, useCallback, startTransition ,useTransition} from 'react'
function List({ query}) {
const items = []
const word = 'hello World'
if (query !== '' && word.includes(query)) {
const arr = word.split(query)
for (let i = 0; i < 1000; i++) {
items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
}
} else {
for(let i=0;i<1000;i++){
items.push(<li key={i}>{ word}</li>)
}
}
return (
<ul>
{items}
</ul>
)
}
function App() {
const [search, setSearch] = useState('')
const [query, setQuery] = useState('')
const [pending, startTransition] = useTransition()
const handleChange = (e) => {
//紧急
setSearch(e.target.value)
//将这个任务设置为非紧急
startTransition(() => {
setQuery(e.target.value)})
}
return (
<div>
Hello App
<input type="text" value={search} onChange={handleChange} />
{pending && <div>loading...</div>}
<List query={ query} />
</div>
)
}
export default App
useDeferredValue的使用
import { useState,memo,useMemo, useCallback, startTransition ,useTransition, useDebugValue, useDeferredValue} from 'react'
function List({ query}) {
const items = []
const word = 'hello World'
if (query !== '' && word.includes(query)) {
const arr = word.split(query)
for (let i = 0; i < 1000; i++) {
items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
}
} else {
for(let i=0;i<1000;i++){
items.push(<li key={i}>{ word}</li>)
}
}
return (
<ul>
{items}
</ul>
)
}
function App() {
const [search, setSearch] = useState('')
const query = useDeferredValue(search)
const handleChange = (e) => {
//紧急
setSearch(e.target.value)
}
return (
<div>
Hello App
<input type="text" value={search} onChange={handleChange} />
<List query={query} />
</div>
)
}
export default App
useId生成唯一id值
很少用,会在一些无障碍的操作用到
如果两次调用同一个组件,会发现这两个组件的id一样
function MyInput() {
return (
<div>
<label>密码:<input type="password" aria-describedby="password" /></label>
<p id="password">密码至少包含18个字符</p>
</div>
)
}
function App() {
return (
<div>
Hello App
<MyInput />
<MyInput />
</div>
);
}
export default App;
id相同👇但是最好不应该相同
import { useId } from 'react-id-generator'
function MyInput() {
const password = useId()
return (
<div>
<label>密码:<input type="password" aria-describedby={password} /></label>
<p id={password}>密码至少包含18个字符</p>
</div>
)
}
function App() {
return (
<div>
Hello App
<MyInput />
<MyInput />
</div>
);
}
export default App;
生成两个不同的id