一、useReducer

// 1. 定义reducer函数,根据不同的action返回不同的新状态 function reducer(state, action) { switch (action.type) { case 'INC': return state + 1 case 'DEC': return state - 1 default: return state } }
const [state, dispatch] = useReducer(reducer, 0)
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
完整代码
import { useReducer } from 'react'
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
</>
)
}
export default App

做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数
// 定义reducer import { useReducer } from 'react' // 1. 根据不同的action返回不同的新状态 function reducer(state, action) { console.log('reducer执行了') switch (action.type) { case 'INC': return state + 1 case 'DEC': return state - 1 case 'UPDATE': return action.payload default: return state } } function App() { // 2. 使用useReducer分派action const [state, dispatch] = useReducer(reducer, 0) return ( <> {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */} <button onClick={() => dispatch({ type: 'DEC' })}>-</button> {state} <button onClick={() => dispatch({ type: 'INC' })}>+</button> <button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}> update to 100 </button> </> ) } export default App
二、渲染性能优化
1、
作用:它在每次重新渲染的时候能够缓存计算的结果
useMemo(()=>{ // 根据count1返回计算的结果 },[count1])

组件有两个状态count1和count2,现在的需求是基于count1进行斐波那契数列求和,意思是只要count1发生变化,fn这个函数才会重新执行,思考一下,当组件内部的count2状态发生变化的时候,fn会重新执行吗?也会执行,因为我们知道不管哪个状态数据发生变化,组件函数都会重新执行(函数执行时的参数仍然是count1,不是count2),达到重新渲染的目的,位于组件内部的fn也会一起执行,这显然是不合理的,因为fn只跟count1相关,但是count2变化的时候居然也会执行,显然是存在浪费的,针对这个场景,useMemo这个hook函数保证只有count1发生变化的时候,fn才会重新执行,达到缓存结果的目的
// useMemo // 作用:在组件渲染时缓存计算的结果 import { useState } from 'react' function factorialOf(n) { console.log('斐波那契函数执行了',n) return n <= 0 ? 1 : n * factorialOf(n - 1) } function App() { const [count, setCount] = useState(0) // 计算斐波那契之和 const sumByCount = factorialOf(count) const [num, setNum] = useState(0) return ( <> {sumByCount} <button onClick={() => setCount(count + 1)}>+count:{count}</button> <button onClick={() => setNum(num + 1)}>+num:{num}</button> </> ) } export default App
效果如下:我们发现点击+num后,factorialOf(n)也执行了

num 改变会执行 factorialOf(count),根本原因不是 num 和 factorialOf 有关系,而是:num 是 App 组件的 state,它一变,App 整个函数就会重新执行一遍,函数体里所有语句都会重跑,包括 const sumByCount = factorialOf(count) 这一行,此时factorialOf函数执行时的参数仍然是count,而不是num。
思路: 只有count发生变化时才重新进行计算
// useMemo // 作用:在组件渲染时缓存计算的结果 import { useState,useMemo } from 'react' function factorialOf(n) { console.log('斐波那契函数执行了',n) return n <= 0 ? 1 : n * factorialOf(n - 1) } function App() { const [count, setCount] = useState(0) // 计算斐波那契之和 // const sumByCount = factorialOf(count) // 通过useMemo缓存计算结果,只有count发生变化时才重新计算 const sumByCount = useMemo(() => { return factorialOf(count) }, [count]) const [num, setNum] = useState(0) return ( <> {sumByCount} <button onClick={() => setCount(count + 1)}>+count:{count}</button> <button onClick={() => setNum(num + 1)}>+num:{num}</button> </> ) } export default App
发现num值得改变不会重新执行factorialOf函数
2、
作用:允许组件在props没有改变的情况下跳过重新渲染
默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

App是父组件,Son是子组件,如果因为状态的变化引发了App组件的重新渲染,那么Son组件就会跟着一起渲染
const MemoComponent = React.memo(function SonComponent(props){ // ... })
说明:经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
// memo // 作用:允许组件在props没有改变的情况下跳过重新渲染 import { useState } from 'react' function Son() { console.log('子组件被重新渲染了') return <div>this is son</div> } function App() { const [, forceUpdate] = useState() console.log('父组件重新渲染了') return ( <> <Son /> <button onClick={() => forceUpdate(Math.random())}>update</button> </> ) } export default App

// memo // 作用:允许组件在props没有改变的情况下跳过重新渲染 import { useState,memo } from 'react' // function Son() { // console.log('子组件被重新渲染了') // return <div>this is son</div> // } const MemoSon = memo(function Son(props){ console.log('子组件被重新渲染了') return <div>this is son</div> }) function App() { // useState() 不传初始值,返回 [undefined, setState函数] // 用数组解构的逗号占位,只取第二个元素(更新函数),忽略第一个状态值 // 变量名 forceUpdate 是自定义的,它其实就是 setState const [, forceUpdate] = useState() console.log('父组件重新渲染了') return ( <> <MemoSon /> {/* 点击时调用 forceUpdate(Math.random()),传入一个随机数作为新 state */} {/* 因为每次 Math.random() 结果都不同,React 认为 state 变了 → 触发 App 重新渲染 */} <button onClick={() => forceUpdate(Math.random())}>update</button> </> ) } export default App
效果如下:

3、
import React, { useState } from 'react'
const MemoSon = React.memo(function Son(props){
console.log('子组件被重新渲染了',props.count)
return <div>this is son</div>
})
function App() {
console.log('父组件重新渲染了')
const [count, setCount] = useState(0)
return (
<>
<MemoSon count={count} />
<button onClick={() => setCount(count + 1)}>+{count}</button>
</>
)
}
export default App
效果:

| 比较对象 | 结果 |
|---|---|
1 === 1 |
true |
'a' === 'a' |
true |
[1,2,3] === [1,2,3] |
false |
{a:1} === {a:1} |
false |
import React, { useState } from 'react'
const MemoSon = React.memo(function Son(props) {
console.log('子组件被重新渲染了',props.count)
return <div>this is Son</div>
})
function App() {
const [count, setCount] = useState(0)
return (
<>
<MemoSon count={count} />
<button onClick={() => setCount(count+1)}>
Change Count
</button>
</>
)
}
export default App
效果:

再来演示props不发生变化的情况
import React, { useState } from 'react'
const MemoSon = React.memo(function Son(props) {
console.log('子组件被重新渲染了',props.count)
return <div>this is Son</div>
})
function App() {
const [count, setCount] = useState(0)
const num = 100
return (
<>
<MemoSon count={num} />
<button onClick={() => setCount(count+1)}>
Change Count
</button>
</>
)
}
export default App
效果:初始化时子组件被渲染了一次,后面不会再被渲染

虽然在点击的时候,count发生变化,父组件重新渲染,但是父组件给子组件传递的值一直是100,子组件不再被渲染
import React, { useState } from 'react'
const MemoSon = React.memo(function Son(props) {
console.log('子组件被重新渲染了',props.list)
return <div>this is Son</div>
})
function App() {
const [count, setCount] = useState(0)
const list = [1,2,3]
return (
<>
<MemoSon list={list} />
<button onClick={() => setCount(count+1)}>
Change Count
</button>
</>
)
}
export default App
效果:

点击按钮,父组件会重新渲染,list始终是数组1,2,3,看起来子组件不会发生渲染,而实际上子组件也会跟着渲染,因为点击按钮会引发父组件的重新渲染,function App函数会重新执行,代码const list = [1,2,3]也会重新执行,而list每次都是一个全新的引用,引用不相等就会重新渲染。
import React, { useState,useMemo } from 'react'
const MemoSon = React.memo(function Son(props) {
console.log('子组件被重新渲染了',props.list)
return <div>this is Son</div>
})
function App() {
const [count, setCount] = useState(0)
// 创建一个 list,它的值是 [1, 2, 3],并且这个数组在整个组件生命周期中只创建一次,
// 之后每次重新渲染都复用同一个数组引用。 依赖数组 [] 表示"永不重新计算"。
const list = useMemo(()=>{
return [1,2,3]
},[])
return (
<>
<MemoSon list={list} />
<button onClick={() => setCount(count+1)}>
Change Count
</button>
</>
)
}
export default App

import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
// const [count, setCount] = useState(0)
const [list, setList] = useState([1, 2, 3])
return (
<>
<MemoSon list={list} />
<button onClick={() => setList([1, 2, 3])}>
{JSON.stringify(list)}
</button>
</>
)
}
export default App
怎么才能真正生效?自定义比较函数(第二个参数)
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
},
(prevProps, nextProps) => {
// 返回 true 表示"props 相等",跳过渲染
return JSON.stringify(prevProps.list) === JSON.stringify(nextProps.list)
})
function App() {
// const [count, setCount] = useState(0)
const [list, setList] = useState([1, 2, 3])
return (
<>
<MemoSon list={list} />
<button onClick={() => setList([1, 2, 3])}>
{JSON.stringify(list)}
</button>
</>
)
}
export default App
这样即使引用变了,只要内容一样,就跳过渲染。
4、
如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
import React, { useState } from 'react'
// 自定义比较函数
function arePropsEqual(oldProps, newProps) {
console.log(oldProps, newProps)
return (
oldProps.list.length === newProps.list.length &&
oldProps.list.every((oldItem, index) => {
const newItem = newProps.list[index]
console.log(newItem, oldItem)
return oldItem === newItem
})
)
}
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
}, arePropsEqual)
function App() {
console.log('父组件重新渲染了')
const [list, setList] = useState([1, 2, 3])
return (
<>
<MemoSon list={list} />
<button onClick={() => setList([1, 2, 3])}>
内容一样{JSON.stringify(list)}
</button>
<button onClick={() => setList([4, 5, 6])}>
内容不一样{JSON.stringify(list)}
</button>
</>
)
}
export default App
5、
// useCallBack import { memo, useState } from 'react' const Input = memo(function Input({onChange}) { // memo 期望:props 不变时跳过渲染 console.log('子组件组件渲染了') // Input 接收一个 prop:onChange return <input type="text" onChange={(e)=>onChange(e.target.value)}></input> }) function App() { // 传给子组件的函数(函数为引用类型) // 父组件 App 每次重新渲染,changeHandler 都会被重新创建成一个新函数(引用变了) const changeHandler = (value) => console.log(value) // 触发父组件重新渲染的函数 const [count,setCount] = useState(0) return ( <div> <Input onChange={changeHandler} /> <button onClick={() => setCount(count + 1)}>{count}</button> </div> ) } export default App
效果如下:

useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
// useCallBack import { memo, useState,useCallback } from 'react' const Input = memo(function Input({onChange}) { // memo 期望:props 不变时跳过渲染 console.log('子组件组件渲染了') // Input 接收一个 prop:onChange return <input type="text" onChange={(e)=>onChange(e.target.value)}></input> }) function App() { // 传给子组件的函数(函数为引用类型) //把 (value) => console.log(value) 这个函数缓存起来, // 依赖数组 [] 表示它只在首次渲染时创建一次,之后每次重新渲染都复用同一个函数引用。 const changeHandler = useCallback((value) => console.log(value),[]) // 触发父组件重新渲染的函数 const [count,setCount] = useState(0) return ( <div> <Input onChange={changeHandler} /> <button onClick={() => setCount(count + 1)}>{count}</button> </div> ) } export default App
效果如下:

三、
1、

import { forwardRef,useRef } from 'react'
const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
return <input type="text" ref = {ref}></input>
})
function App() {
const inputRef = useRef(null)
return (
<div>
<Input ref={inputRef} />
</div>
)
}
export default App
在父组件中使用useRef这个hook函数得到一个inputRef对象,把inputRef通过ref绑定到子组件,子组件使用forwardRef进行包裹,在子组件里面把inputRef再次通过ref标识绑定到你想要获取的input元素上去,看起来像透传的机制,在父组件中就可以通过inputRef对象拿到子组件的input元素了。
import { forwardRef,useRef } from 'react'
const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
return <input type="text" ref = {ref}></input>
})
function App() {
const inputRef = useRef(null)
const showRef = () =>{
console.log(inputRef)
inputRef.current.focus()
}
return (
<div>
<Input ref={inputRef} />
<button onClick={showRef}>focus</button>
</div>
)
}
export default App
效果如下:


