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

Redux:不可变数据与纯函数的艺术

Redux:不可变数据与纯函数的艺术

状态管理的困境

随着现代 Web 应用功能的不断扩展,前端开发者面临着日益复杂的状态管理挑战。当应用从简单的表单交互发展到复杂的单页应用时,组件间共享状态的问题变得尤为突出。想象一个电商平台,购物车状态需要在商品列表、导航栏计数器和结算页面之间共享,这种跨组件的状态共享会导致:

  • 状态追踪困难:当多个组件可以修改同一状态时,很难确定状态变化的来源
  • 调试复杂度增加:状态变化路径不明确导致 bug 难以定位
  • 维护成本上升:随着应用规模扩大,组件间紧耦合的状态依赖使代码难以维护
  • 可预测性降低:当状态修改分散在各处时,应用行为变得难以预测

这些问题催生了对统一状态管理解决方案的需求。

Redux 的诞生背景

Redux 由 Dan Abramov 在 2015 年创建,其诞生并非偶然,而是对前端状态管理问题的深思熟虑回应。Redux 的灵感主要来自两个源头:

  1. Elm 架构:Elm 是一门函数式编程语言,其架构强调单向数据流和不可变状态。Redux 借鉴了 Elm 的模型-视图-更新(Model-View-Update)模式。

  2. Facebook 的 Flux:作为 React 配套的状态管理方案,Flux 提出了单向数据流的理念,但其实现相对复杂,包含多个 store 和 dispatcher。

Dan Abramov 在参加 React Europe 大会准备演讲"热加载与时间旅行调试"时,为展示状态回溯功能,设计了一个简化版的状态管理库,这就是 Redux 的雏形。Redux 保留了 Flux 的精髓,同时通过引入函数式编程概念大幅简化了其设计,它的出现恰逢其时,完美契合了 React 社区对状态管理的需求。

从 Flux 到 Redux

Flux 架构引入了单向数据流的概念,其数据流动路径为:

Action → Dispatcher → Store → View

这种模式要求所有数据变更必须通过派发(dispatch)action 来进行,使数据流动变得可预测。然而,Flux 实现上较为复杂:

  • 需要创建多个 Store
  • 需要显式注册 Dispatcher
  • Store 之间可能存在依赖关系
  • 没有明确处理副作用的机制

Redux 通过以下改进简化了这一架构:

  • 去除了 Dispatcher:Redux 将 dispatch 功能集成到 store 中
  • 单一 Store:整个应用状态集中在一个对象树中
  • 引入纯函数 Reducer:状态更新逻辑由纯函数处理,确保可预测性
  • 不可变数据更新:禁止直接修改状态,强制通过创建新状态对象来表示变化

这些简化使 Redux 更加优雅且易于理解,同时保留了 Flux 的核心优势。

Redux 核心概念

Redux 的设计建立在三个核心原则之上,这些原则共同构成了其强大而简洁的状态管理哲学。

三大原则详解

1. 单一数据源(Single Source of Truth)

Redux 要求将应用的整个状态存储在单一的 store 对象树中。这种方法带来的好处包括:

  • 简化应用状态模型:不再需要跟踪多个数据源
  • 便于状态快照:整个应用状态可通过一个对象快照保存
  • 简化调试:可以轻松观察状态变化历史
  • 服务器渲染支持:服务端可以预先生成状态并传递给客户端

例如,一个包含用户信息和待办事项的状态树可能如下:

{user: {id: 1,name: 'Alex',isLoggedIn: true},todos: [{ id: 1, text: '学习 Redux', completed: false },{ id: 2, text: '理解不可变数据', completed: true }],visibilityFilter: 'SHOW_ALL'
}
2. 状态只读(State is Read-Only)

在 Redux 中,唯一改变状态的方法是触发 action,这是一个描述发生了什么的普通对象。这一原则确保:

  • 视图和网络请求不能直接修改状态
  • 状态修改集中且按严格顺序执行
  • 所有修改可追踪
  • 便于实现撤销/重做功能

强制通过 action 修改状态看似繁琐,但这种约束为应用带来了可预测性,让复杂的状态变化变得可管理。

3. 使用纯函数修改(Changes are Made with Pure Functions)

Reducer 是指定状态如何响应 action 变化的纯函数,它接收当前状态和 action,返回新的状态:

(previousState, action) => newState

纯函数的特性保证了状态变化的可预测性和可测试性,避免了副作用带来的不确定性。

核心组件详解

Store

Store 是 Redux 应用中的核心,它承担着以下责任:

  • 持有应用状态
  • 提供 getState() 方法访问状态
  • 提供 dispatch(action) 方法更新状态
  • 通过 subscribe(listener) 注册监听器
  • 通过 unsubscribe() 取消监听

创建 store 非常简单:

import { createStore } from 'redux'
import rootReducer from './reducers'// 创建 store,传入根 reducer
const store = createStore(rootReducer,// 可选的初始状态initialState,// 可选的增强器(如中间件)enhancer
)// 访问状态
console.log(store.getState())// 订阅状态变化
const unsubscribe = store.subscribe(() => {console.log('状态已更新:', store.getState())
})// 分发 action 触发状态更新
store.dispatch({ type: 'INCREMENT' })// 取消订阅
unsubscribe()

在实际应用中,通常会为 store 添加中间件以处理异步操作、日志记录等:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk' // 处理异步操作
import logger from 'redux-logger' // 记录状态变化日志
import rootReducer from './reducers'const store = createStore(rootReducer,applyMiddleware(thunk, logger)
)
Action

Action 是向 store 传递数据的唯一方式,它是一个普通 JavaScript 对象,必须包含一个 type 属性标识动作类型,其余结构自定义。设计良好的 action 应满足:

  • 具有描述性的 type 值,通常为字符串常量
  • 包含尽可能少的必要数据
  • 可序列化,方便存储和传输

为避免直接创建 action 对象带来的重复和错误,通常使用"action 创建函数":

