当前位置: 首页 > news >正文

useSelector useDispatch

下面,我们来系统的梳理关于 React-Redux Hooks:useSelector & useDispatch 的基本知识点:


一、核心概念概述

1.1 React-Redux Hooks 简介

React-Redux 提供了两个核心 Hooks,用于在函数组件中访问 Redux store:

  • useSelector:从 store 中提取状态数据
  • useDispatch:获取 dispatch 函数以派发 actions

1.2 与传统 connect 对比

特性Hooks 方式connect 方式
代码量简洁较冗长
组件类型函数组件类组件/函数组件
性能优化浅比较/自定义比较mapStateToProps 比较
学习曲线
嵌套问题可能产生嵌套地狱

二、useSelector 深度解析

2.1 基本用法

import { useSelector } from 'react-redux';function UserProfile() {const user = useSelector(state => state.user);return (<div><h2>{user.name}</h2><p>Email: {user.email}</p></div>);
}

2.2 选择器函数详解

选择器函数接收整个 Redux state 作为参数:

// 基本选择
const counter = useSelector(state => state.counter.value);// 派生数据
const totalPrice = useSelector(state => state.cart.items.reduce((total, item) => total + item.price * item.quantity, 0)
);// 多状态组合
const { user, theme } = useSelector(state => ({user: state.user,theme: state.theme
}));

2.3 性能优化:避免不必要的渲染

问题:每次状态变化都重新渲染
// ❌ 错误:每次state变化都触发渲染
const user = useSelector(state => state.user);// ✅ 正确:仅当user变化时渲染
const user = useSelector(state => state.user, shallowEqual);
解决方案:
  1. 使用浅比较
import { shallowEqual, useSelector } from 'react-redux';const user = useSelector(state => state.user, shallowEqual);
  1. 创建记忆化选择器
import { createSelector } from '@reduxjs/toolkit';const selectUser = state => state.user;
const selectTheme = state => state.theme;// 创建记忆化选择器
const selectUserAndTheme = createSelector([selectUser, selectTheme],(user, theme) => ({ user, theme })
);// 组件中使用
const { user, theme } = useSelector(selectUserAndTheme);
  1. 按字段精确选择
// 仅当name变化时重新渲染
const userName = useSelector(state => state.user.name);

三、useDispatch 深度解析

3.1 基本用法

import { useDispatch } from 'react-redux';
import { increment } from './counterSlice';function CounterButton() {const dispatch = useDispatch();return (<button onClick={() => dispatch(increment())}>增加</button>);
}

3.2 实践:避免重复渲染

问题:每次渲染创建新回调
// ❌ 错误:每次渲染创建新函数
<button onClick={() => dispatch(increment())}>
解决方案:使用 useCallback
import { useCallback } from 'react';function CounterButton() {const dispatch = useDispatch();// ✅ 正确:记忆化回调函数const handleIncrement = useCallback(() => {dispatch(increment());}, [dispatch]);return (<button onClick={handleIncrement}>增加</button>);
}

3.3 封装自定义 dispatch 函数

export function useCartActions() {const dispatch = useDispatch();const addToCart = useCallback((product) => {dispatch(addItem(product));}, [dispatch]);const removeFromCart = useCallback((productId) => {dispatch(removeItem(productId));}, [dispatch]);return { addToCart, removeFromCart };
}// 在组件中使用
function ProductItem({ product }) {const { addToCart } = useCartActions();return (<div><h3>{product.name}</h3><button onClick={() => addToCart(product)}>加入购物车</button></div>);
}

四、组合使用案例

4.1 电商购物车组件

import React from 'react';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { addItem, removeItem, updateQuantity 
} from './cartSlice';function ShoppingCart() {// 选择购物车状态const cart = useSelector(state => state.cart, shallowEqual);const dispatch = useDispatch();// 计算总价const total = cart.items.reduce((sum, item) => sum + (item.price * item.quantity),0);// 处理数量变更const handleQuantityChange = (id, quantity) => {if (quantity > 0) {dispatch(updateQuantity({ id, quantity }));} else {dispatch(removeItem(id));}};return (<div className="cart"><h2>购物车 ({cart.items.length} 件商品)</h2>{cart.items.length === 0 ? (<p>您的购物车是空的</p>) : (<><ul>{cart.items.map(item => (<li key={item.id}><div><h4>{item.name}</h4><p>¥{item.price.toFixed(2)}</p></div><div className="quantity-controls"><button onClick={() => handleQuantityChange(item.id, item.quantity - 1)}disabled={item.quantity <= 1}>-</button><span>{item.quantity}</span><button onClick={() => handleQuantityChange(item.id, item.quantity + 1)}>+</button><button onClick={() => dispatch(removeItem(item.id))}className="remove">删除</button></div></li>))}</ul><div className="cart-total"><p>总计: ¥{total.toFixed(2)}</p><button>结算</button></div></>)}</div>);
}