2、

focus方法在子组件内部自行实现的,现在想要在父组件中通过ref调用子组件的focus方法实现子组件input输入框的聚焦

完整代码如下:
import { forwardRef,useImperativeHandle,useRef } from 'react'
const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
const inputRef = useRef(null)
// 实现聚焦逻辑函数
const focusHandler = ()=>{
inputRef.current.focus()
}
// 暴露函数给父组件使用
useImperativeHandle(ref,()=>{
return {focusHandler}
})
return <input type="text" ref = {inputRef}></input>
})
function App() {
const sonRef = useRef(null)
const focus = () =>{
console.log(sonRef.current)
sonRef.current.focusHandler()
}
return (
<div>
<Input ref={sonRef} />
<button onClick={focus}>focus</button>
</div>
)
}
export default App
效果:


四、Class API

顾名思义,Class API就是使用ES6支持的原生Class API来编写React组件
import { Component } from 'react'
class Counter extends Component{
// 定义状态变量
state = {
count: 0
}
// 事件回调
clickHandler = ()=>{
this.setState({
count: this.state.count + 1
})
}
//UI模板(JSX)
render(){
return <button onClick={this.clickHandler}>{this.state.count}</button>
}
}
function App() {
return (
<div>
<Counter/>
</div>
)
}
export default App
效果:

类组件的生命周期函数

