目录
三、组件通信:forwardRef和useImperativeHandle
[四、Class API:传统的组件编写方式](#四、Class API:传统的组件编写方式)
引言
React作为前端开发的主流框架,提供了丰富的特性和工具来帮助开发者构建高效、可维护的应用。本文将介绍React的一些进阶特性,包括状态管理、性能优化、组件通信等方面的内容,帮助开发者更好地掌握React的核心概念和最佳实践。
一、useReducer:状态管理的另一种方式
基础使用
useReducer是React提供的一个Hook,用于管理多个相对关联的状态数据。它的使用方式类似于Redux,通过定义一个reducer函数来处理不同类型的action,从而更新状态。
javascript
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
更新流程
useReducer的更新流程如下:

-
调用
dispatch函数,传入一个action对象 -
触发reducer函数,根据action.type执行相应的逻辑
-
reducer函数返回新的状态
-
React使用新状态重新渲染组件
分派action传参
当我们需要在分派action时传递参数,可以在action对象中添加一个payload属性:
javascript
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 state + 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
二、渲染性能优化
useMemo:缓存计算结果
useMemo是React提供的一个Hook,用于在每次重新渲染时缓存计算的结果,避免不必要的计算。
场景分析
当我们有一个耗时的计算操作,并且这个计算只依赖于特定的状态变量时,使用useMemo可以避免在其他状态变量变化时重复计算。
javascript
import { useMemo, useState } from 'react'
function fib (n) {
console.log('计算函数执行了')
if (n < 3) return 1
return fib(n - 2) + fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sum = fib(count)
// 通过useMemo缓存计算结果,只有count发生变化时才重新计算
const sum = useMemo(() => {
return fib(count)
}, [count])
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
React.memo:跳过不必要的渲染
React.memo是React提供的一个高阶组件,用于在props没有改变的情况下跳过组件的重新渲染。
组件默认的渲染机制
默认情况下,当顶层组件发生重新渲染时,整个组件树的子级组件都会被重新渲染,即使子组件的props没有变化。
javascript
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
使用React.memo优化
通过使用React.memo包裹组件,可以让组件在props没有变化时跳过重新渲染:
javascript
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
const [, forceUpdate] = useState()
console.log('父组件重新渲染了')
return (
<>
<MemoSon />
<button onClick={() => forceUpdate(Math.random())}>update</button>
</>
)
}
export default App
props的比较机制
React.memo对props的比较是浅比较,底层使用Object.is进行比较。对于对象数据类型,只会对比两次的引用是否相等,如果不相等就会重新渲染。
javascript
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
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>
</>
)
}
export default App
自定义比较函数
如果我们需要更复杂的props比较逻辑,可以通过自定义比较函数来实现:
javascript
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
useCallback:缓存回调函数
useCallback是React提供的一个Hook,用于缓存回调函数,避免在每次渲染时创建新的函数引用。
场景分析
当我们给子组件传递一个回调函数作为prop时,即使使用了React.memo,如果每次渲染都创建新的函数引用,子组件仍然会重新渲染。
javascript
import { memo, useState } from 'react'
const MemoSon = memo(function Son() {
console.log('Son组件渲染了')
return <div>this is son</div>
})
function App() {
const [, forceUpate] = useState()
console.log('父组件重新渲染了')
const onGetSonMessage = (message) => {
console.log(message)
}
return (
<div>
<MemoSon onGetSonMessage={onGetSonMessage} />
<button onClick={() => forceUpate(Math.random())}>update</button>
</div>
)
}
export default App
使用useCallback缓存函数
通过使用useCallback缓存回调函数,可以在组件渲染时保持引用稳定:
javascript
import { memo, useCallback, useState } from 'react'
const MemoSon = memo(function Son() {
console.log('Son组件渲染了')
return <div>this is son</div>
})
function App() {
const [, forceUpate] = useState()
console.log('父组件重新渲染了')
const onGetSonMessage = useCallback((message) => {
console.log(message)
}, [])
return (
<div>
<MemoSon onGetSonMessage={onGetSonMessage} />
<button onClick={() => forceUpate(Math.random())}>update</button>
</div>
)
}
export default App
三、组件通信:forwardRef和useImperativeHandle
forwardRef:暴露DOM节点给父组件
forwardRef是React提供的一个函数,用于允许组件使用ref将一个DOM节点暴露给父组件。
javascript
import { forwardRef, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
return <input {...props} type="text" ref={ref} />
}, [])
function App() {
const ref = useRef(null)
const focusHandle = () => {
console.log(ref.current.focus())
}
return (
<div>
<MyInput ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
)
}
export default App
useImperativeHandle:暴露子组件内部方法
useImperativeHandle是React提供的一个Hook,用于暴露子组件内部的方法给父组件,而不是直接暴露DOM节点。
javascript
import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
// 实现内部的聚焦逻辑
const inputRef = useRef(null)
const focus = () => inputRef.current.focus()
// 暴露子组件内部的聚焦方法
useImperativeHandle(ref, () => {
return {
focus,
}
})
return <input {...props} ref={inputRef} type="text" />
})
function App() {
const ref = useRef(null)
const focusHandle = () => ref.current.focus()
return (
<div>
<MyInput ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
)
}
export default App
四、Class API:传统的组件编写方式
基础体验
Class API是使用ES6支持的原生Class API来编写React组件的方式,是React早期的组件编写方式。
javascript
// class API
import { Component } from 'react'
class Counter extends Component {
// 状态变量
state = {
count: 0,
}
// 事件回调
clickHandler = () => {
// 修改状态变量 触发UI组件渲染
this.setState({
count: this.state.count + 1,
})
}
// UI模版
render() {
return <button onClick={this.clickHandler}>+{this.state.count}</button>
}
}
function App() {
return (
<div>
<Counter />
</div>
)
}
export default App
组件通信
父传子
在Class API中,父组件可以通过props向子组件传递数据:
javascript
// 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
子传父
在Class API中,子组件可以通过调用父组件传递的回调函数来向父组件传递数据:
javascript
// 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:轻量级状态管理库
快速上手
Zustand是一个轻量级的状态管理库,使用简单,API友好,适合管理应用的全局状态。
创建store
javascript
import { create } from 'zustand'
const useStore = create((set) => {
return {
count: 0,
inc: () => {
set(state => ({ count: state.count + 1 }))
}
}
})
export default useStore
绑定组件
javascript
import useStore from './store/useCounterStore.js'
function App() {
const { count, inc } = useStore()
return <button onClick={inc}>{count}</button>
}
export default App
异步支持
Zustand对异步操作的支持非常简单,直接在函数中编写异步逻辑,最后把接口的数据放到set函数中返回即可:
创建store
javascript
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
绑定组件
javascript
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
切片模式
当单个store比较大时,可以采用切片模式进行模块拆分再组合:
javascript
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)
}))
对接DevTools
Zustand可以通过安装simple-zustand-devtools来对接React DevTools,方便调试状态:
安装调试包
javascript
npm i simple-zustand-devtools -D
配置调试工具
javascript
import create from 'zustand'
// 导入核心方法
import { mountStoreDevtool } from 'simple-zustand-devtools'
// 省略部分代码...
// 开发环境开启调试
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('channelStore', useChannelStore)
}
export default useChannelStore
打开React调试工具