4.2 异步操作处理

import { useDispatch } from 'react-redux';
import { fetchUser } from './userSlice';function UserLoader({ userId }) {const dispatch = useDispatch();const user = useSelector(state => state.user.data[userId]);const status = useSelector(state => state.user.status);useEffect(() => {if (!user) {dispatch(fetchUser(userId));}}, [dispatch, user, userId]);if (status === 'loading') {return <div>加载中...</div>;}if (status === 'failed') {return <div>加载失败</div>;}if (!user) {return <div>用户未找到</div>;}return (<div><h3>{user.name}</h3><p>{user.email}</p></div>);
}

五、性能优化

5.1 使用 createSelector 记忆化派生数据

import { createSelector } from '@reduxjs/toolkit';// 基础选择器
const selectCartItems = state => state.cart.items;// 记忆化派生选择器
export const selectTotalPrice = createSelector([selectCartItems],items => items.reduce((total, item) => total + (item.price * item.quantity), 0)
);export const selectTotalItems = createSelector([selectCartItems],items => items.reduce((total, item) => total + item.quantity, 0)
);// 组件中使用
function CartSummary() {const totalPrice = useSelector(selectTotalPrice);const totalItems = useSelector(selectTotalItems);return (<div><p>{totalItems} 件商品</p><p>总计: ¥{totalPrice.toFixed(2)}</p></div>);
}

5.2 自定义相等比较函数

// 自定义比较函数
const shallowCompareArray = (a, b) => {if (a.length !== b.length) return false;return a.every((item, index) => item === b[index]);
};// 在useSelector中使用
const userRoles = useSelector(state => state.user.roles, shallowCompareArray
);

5.3 批量 dispatch 操作

