React篇——第六章 React进阶特性与状态管理

目录

引言

一、useReducer:状态管理的另一种方式

基础使用

更新流程

分派action传参

二、渲染性能优化

useMemo:缓存计算结果

场景分析

React.memo:跳过不必要的渲染

组件默认的渲染机制

使用React.memo优化

props的比较机制

自定义比较函数

useCallback:缓存回调函数

场景分析

使用useCallback缓存函数

三、组件通信:forwardRef和useImperativeHandle

forwardRef:暴露DOM节点给父组件

useImperativeHandle:暴露子组件内部方法

[四、Class API:传统的组件编写方式](#四、Class API:传统的组件编写方式)

基础体验

组件通信

父传子

子传父

五、Zustand:轻量级状态管理库

快速上手

创建store

绑定组件

异步支持

创建store

绑定组件

切片模式

对接DevTools

安装调试包

配置调试工具

打开React调试工具

六、总结

最佳实践


引言

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的更新流程如下:

  1. 调用dispatch函数,传入一个action对象

  2. 触发reducer函数,根据action.type执行相应的逻辑

  3. reducer函数返回新的状态

  4. 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的一些进阶特性和状态管理方案,包括:

  1. useReducer:用于管理多个相对关联的状态数据,类似于Redux的状态管理方式。

  2. 渲染性能优化

    • useMemo:缓存计算结果,避免不必要的计算。

    • React.memo:跳过不必要的组件渲染。

    • useCallback:缓存回调函数,保持引用稳定。

  3. 组件通信

    • forwardRef:暴露DOM节点给父组件。

    • useImperativeHandle:暴露子组件内部方法给父组件。

  4. Class API:传统的组件编写方式,适用于一些特定场景。

  5. Zustand:轻量级状态管理库,使用简单,API友好。

这些特性和工具可以帮助开发者更好地构建React应用,提高应用的性能和可维护性。在实际开发中,我们应该根据具体的场景选择合适的工具和方法,以达到最佳的开发效果。

最佳实践

  1. 状态管理

    • 对于简单的状态,使用useState即可。

    • 对于复杂的状态逻辑,使用useReducer

    • 对于全局状态,使用Zustand等状态管理库。

  2. 性能优化

    • 对于耗时的计算,使用useMemo缓存结果。

    • 对于频繁渲染的组件,使用React.memo避免不必要的渲染。

    • 对于传递给子组件的回调函数,使用useCallback缓存函数引用。

  3. 组件通信

    • 对于父子组件通信,使用props和回调函数。

    • 对于需要访问子组件DOM节点的场景,使用forwardRef

    • 对于需要调用子组件内部方法的场景,使用useImperativeHandle

  4. 代码组织

    • 对于大型应用,使用切片模式拆分和组织状态管理逻辑。

    • 合理使用React DevTools和Zustand DevTools进行调试。

通过掌握这些进阶特性和最佳实践,开发者可以构建出更加高效、可维护的React应用。

相关推荐
菜鸟茜2 小时前
ES6核心知识解析03:为什么用let和const取代var
前端·javascript·es6
wuhen_n2 小时前
复杂任务拆解:让AI像项目经理一样思考
前端·javascript·ai编程
Arya_aa2 小时前
拿取gitee中现成的vue-manage-system模板
前端·javascript·vue.js
白嫖叫上我2 小时前
弹窗之封装拖拽与拖动拉伸
前端
清汤饺子2 小时前
Spec Kit:让 AI 编程从 Vibe Coding 到 Spec First
前端·javascript·后端
爱学习的小仙女!3 小时前
面试题 前端(二)元素显示模式 块元素行内元素区别
前端·前端面试题
酉鬼女又兒3 小时前
零基础快速入门前端蓝桥杯 Web 备考:AJAX 与 XMLHttpRequest 核心知识点及实战(可用于备赛蓝桥杯Web应用开发)
前端·ajax·职场和发展·蓝桥杯·css3·js
Sammyyyyy3 小时前
Node.js、Bun 与 Deno,2026 年后端运行时选择指南
前端·后端·node.js·servbay