import { Component, useState } from 'react'
class Son extends Component{
// 声明周期函数
// 组件渲染完毕自动执行一次
componentDidMount(){
console.log('组件渲染完毕,请求发送起来')
// 开启定时器
this.timer = setInterval(()=>{
console.log('定时器运行中')
},1000)
}
// 组件卸载的时候执行
componentWillUnmount(){
console.log('组件Son被卸载了')
// 清除定时器
clearInterval(this.timer)
} //UI模板(JSX)
render(){ //render函数是必须要有的
return <div>i am Son</div>
}
}
function App() {
const [show,setShow] = useState(true)
return (
<div>
{show && <Son/>}
<button onClick={()=>setShow(false)}>卸载子组件</button>
</div>
)
}
export default App

1. 父传子:通过prop绑定数据
2. 子传父:通过prop绑定父组件中的函数,子组件调用
3. 兄弟通信:状态提升,通过父组件做桥接
import { Component, useState } from 'react'
class Son extends Component{
// 使用this.props.msg
render(){ //render函数是必须要有的
return <div>i am Son{this.props.msg}</div>
}
}
class Parent extends Component{
state = {
msg: 'this is parent msg'
}
render(){ //render函数是必须要有的
return <div>i am Parent<Son msg={this.state.msg}/></div>
}
}
function App() {
const [show,setShow] = useState(true)
return (
<div>
<Parent/>
</div>
)
}
export default App
效果:

案例2
// class API import { Component } from 'react' class Son extends Component { render() { const { count } = this.props return <div>this is Son, {count}</div> } } class App extends Component { // 状态变量 state = { count: 0, } setCount = () => { this.setState({ count: this.state.count + 1, }) } // UI模版 render() { return ( <> <Son count={this.state.count} /> <button onClick={this.setCount}>+</button> </> ) } } export default App
import { Component, useState } from 'react'
class Son extends Component{
// 使用this.props.msg
render(){ //render函数是必须要有的
return <div>
<div>i am Son{this.props.msg}</div>
<button onClick={()=>this.props.onGetSonMsg('我是Son组件中的数据')}>sendMsgToParent</button>
</div>
}
}
class Parent extends Component{
state = {
msg: 'this is parent msg'
}
getSonMsg = (sonMsg)=>{
console.log(sonMsg)
}
render(){ //render函数是必须要有的
return <div>i am Parent<Son msg={this.state.msg} onGetSonMsg={this.getSonMsg}/></div>
}
}
function App() {
const [show,setShow] = useState(true)
return (
<div>
<Parent/>
</div>
)
}
export default App
效果如下:

案例2
// class API import { Component } from 'react' class Son extends Component { render() { const { msg, onGetSonMsg } = this.props return ( <> <div>this is Son, {msg}</div> <button onClick={() => onGetSonMsg('this is son msg')}> changeMsg </button> </> ) } } class App extends Component { // 状态变量 state = { msg: 'this is initail app msg', } onGetSonMsg = (msg) => { this.setState({ msg }) } // UI模版 render() { return ( <> <Son msg={this.state.msg} onGetSonMsg={this.onGetSonMsg} /> </> ) } } export default App
五、zustand
import { create } from 'zustand'
const useStore = create((set) => {
return {
count: 0,
inc: () => {
set(state => ({ count: state.count + 1 }))
}
}
})
export default useStore
import useStore from './store/useCounterStore.js' function App() { const { count, inc } = useStore() return <button onClick={inc}>{count}</button> } export default App
import { create } from 'zustand'
const URL = 'http://geek.itheima.net/v1_0/channels'
const useStore = create((set) => {
return {
count: 0,
ins: () => {
return set(state => ({ count: state.count + 1 }))
},
channelList: [],
fetchChannelList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({channelList: jsonData.data.channels})
}
}
})
export default useStore
import { useEffect } from 'react'
import useChannelStore from './store/channelStore'
function App() {
const { channelList, fetchChannelList } = useChannelStore()
useEffect(() => {
fetchChannelList()
}, [fetchChannelList])
return (
<ul>
{channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
export default App
import { create } from 'zustand'
// 创建counter相关切片
const createCounterStore = (set) => {
return {
count: 0,
setCount: () => {
set(state => ({ count: state.count + 1 }))
}
}
}
// 创建channel相关切片
const createChannelStore = (set) => {
return {
channelList: [],
fetchGetList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({ channelList: jsonData.data.channels })
}
}
}
// 组合切片
const useStore = create((...a) => ({
...createCounterStore(...a),
...createChannelStore(...a)
}))
function App() { const {count, inc, channelList, fetchChannelList } = useStore() return ( <> <button onClick={inc}>{count}</button> <ul> {channelList.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ) } export default App
简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具
npm i simple-zustand-devtools -D
import create from 'zustand' // 导入核心方法 import { mountStoreDevtool } from 'simple-zustand-devtools' // 省略部分代码... // 开发环境开启调试 if (process.env.NODE_ENV === 'development') { mountStoreDevtool('channelStore', useChannelStore) } export default useChannelStore

浙公网安备 33010602011771号