import { batch } from 'react-redux';function ComplexAction() {const dispatch = useDispatch();const handleComplexAction = () => {// 使用batch减少渲染次数batch(() => {dispatch(action1());dispatch(action2());dispatch(action3());});};return (<button onClick={handleComplexAction}>执行复杂操作</button>);
}

六、常见问题与解决方案

6.1 过多次渲染问题

问题: 组件在无关状态变化时重新渲染
解决方案:

// ✅ 精确选择需要的数据
const userName = useSelector(state => state.user.name);// ✅ 使用浅比较
const user = useSelector(state => state.user, shallowEqual);// ✅ 使用记忆化选择器
const user = useSelector(selectUser);

6.2 循环依赖问题

问题: 在 useEffect 中 dispatch 导致循环
解决方案:

function UserComponent({ userId }) {const user = useSelector(state => state.users[userId]);const dispatch = useDispatch();useEffect(() => {// 仅在user不存在时获取数据if (!user) {dispatch(fetchUser(userId));}}, [dispatch, user, userId]); // 依赖中包含user// ...
}

6.3 过时闭包问题

问题: 回调函数捕获过时状态
解决方案:

function TimerComponent() {const dispatch = useDispatch();const counter = useSelector(state => state.counter);useEffect(() => {const timer = setInterval(() => {// ❌ 错误:捕获过时的counter// dispatch(increment());// ✅ 正确:传递当前值dispatch(incrementByAmount(1));}, 1000);return () => clearInterval(timer);}, [dispatch]); // 不依赖counter// ...
}

七、实践

7.1 useSelector 实践

  1. 精确选择:只选择组件需要的最小状态
  2. 使用记忆化选择器:用于派生数据
  3. 合理使用比较函数:默认引用比较,使用浅比较优化
  4. 避免内联函数:将选择器函数移出组件

7.2 useDispatch 实践

  1. 记忆化回调:使用 useCallback 避免重复创建函数
  2. 封装 action 创建函数:提高代码可读性和复用性
  3. 避免在渲染中 dispatch:在事件处理或 useEffect 中 dispatch
  4. 批量更新:使用 batch 减少渲染次数

7.3 整体代码组织

// feature/cart/
//   cartSlice.js     // Redux slice
//   cartSelectors.js // 选择器
//   cartActions.js   // action hooks
//   Cart.js          // 组件// cartActions.js
export function useCartActions() {const dispatch = useDispatch();const addToCart = useCallback((product) => {dispatch(addItem(product));}, [dispatch]);// 其他actions...return { addToCart };
}// cartSelectors.js
export const selectCartItems = state => state.cart.items;export const selectTotalPrice = createSelector([selectCartItems],items => items.reduce((total, item) => total + (item.price * item.quantity), 0)
);// Cart.js
import { useSelector } from 'react-redux';
import { selectCartItems, selectTotalPrice } from './cartSelectors';
import { useCartActions } from './cartActions';function Cart() {const items = useSelector(selectCartItems);const total = useSelector(selectTotalPrice);const { removeItem } = useCartActions();// ...
}

八、TypeScript 集成

8.1 类型化 useSelector

import { TypedUseSelectorHook, useSelector } from 'react-redux';
import type { RootState } from '../store';// 创建类型化的 useSelector
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;// 在组件中使用
function UserProfile() {const user = useAppSelector(state => state.user);// ...
}

8.2 类型化 useDispatch

import { useDispatch } from 'react-redux';
import type { AppDispatch } from '../store';// 创建类型化的 useDispatch
export const useAppDispatch = () => useDispatch<AppDispatch>();// 在组件中使用
function Counter() {const dispatch = useAppDispatch();const increment = () => {dispatch(increment()); // 类型安全};// ...
}

8.3 完整类型化示例

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';export const store = configureStore({reducer: {counter: counterReducer}
});// 导出类型
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';interface CounterState {value: number;
}const initialState: CounterState = {value: 0
};const counterSlice = createSlice({name: 'counter',initialState,reducers: {increment: state => {state.value += 1;},decrement: state => {state.value -= 1;},incrementByAmount: (state, action: PayloadAction<number>) => {state.value += action.payload;}}
});export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;// Counter.tsx
import React from 'react';
import { useAppSelector, useAppDispatch } from './hooks';
import { increment, incrementByAmount } from './counterSlice';function Counter() {const count = useAppSelector(state => state.counter.value);const dispatch = useAppDispatch();return (<div><span>{count}</span><button onClick={() => dispatch(increment())}>+1</button><button onClick={() => dispatch(incrementByAmount(5))}>+5</button></div>);
}

九、总结

9.1 核心知识点

  1. useSelector:从 store 中提取状态数据

    • 精确选择需要的数据
    • 使用浅比较或自定义比较优化性能
    • 使用记忆化选择器处理派生数据
  2. useDispatch:获取 dispatch 函数

    • 使用 useCallback 记忆化回调
    • 封装自定义 action 函数
    • 批量 dispatch 减少渲染

9.2 实践总结

  • 组件与状态分离:使用选择器和 action hooks
  • 性能优先:精确选择、记忆化、批量更新
  • 类型安全:使用 TypeScript 增强可靠性
  • 代码组织:按功能模块组织 slice、选择器和组件
http://www.dtcms.com/a/313242.html

相关文章:

  • 高级开发 | 零人工干预的 @Value 实时刷新
  • 直流无刷电机(一)
  • ElementUI之使用以及表单验证
  • lumerical——锥形波导偏振转换
  • 第九章:了解特殊场景下的redis
  • 解决dify前端页面中公式显示的问题
  • 字典序最小的拼接字符串(贪心+全排列)详解
  • 数据分析—numpy库
  • Pytorch-03数据的Transform
  • 2106. 摘水果,梳理思路
  • 新手向:Python制作贪吃蛇游戏(Pygame)
  • Redis面试精讲 Day 9:Redis模块开发与扩展
  • 信创数据库-DM(达梦)数据库安装教程
  • Rust:如何访问 *.ini 配置文件?
  • 【项目日志|苍穹外卖】 Day1:项目环境搭建与架构设计
  • 反向代理+网关部署架构
  • Java学习第一百零二部分——API网关
  • Claude Code入门学习笔记(五)--Claude Code命令行输入
  • 大白话讲解MCP
  • 多种单文件版分析型数据库调用底层函数对比
  • [Oracle] TO_DATE()函数
  • cs285 lecture13
  • 手机端使用表格填写表单问题
  • 复现YOLOV5+训练指定数据集
  • STM32-ESP8266通过MQTT与阿里云通讯
  • MySQL连接算法和小表驱动大表的原理
  • 李宏毅深度学习教程 第8-9章 生成模型+扩散模型
  • 【Django】-7- 实现注册功能
  • 09.Redis 常用命令
  • Android 之 蓝牙通信(2.0 经典)