六、总结
本文介绍了React的一些进阶特性和状态管理方案,包括:
-
useReducer:用于管理多个相对关联的状态数据,类似于Redux的状态管理方式。
-
渲染性能优化:
-
useMemo:缓存计算结果,避免不必要的计算。 -
React.memo:跳过不必要的组件渲染。 -
useCallback:缓存回调函数,保持引用稳定。
-
-
组件通信:
-
forwardRef:暴露DOM节点给父组件。 -
useImperativeHandle:暴露子组件内部方法给父组件。
-
-
Class API:传统的组件编写方式,适用于一些特定场景。
-
Zustand:轻量级状态管理库,使用简单,API友好。
这些特性和工具可以帮助开发者更好地构建React应用,提高应用的性能和可维护性。在实际开发中,我们应该根据具体的场景选择合适的工具和方法,以达到最佳的开发效果。
最佳实践
-
状态管理:
-
对于简单的状态,使用
useState即可。 -
对于复杂的状态逻辑,使用
useReducer。 -
对于全局状态,使用
Zustand等状态管理库。
-
-
性能优化:
-
对于耗时的计算,使用
useMemo缓存结果。 -
对于频繁渲染的组件,使用
React.memo避免不必要的渲染。 -
对于传递给子组件的回调函数,使用
useCallback缓存函数引用。
-
-
组件通信:
-
对于父子组件通信,使用props和回调函数。
-
对于需要访问子组件DOM节点的场景,使用
forwardRef。 -
对于需要调用子组件内部方法的场景,使用
useImperativeHandle。
-
-
代码组织:
-
对于大型应用,使用切片模式拆分和组织状态管理逻辑。
-
合理使用React DevTools和Zustand DevTools进行调试。
-
通过掌握这些进阶特性和最佳实践,开发者可以构建出更加高效、可维护的React应用。