// Action 类型常量
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'// Action 创建函数
export function addTodo(text) {return {type: ADD_TODO,payload: {id: Date.now(), // 生成唯一 IDtext,completed: false}}
}export function toggleTodo(id) {return {type: TOGGLE_TODO,payload: { id }}
}// 使用 action 创建函数
dispatch(addTodo('学习 Redux'))

Action 的 type 通常使用字符串常量而非直接字符串,这有助于避免拼写错误并便于集中管理。

Reducer

Reducer 是 Redux 的核心,它负责指定应用状态如何响应 action 变化。reducer 是一个纯函数,接收当前状态和 action 作为参数,返回新状态。一个典型的 reducer 结构如下:

const initialState = { todos: [] }function todoReducer(state = initialState, action) {switch (action.type) {case 'ADD_TODO':// 返回新状态,不修改原状态return {...state, // 复制原状态所有属性todos: [...state.todos, action.payload] // 添加新待办项}case 'TOGGLE_TODO':return {...state,todos: state.todos.map(todo =>todo.id === action.payload.id? { ...todo, completed: !todo.completed } // 更新匹配到的待办项: todo // 保持其他待办项不变)}default:// 对于不认识的 action,返回原状态return state}
}

关于 reducer 需要特别注意:

  1. 默认值处理:提供初始状态作为默认参数
  2. 处理未知 action:对不匹配的 action 类型返回当前状态
  3. 不可变更新:不直接修改原状态对象,而是创建新对象
  4. 纯函数特性:同样的输入必定得到同样的输出,无副作用

随着应用复杂度增加,可以使用 combineReducers() 将多个 reducer 合并:

import { combineReducers } from 'redux'
import todosReducer from './todosReducer'
import filterReducer from './filterReducer'
import userReducer from './userReducer'const rootReducer = combineReducers({todos: todosReducer,visibilityFilter: filterReducer,user: userReducer
})// 生成的状态结构:
// {
//   todos: [...],
//   visibilityFilter: '...',
//   user: {...}
// }

这种组合方式使每个 reducer 只负责管理整个状态树中的特定部分,有助于保持代码模块化和可维护。

不可变数据的艺术

不可变性(Immutability)是 Redux 的基石之一,它要求我们在更新状态时不直接修改原对象,而是创建新对象。这一理念源自函数式编程,并在 Redux 中扮演着至关重要的角色。

为什么需要不可变性?

不可变性在 Redux 中的价值不仅是理论上的严谨,更在于实际应用中带来的诸多优势:

1. 可预测性

当状态对象不可变时,状态变化只能通过创建新对象来实现,这使得状态的变化变得可控和可预测。你可以确信只有通过明确的 action 才能改变状态,而不会有意外的修改发生。

2. 追踪变化

不可变数据结构使状态比较变得简单高效。比较两个对象是否发生变化,只需比较它们的引用是否相同,而不需要递归比较深层属性:

// 高效的状态变化检测
const hasChanged = prevState !== nextState

这种特性使 React 组件能够高效决定是否需要重新渲染,例如在 React.memo()shouldComponentUpdate() 中进行浅比较。

3. 时间旅行调试

不可变性使得保存状态历史变得简单:每次状态更新都会产生一个全新的状态对象,我们只需保存这些历史状态的引用。这直接支持了时间旅行调试功能,开发者可以在不同的状态快照间切换,重现应用的过去状态。

Redux DevTools 正是基于这一特性,提供了强大的调试能力:

  • 记录所有 action 和状态变化
  • 回放用户操作
  • 导出/导入状态快照
  • "热重启"保留状态
4. 易于测试

纯函数和不可变数据使 Redux 应用易于测试。Reducer 测试尤其简单,只需传入特定状态和 action,然后断言返回的新状态:

it('应该添加一个待办项', () => {const prevState = { todos: [] }const action = {type: 'ADD_TODO',payload: { id: 1, text: '测试 Redux', completed: false }}const nextState = todoReducer(prevState, action)expect(nextState.todos.length).toBe(1)expect(nextState.todos[0].text).toBe('测试 Redux')// 确保原状态不被修改expect(prevState.todos).toEqual([])
})

实现不可变性的方法

在 JavaScript 中实现不可变更新有多种方式,从手动实现到使用专用库,每种方法各有优劣:

1. 对象扩展运算符(…)

ES6 的扩展运算符是最常用的不可变更新方法:

// 对象浅复制并更新属性
const updatedUser = { ...user,          // 复制现有属性name: 'Redux',    // 更新特定属性score: user.score + 1
}// 注意:这只是浅复制,嵌套对象仍需单独处理
const updatedState = {...state,user: {...state.user,settings: {...state.user.settings,theme: 'dark'  // 更新深层嵌套属性}}
}

这种写法在嵌套对象较深时会变得冗长,但它不需要额外依赖,适合简单场景。

2. 数组方法

JavaScript 数组有许多返回新数组的方法,非常适合不可变更新:

// 添加元素
const newArray = [...array, newItem]  // 末尾添加
const newArray = [newItem, ...array]  // 开头添加
const newArray = [...array.slice(0, index),newItem,...array.slice(index)
]  // 中间插入// 删除元素
const newArray = array.filter(item => item.id !== idToRemove)// 替换元素
const newArray = array.map(item => item.id === targetId ? { ...item, updated: true } : item
)
3. Object.assign()

Object.assign() 是 ES6 特性,用于对象的浅合并:

const updatedState = Object.assign({}, state, { name: 'Redux' })// 嵌套对象更新
const updatedState = Object.assign({}, state, {user: Object.assign({}, state.user, {settings: Object.assign({}, state.user.settings, {theme: 'dark'})})
})

相比扩展运算符,这种方法更适合动态属性名:

const updatedState = Object.assign({}, state, {[dynamicKey]: newValue
})
4. Immer 库

对于复杂嵌套状态,手动管理不可变更新可能繁琐且容易出错。Immer 库提供了一种优雅解决方案,允许编写看似可变的代码,同时保持不可变更新:

import produce from 'immer'const nextState = produce(state, draft => {// 可以"直接修改" draftdraft.todos.push({ id: Date.now(),text: '学习 Immer', completed: false })// 嵌套更新也变得简单draft.user.settings.notifications.email = false// 可以使用所有可变方法const todo = draft.todos.find(t => t.id === 3)if (todo) {todo.completed = true}
})

Immer 内部使用 Proxy 实现,它追踪对草稿(draft)的所有修改,然后据此生成不可变的新状态。这大大简化了复杂状态的更新,特别是在 Redux Toolkit 中,Immer 已成为内置工具。

不可变性陷阱与注意事项

虽然不可变性有诸多优点,但实现时需注意几个常见陷阱:

  1. 浅复制的局限性:扩展运算符和 Object.assign() 只执行浅复制,嵌套对象仍是引用,需要手动处理。

  2. 引用类型复杂性:对象、数组等引用类型需要特殊处理,尤其在深层嵌套结构中。

  3. 性能考量:频繁创建新对象可能带来性能开销,特别是大型数据结构。在这种情况下,可考虑使用优化的不可变数据库如 Immutable.js 或性能优化的 Immer。

  4. 错误写法

// 错误:修改了原状态
function brokenReducer(state, action) {state.completed = true;  // 直接修改了参数return state;  // 返回被修改的同一对象
}// 正确:创建新状态
function correctReducer(state, action) {return {...state,completed: true};
}

纯函数的力量

纯函数是函数式编程的核心概念,也是 Redux 设计的重要基础。理解纯函数对于掌握 Redux 的工作原理至关重要。

什么是纯函数?

纯函数是指符合以下条件的函数:

  1. 确定性输出:相同输入总是返回相同输出,不受外部状态影响
  2. 无副作用:不修改外部状态,不执行 I/O 操作,不调用不纯的函数
  3. 不依赖外部状态:只依赖输入参数,不读取外部变量或全局状态

举例说明:

// 纯函数
function add(a, b) {return a + b;
}// 非纯函数 - 依赖外部状态
let multiplier = 2;
function multiply(a) {return a * multiplier;  // 依赖外部变量
}// 非纯函数 - 有副作用
function logAndAdd(a, b) {console.log(`Adding ${a} and ${b}`);  // 副作用:I/O 操作return a + b;
}// 非纯函数 - 修改输入
function addAndModify(arr, value) {arr.push(value);  // 修改了输入参数return arr;
}

纯函数的特性使得代码更易于理解、测试和维护,同时也是实现不变性的关键技术基础。

Reducer 作为纯函数

在 Redux 中,reducer 必须是纯函数,这是确保状态更新可预测性的核心要求。一个标准的 reducer 函数:

function counterReducer(state = 0, action) {switch (action.type) {case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;}
}

这个 reducer 是纯函数,因为:
相同的 state 和 action 总是产生相同的结果

  • 不依赖任何外部状态
  • 不修改输入参数
  • 无副作用,如网络请求、随机值生成等

常见的纯函数错误模式

在编写 reducer 时,开发者常犯以下错误,破坏了纯函数特性:

1. 直接修改状态
// 错误示例 - 修改原始状态
function impureReducer(state, action) {state.value += action.amount  // 直接修改了 statereturn state  // 返回同一对象引用
}// 正确的实现
function pureReducer(state, action) {return { ...state, value: state.value + action.amount }  // 创建新对象
}
2. 包含副作用
// 错误示例 - 包含副作用
function sideEffectReducer(state, action) {localStorage.setItem('data', JSON.stringify(state))  // 副作用:I/O 操作console.log('State updated:', state)  // 副作用:日志输出return { ...state, value: state.value + action.amount }
}// 正确实现:副作用应在 reducer 外处理,如通过中间件
function pureReducer(state, action) {return { ...state, value: state.value + action.amount }
}
3. 使用非确定性函数
// 错误示例 - 使用非确定性函数
function nonDeterministicReducer(state, action) {return {...state,id: Math.random(),  // 非确定性:每次调用结果不同timestamp: Date.now()  // 非确定性:依赖当前时间}
}// 正确实现:非确定性值应在 action 创建时生成
function createAddItemAction(text) {return {type: 'ADD_ITEM',payload: {id: Math.random(),  // 在 action 创建时生成timestamp: Date.now(),text}}
}function pureReducer(state, action) {switch (action.type) {case 'ADD_ITEM':return {...state,items: [...state.items, action.payload]}default:return state}
}

纯函数的优势

纯函数在 Redux 架构中带来诸多好处:

  1. 可预测性:无论何时调用,相同输入产生相同输出
  2. 易于测试:不需要模拟外部依赖,只需验证输入输出关系
  3. 自文档化:函数行为完全由参数决定,更易理解
  4. 可组合性:纯函数易于组合,创建更复杂的功能
  5. 并行安全:无副作用意味着可以安全地并行执行
  6. 易于缓存:结果可缓存,提高性能

纯函数与状态隔离

在 Redux 中,纯函数通过明确的输入输出边界将状态变化与副作用隔离:

  • 状态计算(纯函数):reducer 纯粹负责计算新状态
  • 副作用处理(非纯函数):通过中间件等机制在 reducer 之外处理

这种分离使得应用逻辑更清晰,状态变化更可控,也为处理复杂异步操作提供了基础。

单向数据流与副作用控制

Redux 的核心价值之一是实现了严格的单向数据流,这种模式使应用中的数据变化更加可预测和可追踪。

Redux 数据流详解

Redux 的数据在应用中遵循一个明确的单向循环流动路径:

View → Action → Reducer → Store → View

让我们详细解析这个循环:

  1. View 触发 Action:用户在界面上的交互(如点击按钮)触发 action 的分发

    // 在 React 组件中
    function TodoItem({ todo, dispatch }) {return (<li><inputtype="checkbox"checked={todo.completed}onChange={() => dispatch(toggleTodo(todo.id))}/>{todo.text}</li>);
    }
    
  2. Action 描述变化:被触发的 action 是一个普通对象,描述"发生了什么"

    // action 对象
    {type: 'TOGGLE_TODO',payload: { id: 42 }
    }
    
  3. Reducer 计算新状态:store 将当前状态和 action 传给 reducer,后者计算并返回新状态

    function todoReducer(state = [], action) {switch (action.type) {case 'TOGGLE_TODO':return state.map(todo =>todo.id === action.payload.id? { ...todo, completed: !todo.completed }: todo);default:return state;}
    }
    
  4. Store 更新状态:store 用 reducer 返回的新状态替换旧状态

    // 内部实现类似于
    currentState = reducer(currentState, action);
    
  5. View 响应变化:订阅 store 的组件检测到状态变化,重新渲染

    // 在 React-Redux 中
    function TodoList() {// 通过 useSelector 订阅 store 状态const todos = useSelector(state => state.todos);return (<ul>{todos.map(todo => (<TodoItem key={todo.id} todo={todo} />))}</ul>);
    }
    

这个循环确保了:

  • 所有状态变化有明确的来源(action)
  • 状态变化是可预测的(由 reducer 纯函数决定)
  • 变化是单向流动的,不会出现双向绑定带来的复杂性

处理副作用的策略

纯 Redux 只处理同步、无副作用的数据流。然而,实际应用中常需要处理网络请求、定时操作等副作用。Redux 生态提供了多种处理副作用的中间件方案:

1. Redux-Thunk

Redux-Thunk 是最简单的副作用处理方案,它允许 action 创建函数返回函数而非对象,这个返回的函数可以执行异步操作:

// 普通 action 创建函数
const increment = () => ({ type: 'INCREMENT' })// thunk action 创建函数
const fetchTodos = () => {// 返回一个函数,而非 action 对象return async (dispatch, getState) => {// 表明异步操作开始dispatch({ type: 'FETCH_TODOS_START' })try {// 执行异步操作const response = await fetch('/api/todos')const todos = await response.json()// 成功后分发包含数据的 actiondispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos })} catch (error) {// 失败时分发错误 actiondispatch({ type: 'FETCH_TODOS_FAILURE', error: error.message })}}
}// 在组件中使用
dispatch(fetchTodos())

Thunk 的优势在于简单易学,适合处理基本的异步流程;劣势是复杂异步操作(如竞态条件、请求取消)处理起来较繁琐。

2. Redux-Saga

Redux-Saga 使用 ES6 生成器(Generator)函数来控制异步流程,提供了强大的异步流程编排能力:

import { call, put, takeEvery } from 'redux-saga/effects'// 监听 'FETCH_TODOS_REQUEST' action 的 saga
function* watchFetchTodos() {yield takeEvery('FETCH_TODOS_REQUEST', fetchTodosSaga)
}// 处理获取 todos 的具体 saga
function* fetchTodosSaga(action) {try {// call 效应阻塞执行,直到 Promise 解决const response = yield call(fetch, '/api/todos')const todos = yield call([response, 'json'])// put 效应分发 actionyield put({ type: 'FETCH_TODOS_SUCCESS', payload: todos })} catch (error) {yield put({ type: 'FETCH_TODOS_FAILURE', error: error.message })}
}// 根 saga
function* rootSaga() {yield all([watchFetchTodos(),// 其他 saga])
}// 配置 saga 中间件
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer,applyMiddleware(sagaMiddleware)
)// 运行 saga
sagaMiddleware.run(rootSaga)

Saga 的优势在于声明式处理复杂异步流程,支持请求取消、竞态处理等高级功能;劣势是学习曲线较陡,需要理解生成器和 Saga 特有概念。

3. Redux-Observable

Redux-Observable 基于 RxJS,使用响应式编程模型处理副作用:

import { ofType } from 'redux-observable'
import { mergeMap, map, catchError } from 'rxjs/operators'
import { of, from } from 'rxjs'// Epic 是处理 action 流的函数
const fetchTodosEpic = action$ => action$.pipe(// 过滤出特定类型的 actionofType('FETCH_TODOS_REQUEST'),// 将 action 转换为 ObservablemergeMap(action => // 从 Promise 创建 Observablefrom(fetch('/api/todos').then(res => res.json())).pipe(// 成功时映射到一个新 actionmap(todos => ({ type: 'FETCH_TODOS_SUCCESS', payload: todos })),// 错误处理catchError(error => of({ type: 'FETCH_TODOS_FAILURE', error: error.message })))))// 配置 epic 中间件
const epicMiddleware = createEpicMiddleware()
const store = createStore(rootReducer,applyMiddleware(epicMiddleware)
)// 运行 epic
epicMiddleware.run(fetchTodosEpic)

Redux-Observable 的优势在于 RxJS 提供的强大操作符,便于处理复杂的事件流、时间相关操作和组合异步操作;劣势是需要学习 RxJS,这是一个相对复杂的库。

4. Redux Toolkit 的 createAsyncThunk

Redux Toolkit 提供了 createAsyncThunk 工具,简化了异步 action 的处理:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'// 创建异步 thunk
const fetchTodos = createAsyncThunk('todos/fetchTodos', // action 类型前缀async (arg, { rejectWithValue }) => {try {const response = await fetch('/api/todos')if (!response.ok) {throw new Error('Server error')}return await response.json()} catch (error) {return rejectWithValue(error.message)}}
)// 创建 slice,包含 reducer 和 action
const todosSlice = createSlice({name: 'todos',initialState: {items: [],loading: false,error: null},reducers: {// 同步 reducer},extraReducers: (builder) => {// 处理异步 actionbuilder.addCase(fetchTodos.pending, (state) => {state.loading = truestate.error = null}).addCase(fetchTodos.fulfilled, (state, action) => {state.loading = falsestate.items = action.payload}).addCase(fetchTodos.rejected, (state, action) => {state.loading = falsestate.error = action.payload})}
})

Redux Toolkit 的方案结合了 Thunk 的简单性和现代化的 API 设计,是目前推荐的处理副作用的方式。

副作用管理

无论选择哪种中间件,以下建议有助于更好地管理副作用:

  1. 保持 reducer 纯净:所有副作用应在 reducer 外处理
  2. 规范化错误处理:为异步操作定义清晰的成功/失败状态和错误传播机制
  3. 考虑请求状态:跟踪异步操作的加载、成功、失败状态
  4. 处理竞态条件:考虑多个请求并发情况下的状态更新策略
  5. 请求缓存与防抖:避免重复的网络请求,提升用户体验

实战案例:Todo 应用

为深入理解 Redux 的工作原理,我们将构建一个功能完整的 Todo 应用。这个应用虽然简单,但涵盖了 Redux 的核心概念和最佳实践。

定义 Action 类型

首先,我们定义清晰的 action 类型常量,集中管理所有可能的状态变化类型:

// actionTypes.js
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const SET_FILTER = 'SET_FILTER'// 过滤器常量
export const FILTERS = {SHOW_ALL: 'SHOW_ALL',SHOW_ACTIVE: 'SHOW_ACTIVE',SHOW_COMPLETED: 'SHOW_COMPLETED'
}

这种集中定义方式有助于避免拼写错误,并使整个应用中的状态变化类型一目了然。

Action 创建函数

接下来,我们创建 action 创建函数,封装 action 对象的生成逻辑:

// actions.js
import { ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER } from './actionTypes'// 添加待办事项
export const addTodo = text => ({type: ADD_TODO,payload: { id: Date.now(),  // 使用时间戳作为简单的唯一 IDtext,completed: false}
})// 切换待办事项完成状态
export const toggleTodo = id => ({type: TOGGLE_TODO,payload: { id }
})// 删除待办事项
export const deleteTodo = id => ({type: DELETE_TODO,payload: { id }
})// 设置过滤器
export const setFilter = filter => ({type: SET_FILTER,payload: { filter }
})

这些函数使 action 的创建更加类型安全,也便于在不同组件间复用。

Reducer

现在,我们需要编写处理这些 action 的 reducer 函数。为了保持代码的模块化,我们将创建两个分离的 reducer,然后组合它们:

// reducers.js
import { combineReducers } from 'redux'
import { ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER, FILTERS 
} from './actionTypes'// 处理 todos 列表的 reducer
function todos(state = [], action) {switch (action.type) {case ADD_TODO:return [...state,action.payload]case TOGGLE_TODO:return state.map(todo =>todo.id === action.payload.id? { ...todo, completed: !todo.completed }: todo)case DELETE_TODO:return state.filter(todo => todo.id !== action.payload.id)default:return state}
}// 处理过滤器状态的 reducer
function visibilityFilter(state = FILTERS.SHOW_ALL, action) {switch (action.type) {case SET_FILTER:return action.payload.filterdefault:return state}
}// 组合 reducer
const rootReducer = combineReducers({todos,visibilityFilter
})export default rootReducer

通过 combineReducers 函数,我们将两个独立的 reducer 合并成一个根 reducer,各自只负责状态树的特定部分,保持关注点分离。

Store 配置

接下来,我们创建 Redux store 并配置必要的中间件:

// store.js
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import rootReducer from './reducers'const store = createStore(rootReducer,composeWithDevTools(applyMiddleware(thunk)// 其他增强器可以在这里添加)
)export default store

我们使用了:

  • redux-devtools-extension 支持时间旅行调试
  • redux-thunk 中间件处理异步操作(虽然本例中暂未使用)

与 React 集成

最后,我们将 Redux 与 React 应用集成,创建相应的组件:

// index.js - 应用入口
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'render(<Provider store={store}><App /></Provider>,document.getElementById('root')
)
// App.js - 应用主组件
import React from 'react'
import AddTodo from './components/AddTodo'
import TodoList from './components/TodoList'
import FilterButtons from './components/FilterButtons'function App() {return (<div className="todo-app"><h1>Redux Todo App</h1><AddTodo /><TodoList /><FilterButtons /></div>)
}export default App
// components/AddTodo.js - 添加待办表单
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { addTodo } from '../actions'function AddTodo() {const [text, setText] = useState('')const dispatch = useDispatch()const handleSubmit = e => {e.preventDefault()if (!text.trim()) returndispatch(addTodo(text))setText('')}return (<form onSubmit={handleSubmit}><inputvalue={text}onChange={e => setText(e.target.value)}placeholder="添加待办事项..."/><button type="submit">添加</button></form>)
}export default AddTodo
// components/TodoList.js - 待办事项列表
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { toggleTodo, deleteTodo } from '../actions'
import { FILTERS } from '../actionTypes'// 根据过滤器过滤待办事项
const getVisibleTodos = (todos, filter) => {switch (filter) {case FILTERS.SHOW_COMPLETED:return todos.filter(todo => todo.completed)case FILTERS.SHOW_ACTIVE:return todos.filter(todo => !todo.completed)default:return todos}
}function TodoList() {const dispatch = useDispatch()// 从 store 获取状态const todos = useSelector(state => state.todos)const visibilityFilter = useSelector(state => state.visibilityFilter)// 应用过滤器const visibleTodos = getVisibleTodos(todos, visibilityFilter)return (<ul className="todo-list">{visibleTodos.map(todo => (<li key={todo.id}><spanstyle={{textDecoration: todo.completed ? 'line-through' : 'none',cursor: 'pointer'}}onClick={() => dispatch(toggleTodo(todo.id))}>{todo.text}</span><button className="delete-btn"onClick={() => dispatch(deleteTodo(todo.id))}>删除</button></li>))}</ul>)
}export default TodoList
// components/FilterButtons.js - 过滤按钮
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { setFilter } from '../actions'
import { FILTERS } from '../actionTypes'function FilterButtons() {const currentFilter = useSelector(state => state.visibilityFilter)const dispatch = useDispatch()// 过滤器配置const filterConfig = [{ type: FILTERS.SHOW_ALL, text: '全部' },{ type: FILTERS.SHOW_ACTIVE, text: '未完成' },{ type: FILTERS.SHOW_COMPLETED, text: '已完成' }]return (<div className="filters"><span>显示: </span>{filterConfig.map(({ type, text }) => (<buttonkey={type}className={currentFilter === type ? 'active' : ''}onClick={() => dispatch(setFilter(type))}>{text}</button>))}</div>)
}export default FilterButtons

这个例子展示了:

  • 如何定义 action、reducer 和 store
  • 如何使用 React-Redux 连接 React 组件与 Redux
  • 如何使用 useSelector 和 useDispatch hooks 获取状态和分发 action
  • 如何实现状态过滤等业务逻辑

Redux 实践指南

随着 Redux 的广泛应用,社区总结了许多技巧,帮助开发者充分发挥其优势,避免常见陷阱。

1. 规范化状态结构

当处理关系型数据时,建议将状态"规范化",类似于数据库表:

// 不规范化(嵌套)
const state = {users: [{id: 1,name: 'Alice',posts: [{ id: 101, title: 'Redux 介绍', content: '...' },{ id: 102, title: '不可变数据', content: '...' }]},// 更多用户...]
}// 规范化(扁平化)
const state = {users: {byId: {'1': { id: 1, name: 'Alice', postIds: [101, 102] }// 更多用户...},allIds: [1, 2, 3]  // 保持顺序},posts: {byId: {'101': { id: 101, title: 'Redux 介绍', content: '...', authorId: 1 },'102': { id: 102, title: '不可变数据', content: '...', authorId: 1 }// 更多文章...},allIds: [101, 102, 103]}
}

规范化结构的优势:

  • 避免数据冗余
  • 简化数据更新逻辑
  • 更容易对单个条目进行增删改
  • 简化关联数据的更新
  • 提高渲染性能(只有相关组件需要更新)

Redux Toolkit 的 createEntityAdapter 提供了规范化状态的工具:

import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'// 创建实体适配器
const todosAdapter = createEntityAdapter({// 可选:自定义如何获取 IDselectId: (todo) => todo.id,// 可选:自定义排序sortComparer: (a, b) => a.createdAt - b.createdAt
})// 创建初始状态
const initialState = todosAdapter.getInitialState({// 可添加额外的状态字段loading: false,error: null
})const todosSlice = createSlice({name: 'todos',initialState,reducers: {todoAdded: todosAdapter.addOne,todosReceived(state, action) {state.loading = falsetodosAdapter.setAll(state, action.payload)},// 其他 reducer...}
})

2. 使用 Selector 函数访问状态

将状态访问逻辑封装在 selector 函数中,而不是直接在组件内访问状态,这提供了几个重要优势:

// selectors.js
export const getTodos = state => state.todos
export const getVisibilityFilter = state => state.visibilityFilterexport const getVisibleTodos = state => {const todos = getTodos(state)const filter = getVisibilityFilter(state)switch (filter) {case 'SHOW_COMPLETED':return todos.filter(todo => todo.completed)case 'SHOW_ACTIVE':return todos.filter(todo => !todo.completed)default:return todos}
}// 与 reselect 结合使用,实现记忆化选择器
import { createSelector } from 'reselect'export const getVisibleTodosOptimized = createSelector([getTodos, getVisibilityFilter],(todos, filter) => {switch (filter) {case 'SHOW_COMPLETED':return todos.filter(todo => todo.completed)case 'SHOW_ACTIVE':return todos.filter(todo => !todo.completed)default:return todos}}
)

Selector 的优势:

  • 封装状态结构:组件不需要了解状态树的具体结构
  • 逻辑复用:同一选择逻辑可在多个组件中复用
  • 性能优化:通过记忆化(memoization)避免不必要的重计算
  • 易于测试:选择逻辑可以单独测试
  • 适应状态变更:可以在不影响组件的情况下重构状态结构

在组件中使用 selector:

import React from 'react'
import { useSelector } from 'react-redux'
import { getVisibleTodos } from '../selectors'function TodoList() {// 使用选择器获取状态,而非直接访问const visibleTodos = useSelector(getVisibleTodos)return (<ul>{visibleTodos.map(todo => (<li key={todo.id}>{todo.text}</li>))}</ul>)
}

3. 使用 Redux Toolkit 简化开发

Redux Toolkit 是官方推荐的 Redux 开发工具集,它简化了 Redux 应用的开发流程,减少了样板代码:

import { createSlice, configureStore } from '@reduxjs/toolkit'// 使用 createSlice 一次性创建 reducer 和 action
const todosSlice = createSlice({name: 'todos',initialState: [],reducers: {// 直接在 reducer 中"修改"状态(实际上是不可变更新)addTodo: (state, action) => {state.push({id: Date.now(),text: action.payload,completed: false})},toggleTodo: (state, action) => {const todo = state.find(todo => todo.id === action.payload)if (todo) {todo.completed = !todo.completed}},deleteTodo: (state, action) => {return state.filter(todo => todo.id !== action.payload)}}
})// 自动生成的 action 创建函数
export const { addTodo, toggleTodo, deleteTodo } = todosSlice.actions// 配置 store,自动应用中间件和开发工具
const store = configureStore({reducer: {todos: todosSlice.reducer,// 其他 reducer...}
})export default store

Redux Toolkit 的主要优势:

  • 减少样板代码:自动生成 action 类型和创建函数
  • 内置不可变更新:通过 Immer 库支持直观的状态更新语法
  • 简化异步逻辑:通过 createAsyncThunk 简化异步操作
  • 内置最佳实践:默认包含 Redux DevTools、thunk 中间件等
  • 类型安全:良好的 TypeScript 支持

4. 小型应用考虑 useReducer + Context API

对于简单应用或组件局部状态管理,完整的 Redux 可能过于重量级。这种情况下,React 的 useReducer 和 Context API 是轻量级替代方案:

// TodoContext.js
import React, { createContext, useReducer, useContext } from 'react'// 初始状态
const initialState = { todos: [] }// Reducer 函数(与 Redux reducer 类似)
function todoReducer(state, action) {switch (action.type) {case 'ADD_TODO':return {...state,todos: [...state.todos, {id: Date.now(),text: action.payload,completed: false}]}case 'TOGGLE_TODO':return {...state,todos: state.todos.map(todo =>todo.id === action.payload? { ...todo, completed: !todo.completed }: todo)}default:return state}
}// 创建 Context
const TodoContext = createContext()// Context Provider 组件
export function TodoProvider({ children }) {const [state, dispatch] = useReducer(todoReducer, initialState)return (<TodoContext.Provider value={{ state, dispatch }}>{children}</TodoContext.Provider>)
}// 自定义 Hook 简化 Context 使用
export function useTodoContext() {const context = useContext(TodoContext)if (!context) {throw new Error('useTodoContext 必须在 TodoProvider 内使用')}return context
}

在组件中使用:

// App.js - 根组件
import React from 'react'
import { TodoProvider } from './TodoContext'
import TodoList from './TodoList'
import AddTodo from './AddTodo'function App() {return (<TodoProvider><h1>Todo App</h1><AddTodo /><TodoList /></TodoProvider>)
}// AddTodo.js - 添加待办组件
import React, { useState } from 'react'
import { useTodoContext } from './TodoContext'function AddTodo() {const [text, setText] = useState('')const { dispatch } = useTodoContext()const handleSubmit = e => {e.preventDefault()if (!text.trim()) returndispatch({ type: 'ADD_TODO', payload: text })setText('')}return (<form onSubmit={handleSubmit}><inputvalue={text}onChange={e => setText(e.target.value)}placeholder="添加待办事项..."/><button type="submit">添加</button></form>)
}// TodoList.js - 待办列表组件
import React from 'react'
import { useTodoContext } from './TodoContext'function TodoList() {const { state, dispatch } = useTodoContext()return (<ul>{state.todos.map(todo => (<likey={todo.id}style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>{todo.text}</li>))}</ul>)
}

这种方法适用于:

  • 中小型应用
  • 组件树中局部的状态管理需求
  • 状态逻辑相对简单的场景
  • 原型开发阶段

当应用规模增长或状态管理需求变得复杂时,可以平滑迁移到完整的 Redux 方案。

最后的话

Redux 提供的不可变数据与纯函数模型已经超越了库本身,成为现代前端状态管理的基础范式。我们一起思考了:

核心价值

  • 可预测性:单向数据流和纯函数模型使状态变化可预测
  • 可调试性:时间旅行调试和明确的状态变更历史简化了问题定位
  • 可维护性:关注点分离和模块化设计提升了代码可维护性
  • 可测试性:纯函数和明确的数据流使测试变得简单

关键技术

  • 不可变更新模式:通过创建新对象而非修改原对象实现状态更新
  • 纯函数的实践:无副作用、确定性输出的函数设计
  • 副作用的隔离:使用中间件处理异步操作和副作用
  • 状态规范化:类数据库的扁平化状态结构设计

演进趋势

Redux 生态系统不断发展,从早期相对繁琐的写法到现代简洁的工具链:

  1. 原始 Redux:手动编写 action 类型、action 创建函数和 reducer
  2. 中间件扩展:Redux-Thunk、Redux-Saga 解决异步问题
  3. 辅助工具:Reselect、Normalizr 优化选择器和状态结构
  4. Redux Toolkit:官方工具集,大幅简化 Redux 开发
  5. React-Redux HooksuseSelectoruseDispatch 提供更简洁的组件集成

这种演进反映了前端状态管理的总体趋势:在保持核心原则的同时,追求更简洁直观的开发体验。

小建议

  1. 从需求出发:小应用可能不需要 Redux,可以先考虑 useReducer + Context
  2. 工具选择:新项目优先使用 Redux Toolkit,享受现代化简化体验
  3. 深入理解:掌握不可变性和纯函数的核心概念,而非仅停留在 API 层面
  4. 与时俱进:关注 Redux 生态的最新实践,但避免频繁重构
  5. 平衡取舍:记住 Redux 是工具而非目的,选择最适合项目的状态管理方案

Redux 的核心价值在于它提供的状态管理思维模式,即使在未来前端框架发生变革的情况下,其不可变数据和纯函数的理念仍将是构建可维护前端应用的重要基石。

参考资源

官方文档

  • Redux 官方文档 - 最权威的 Redux 学习资源,包含完整的概念解释、API 文档和最佳实践
  • Redux Toolkit 文档 - Redux 官方推荐的工具集,简化 Redux 开发流程
  • React-Redux 文档 - 官方 React 绑定库文档,详细介绍了 hooks API 和连接组件

进阶学习资源

  • Redux 风格指南 - Redux 核心团队推荐的编写规范和最佳实践
  • Redux 常见问题 - 解答关于 Redux 的常见疑问和误解
  • Immutable Update Patterns - 详细的不可变更新模式指南

工具和库

  • Immer - 简化不可变数据更新的库,Redux Toolkit 内置使用
  • Reselect - 用于创建记忆化选择器的库,避免不必要的重计算
  • Redux DevTools Extension - Redux 开发者工具,支持时间旅行调试
  • Redux-Saga - 基于 Generator 的副作用管理中间件
  • Redux-Observable - 基于 RxJS 的 Redux 中间件

社区资源

  • Redux GitHub 仓库 - 源码、问题讨论和最新进展
  • React & Redux 中文社区 - 中文开发者交流社区
  • Stack Overflow Redux 标签 - 问题解答和实践经验分享

深入函数式编程

  • JavaScript 函数式编程指南 - 深入浅出的函数式编程教程
  • Professor Frisby’s Mostly Adequate Guide to Functional Programming - 面向 JavaScript 开发者的函数式编程指南
  • 函数式编程术语 - 函数式编程概念解释

示例项目

  • Redux Real World Example - 展示实际应用中如何使用 Redux
  • Redux Todos Example - 经典的 Todo 应用示例
  • Redux Shopping Cart Example - 官方教程配套的购物车应用示例

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

Redux:不可变数据与纯函数的艺术

Redux&#xff1a;不可变数据与纯函数的艺术 状态管理的困境 随着现代 Web 应用功能的不断扩展&#xff0c;前端开发者面临着日益复杂的状态管理挑战。当应用从简单的表单交互发展到复杂的单页应用时&#xff0c;组件间共享状态的问题变得尤为突出。想象一个电商平台&#xf…...

算法篇 八大排序(冒泡 插入 选择 堆 希尔 快排 归并 计数)

目录 引言 1.冒泡排序 思路 代码实现 2.选择排序 思路 代码实现&#xff08;存在易错点&#xff09; 3.插入排序 思路 代码实现 4.希尔排序 思路 代码实现 5.堆排序 思路 代码实现 6.快速排序&#xff08;快排&#xff09; 一.三路划分 思路 代码实现 二.自…...

技术文档写作全攻略

一、引言 在快速迭代的软件开发中&#xff0c;技术文档早已不只是附属品&#xff0c;而是与代码同等重要的交付物&#xff1a; 帮助新成员 T0 → T1 学习曲线指数下降&#xff1b;降低支持成本&#xff0c;将重复性问答前移到自助文档&#xff1b;为合规审计、知识传承及商业…...

网络安全全景解析

引言 在数字化时代&#xff0c;网络已深度融入社会生产生活的各个领域&#xff0c;成为推动经济发展和社会进步的关键力量。然而&#xff0c;随着网络应用的日益复杂&#xff0c;网络安全问题也呈现出多样化、复杂化的趋势。从个人隐私泄露到企业核心数据被盗&#xff0c;从基础…...

音视频之视频压缩编码的基本原理

系列文章&#xff1a; 1、音视频之视频压缩技术及数字视频综述 2、音视频之视频压缩编码的基本原理 一、预测编码&#xff1a; 1、预测编码的基本概念&#xff1a; 预测法是最简单、实用的视频压缩编码方法&#xff0c;经过压缩编码后传输的并不是像素本身的取样值&#xff0…...

IDEA 包分层显示设置

方法一&#xff08;用的IntelliJ IDEA 2024.1.4版本&#xff09;&#xff1a; 找到项目视图设置入口&#xff1a;在左侧Project&#xff08;项目&#xff09;面板的顶部&#xff0c;有个三个点...的按钮 &#xff0c;点击它。 进入树形外观配置&#xff1a;在弹出的菜单中&…...

书籍将正方形矩阵顺时针转动90°(8)0605

题目 给定一个N x N的矩阵matrix,把这个矩阵调整成顺时针转动90后的形式。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 顺时针转动90后为&#xff1a; 13 9 5 1 14 …...

【docker】容器技术如何改变软件开发与部署格局

在当今数字化时代&#xff0c;软件开发与部署的效率和灵活性至关重要。就像古人云&#xff1a;“工欲善其事&#xff0c;必先利其器。”Docker 作为一款强大的容器技术&#xff0c;正如同软件开发领域的一把利器&#xff0c;极大地改变了应用的开发、交付和运行方式。本文将深入…...

C#抽象类深度解析 _ 核心特性与实战指南

—— 面向对象设计的基石 &#x1f50d;抽象类核心定义 abstract class AbClass { ... } // abstract修饰符声明 不可实例化&#xff1a;new AbClass() 将触发编译错误继承专用&#xff1a;仅能作为其他类的基类存在混合成员组合&#xff1a;可同时包含抽象方法和已实现方法…...

时序数据库IoTDB的UDF Sample算法在数据监控、故障预防的应用

一、数据监控在工业物联网中的重要性 设备数据监控是工业物联网&#xff08;IoT&#xff09;中最为广泛应用的领域之一。通过实时监控工厂机械设备的运行状态&#xff0c;企业能够提前发现设备的潜在故障&#xff0c;从而实现预防性维护与可预测性维护。这一做法不仅能有效提升…...

Flask-SQLAlchemy使用小结

链表查询 join方法允许你指定两个或多个表之间的连接条件&#xff0c;并返回一个新的查询对象&#xff0c;该对象包含了连接后的结果。 内连接 from sqlalchemy import join # 使用join函数 query db.session.query(User, Order).join(Order, User.id Order.user_id) res…...

深度学习和神经网络 卷积神经网络CNN

1.什么是卷积神经网络 一种前馈神经网络&#xff1b;受生物学感受野的机制提出专门处理网格结构数据的深度学习模型 核心特点&#xff1a;通过卷积操作自动提取空间局部特征&#xff08;如纹理、边缘&#xff09;&#xff0c;显著降低参数量 2.CNN的三个结构特征 局部连接&a…...

用 NGINX 构建高效 POP3 代理`ngx_mail_pop3_module`

一、模块定位与作用 协议代理 ngx_mail_pop3_module 让 NGINX 能够充当 POP3 代理&#xff1a;客户端与后端 POP3 服务器之间的所有请求均转发到 NGINX&#xff0c;由 NGINX 负责与后端会话逻辑。认证方式控制 通过 pop3_auth 指令指定允许客户端使用的 POP3 认证方法&#xf…...

解决:如何在Windows adb使用dmesg | grep检查内核日志

首先&#xff1a; C:\Users\TF> adb shell 再 rk3568_r:/ $ dmesg | grep -i “goodix” 显示 130|rk3568_r:/ $ dmesg | grep -i “goodix” [ 0.764071] goodix_ts_probe() start111 [ 0.764108] goodix_ts_probe() start222 [ 0.764181] Goodix-TS 1-0014: Linked as a c…...

PlayWright | 初识微软出品的 WEB 应用自动化测试框架

Playwright是微软大厂背书的跨平台 WEB 应用自动化测试框架&#xff0c;支持多开发语言&#xff08;TypeScript、JavaScript、.Net、Python、Java&#xff09;及多浏览器&#xff08;Chromium、WebKit、Firefox&#xff09;&#xff0c;同时支持移动端测试。 安装 playwright …...

Mac电脑_钥匙串操作选项变灰的情况下如何删除?

Mac电脑_钥匙串操作选项变灰的情况下如何删除&#xff1f; 这时候 可以使用相关的终端命令进行操作。 下面附加文章《Mac电脑_钥匙串操作的终端命令》。 《Mac电脑_钥匙串操作的终端命令》 &#xff08;来源&#xff1a;百度~百度AI 发布时间&#xff1a;2025-06&#xff09;…...

Git Patch 使用详解:生成、应用与多提交合并导出

在多人协作、代码审查、离线提交或跨仓库迁移的场景中&#xff0c;git patch 是非常实用的技术。本文将系统地介绍如何使用 Git 的补丁机制导出和应用修改内容。 &#x1f4d6; 什么是 Git Patch&#xff1f; 严格来说&#xff0c;git patch 并不是一个 Git 命令&#xff0c;而…...

2025前端微服务 - 无界 的实战应用

遇饮酒时须饮酒&#xff0c;得高歌处且高歌 文章目录 什么是前端微服务主流框架概述无界 - 腾讯乾坤 - 阿里Micro-app Vue3项目引用⑴. 项目依赖安装⑵. main.ts 文件配置⑶. 路由配置⑷. 页面设置 隐藏子应用菜单及顶部信息栏子应用样式冲突问题虚拟路由⑴. 路由⑵. 页面 跨域…...

Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)

&#x1f4a1; 前言 在高并发、高性能的系统开发中&#xff0c;缓存是提升接口响应速度和降低数据库压力的重要手段。Spring Boot 提供了强大的缓存抽象层 —— spring-context-support&#xff0c;并结合 JSR-107 标准&#xff0c;提供了多个缓存注解&#xff0c;如&#xff…...

【设计模式-4.8】行为型——中介者模式

说明&#xff1a;本文介绍行为型设计模式之一的中介者模式 定义 中介者模式&#xff08;Mediator Pattern&#xff09;又叫作调节者模式或调停者模式。用一个中介对象封装一系列对象交互&#xff0c;中介者使各对象不需要显式地互相作用&#xff0c;从而使其耦合松散&#xf…...

SpringCloud-基于SpringAMQP实现消息队列

在微服务架构中&#xff0c;使用消息队列进行异步通信是一种常见而有效的方法。Spring Cloud提供了一个强大的工具集&#xff0c;用于构建分布式系统&#xff0c;而Spring AMQP是其支持高级消息队列协议(AMQP)的组件&#xff0c;广泛应用于消息队列的场景中&#xff0c;尤其是与…...

ObjectMapper 在 Spring 统一响应处理中的作用详解

ObjectMapper 是 Jackson 库的核心类&#xff0c;专门用于处理 JSON 数据的序列化&#xff08;Java 对象 → JSON&#xff09;和反序列化&#xff08;JSON → Java 对象&#xff09;。在你提供的代码中&#xff0c;它解决了字符串响应特殊处理的关键问题。 一、为什么需要 Obj…...

H5移动端性能优化策略(渲染优化+弱网优化+WebView优化)

一、渲染优化&#xff1a;首屏速度提升的核心​​ ​​1. 关键页面采用SSR或Native渲染​​ ​​适用场景​​&#xff1a;首页、列表页、详情页等强内容展示页面 ​​优化原理​​&#xff1a; ​​SSR&#xff08;服务端渲染&#xff09;​​&#xff1a;在服务端生成完整…...

【汇编逆向系列】二、函数调用包含单个参数之整型-ECX寄存器,LEA指令

目录 一. 汇编源码 二. 汇编分析 1. ECX寄存器 2. 栈位置计算​ 3. 特殊指令深度解析 三、 汇编转化 一. 汇编源码 single_int_param:0000000000000040: 89 4C 24 08 mov dword ptr [rsp8],ecx0000000000000044: 57 push rdi0000…...

行列式的性质

1 行列式使用如下性质定义 1&#xff09;单位矩阵行列式值为 1&#xff0c; &#xff0c;对于任意单位矩阵均成立&#xff1b; 2&#xff09;当矩阵交换一行后&#xff0c;行列式值改变符号&#xff0c;如置换矩阵的行列式值为 &#xff08;根据行交换次数决定&#xff09;&…...

联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率

在金融行业核心基础设施国产化浪潮与网络安全强监管的双重背景下&#xff0c;上海证券交易所&#xff08;以下简称“上交所”&#xff09;积极拥抱变革&#xff0c;携手长期合作伙伴联软科技&#xff0c;成功部署了联软安全策略管理系统&#xff08;NSPM&#xff09;。该项目不…...

Flask + ECharts+MYSQL物联网数字化大屏

基于Flask+ECharts的物联网数字化大屏系统,包含中国地图实时数据更新功能。这个系统模拟了物联网设备在全国范围内的分布和运行状况,并实时更新数据。 一、系统架构设计 技术栈 后端:Flask(轻量级路由+API支持) 前端:ECharts(地图+动态图表)、WebSocket(实时更新)…...

业务到解决方案构想

解决方案构想的核心理解 解决方案构想是连接业务需求与技术实现的关键桥梁&#xff0c;从您描述的内容和我的理解&#xff0c;这个阶段的核心点包括&#xff1a; 核心要点解读 转化视角&#xff1a;将业务视角的需求转变为解决方案视角 业务能力探索阶段识别了"做什么&q…...

数据库系统概论(十六)数据库安全性(安全标准,控制,视图机制,审计与数据加密)

数据库系统概论&#xff08;十六&#xff09;数据库安全性 前言一、数据库安全性1. 什么是数据库安全性&#xff1f;2. 为何会存在安全问题&#xff1f; 二、安全标准的发展1. 早期的“开拓者”&#xff1a;TCSEC标准2. 走向国际统一&#xff1a;CC标准3. TCSEC和CC标准有什么不…...

vue3从入门到精通(基础+进阶+案例)

Vue是什么&#xff1f; 渐进式JavaScript框架&#xff0c;易学易用&#xff0c;性能出色&#xff0c;适用场景丰富的Web前端框架 为什么要学习Vue Vue是目前前端最火的框架之一 Vue是目前企业技术栈中要求的知识点 Vue可以提升开发体验 。。。 Vue简介 Vue(发音为/vju/,…...