环境安装
npm i redux react-redux @types/redux-thunk @types/redux-logger
依次安装 redux、集成react的redux、因redux中的禁止使用异步和打印,需要安装插件支持
redux 项目目录
- src - actions // 存放定义 action 的文件 - actionTypes.js // 存放 action 类型常量的文件 - userActions.js // 存放用户相关的 action 创建函数的文件 - cartActions.js // 存放购物车相关的 action 创建函数的文件 - ... - reducers // 存放定义 reducer 的文件 - index.js // 根 reducer,使用 combineReducers 合并多个 reducer - userReducer.js // 用户相关的 reducer - cartReducer.js // 购物车相关的 reducer - ... - store // 存放 Redux store 相关的文件 - index.js // 创建 Redux store 的文件 - components // 存放 React 组件的文件夹 - UserComponent.js // 用户相关的组件 - CartComponent.js // 购物车相关的组件 - ... - containers // 存放包装组件(连接 Redux)的容器组件 - UserContainer.js // 用户相关的容器组件 - CartContainer.js // 购物车相关的容器组件 - ... - App.js // 主应用组件 - index.js // 应用入口文件
reducer函数
在 Redux 中,reducer 函数是用来处理状态(state)的函数。它接收两个参数:当前的状态(state)和被分发的 action,然后根据 action 的类型来更新状态并返回新的状态对象。
reducer 编写规则
- 只根据 state 和 action 参数计算新的状态值
- 不允许修改现有的state值,必须通过复制现有的值
- 不能做任何异步的操作逻辑、以及副作用
TIP“ 副作用 ”
副作用是在从函数返回值之外可以看到的状态或行为的任何变化。一些常见的副作用是:
- 将值记录到控制台
- 保存文件
- 设置异步计时器
- 发出 AJAX HTTP 请求
- 修改存在于函数之外的某些状态,或改变函数的参数
- 生成随机数或唯一随机 ID(例如 Math.random() 或 Date.now())
reducer 永远不允许改变原始/当前状态值!
// ❌ 非法 - 默认情况下,这会改变状态! state.value = 123
let initialState = { userName:"赵四" } function reducer(state = initialState, action) { switch (action.type) { case "SOME_ACTION_TYPE": // 在这里处理 action,并返回新的状态对象 return newState; case "ANOTHER_ACTION_TYPE": // 处理另一个 action return newState; default: // 默认情况下,返回当前状态,不做任何改变 return state; } }
一个 Redux 应用中可以有多个 reducer 函数。每个 reducer 函数负责管理和更新应用中的一部分状态。Redux 通过 combineReducers
函数来合并多个 reducer 函数,创建一个根 reducer,然后将根 reducer 传递给 createStore
方法。根 reducer 会根据 action 的类型将对应的子状态分发给不同的 reducer 进行处理。
设计 actions
Actions 是具有 type 字段的普通 JavaScript 对象,来描述操作行为。
例如,在一个电商系统中,当用户点击购买按钮时,我们可以创建一个名为 "PURCHASE" 的 action 来描述这个操作。
一个action对象通常包含一个 type 字段来描述action的类型,以及可选的 payload 字段来携带额外的数据,type 字段是一个字符串,用于识别action的类型,而 payload 字段则可以是任何类型的数据,包括对象、数组、字符串等,用于携带一些与该操作相关的数据。
下面是一个示例的action对象:
{ type: 'PURCHASE', payload: { id: 1, text: 'Learn Redux', completed: false } }
可借助 dispatch
派发redux中的操作,来修改store数据。如下,定义一个派发dispath的函数,通常是返回 actions对象
export const get_table = ( ) => { return async (dispatch: Dispatch) => { let { data } = await instance.get('/api/table'); console.log('触发-get_Table接口了') return dispatch({ type: 'get_table', payload: data }); }; };
注意!包含actions对象的函数,不可是异步函数。但可以借助 thunk中间件的能力,在action函数内部执行异步操作。
如下,需 根reducer函数中 开启中间件applyMiddleware
,使用 异步插件 thunk
import { combineReducers, applyMiddleware, legacy_createStore as createStore } from 'redux'; import logger from 'redux-logger'; // 打印日志插件 import thunk from 'redux-thunk';// 执行异步操作插件 import table from './module/table/index'; // 子仓库 import user from './module/user/index' // 子仓库 export default createStore( combineReducers({ table, user }), // 合并仓库 applyMiddleware(thunk, logger) // applyMiddleware 使用中间件 );
使用 redux仓库
Provider
组件的作用就是将 Redux 的 store 注入到 React 应用中,并使应用的所有组件都能够访问 Redux 的状态。
使用 Provider 组件的方式如下:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import store from './store'; // 导入 Redux 的 store ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
使用 provider 优势是简化了redux的集成,不需要在组件内部手动引入状态。
在React组件内部获取Redux的store有几种常见的方式:
- 使用
react-redux
库中的useSelector
Hook:
import { useSelector } from 'react-redux'; const MyComponent = () => { const counter = useSelector((state) => state.counter); // 获取counter状态 // 在组件中使用 counter 值 return ( // JSX ); };
- 使用
react-redux
库中的connect
函数:
import { connect } from 'react-redux'; const MyComponent = ({ counter }) => { // 在组件中使用 counter 值 return ( // JSX ); }; const mapStateToProps = (state) => ({ counter: state.counter, // 将 counter 状态映射为组件的 props }); export default connect(mapStateToProps)(MyComponent);
- 在函数组件外部使用
useStore
Hook:
import { useStore } from 'react-redux'; const MyComponent = () => { const store = useStore(); const counter = store.getState().counter; // 获取 counter 状态 // 在组件中使用 counter 值 return ( // JSX ); };
第一种和第二种方式是使用react-redux
提供的库函数来连接组件和store,提供了更方便的API。
第三种方式是直接使用Redux提供的HookuseStore
,更为底层,可以在函数组件外部使用,适用于一些特殊情况。
拆分 reducers -store
如何将一个复杂的业务仓库,按功能模块拆分为多个小仓库方便管理维护 ?
例如,一个应用可能有多个状态需要管理,比如用户信息、购物车、主题等等。可以为每个状态编写一个单独的 reducer 函数,并使用 combineReducers
将它们合并成一个根 reducer。
使用 combineReducers
将子仓库合并到跟reducer中
import { combineReducers, createStore } from "redux"; import userReducer from "./userReducer"; // 用户信息的 reducer import cartReducer from "./cartReducer"; // 购物车的 reducer import themeReducer from "./themeReducer"; // 主题的 reducer const rootReducer = combineReducers({ user: userReducer, cart: cartReducer, theme: themeReducer, }); const store = createStore(rootReducer); export default store;
在上面的示例中,combineReducers
函数将 userReducer
、cartReducer
和 themeReducer
合并成一个根 reducer。每个 reducer 函数都负责管理对应的状态片段,并根据相应的 action 类型来更新状态。通过这种方式,一个 Redux 应用可以同时管理多个相关联的状态。
react-redux
React Redux 是 Redux 官方提供的一个库,专门用于在 React 应用中集成和操作 Redux 的状态
组件划分
react-redux把组件划分两类,如下:
一、ui组件
UI 组件有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
下面就是一个 UI 组件的例子。
const Title = value => <h1>{value}</h1>;
因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。
二、容器组件
容器组件的特征恰恰相反。
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它 - 阮一峰
connect
函数
connect
是react-redux提供的方法,作用将 UI组件转为 容器组件。connect
接收两个参数 ,分别是mapStateProps
和 mapDispatch
- 参数1
mapStateProps
负责输入逻辑将state
仓库内容、映射到 UI组件的参数props
- 参数2
mapDispatch
负责输出逻辑,将用户的操作映射成action
参数 mapStateProps
1、mapStateProps
是一个函数。建立外部映射关系,将外部store
和组件中的props
进行关联。
mapStateProps
函数返回一个对象,数据结构中的键值对,就是一个映射关系,如:
const mapStateToProps = (state) => { return { todos:state.user // 仓库中的用户数据 } }
上面代码中 mapStateProps
函数接收 state 为参数,返回对象中的 todos属性 、代表UI组件的同名参数。在组件内部,我们通过映射关系的 props
,可以获取到 state 中的数据。
mapStateProps
会订阅Store ,每当 store更新时,会重新计算UI组件参数,重新渲染组件。
如不想更新UI组件,可以省略 connect 方法中的mapStateProps
参数
参数 mapDispatch
mapDispatch
是connect
的第二个参数,用于建立UI组件参数和store.dispatch
方法的映射。mapDispatch
可以是一个对象,也可以是一个函数。
mapDispatch
作为函数,内置两个参数 ,分别是dispatch
和 onwProps
(容器组件中的props)
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
上述代码中 apDispatch
作为函数 ,返回一个对象,对象中的键值对定义了如何发出 Action。
在组件内部,直接访问onclick 方法,即可触发reducer内操作(更新、修改数据等)
mapDispatch
作为对象,它的每个键名对应的UI组件的同名参数,值应该是一个函数。如下:
const mapDispatch:any = { // 属性get-table ,为组件的同名参数 get_table:(flter:any) => ({ type:"get_table", // type 字段为actions 类型 flter:flter // filter 为提交参数 }) }
mapDispatch
高阶用法
bindActionCreators
是 Redux 提供的一个辅助函数,来简化 dispatch
派发动作过程,避免手动编写派发动作的代码。
下面是使用 bindActionCreators
的示例和代码讲解:
import { bindActionCreators } from 'redux'; import { addTodo, completeTodo } from './actions'; // 创建动作创建函数的对象 const actionCreators = { addTodo, completeTodo }; // 获取 Redux store // 假设你已经创建了 store 并引入了所需的动作创建函数 // 将动作创建函数与派发函数绑定 const dispatch = store.dispatch; const boundActionCreators = bindActionCreators(actionCreators, dispatch); // 在组件中使用绑定后的动作创建函数 // 这些函数会自动派发对应的动作到 Redux store // 示例 1:组件中调用绑定后的动作创建函数 boundActionCreators.addTodo('Buy groceries'); // 示例 2:将绑定后的动作创建函数传递给组件的 props // 在组件内部可以直接调用这些函数来派发动作 <MyComponent addTodo={boundActionCreators.addTodo} completeTodo={boundActionCreators.completeTodo} />
在示例代码中,首先创建了一个包含了多个动作创建函数的 actionCreators
对象。然后使用 bindActionCreators
将 actionCreators
中的所有动作创建函数与 Redux store 的派发函数 dispatch
绑定,生成了一个新的对象 boundActionCreators
。
通过调用 boundActionCreators
的函数,可以在组件中自动派发对应的动作到 Redux store,而无需手动编写派发动作的代码。
hooks函数
react-redux库提供了多个钩子(hooks)函数,用于react组件访问redux的状态和操作。下面是常用的hooks函数以及用法
useSelector
useSelector
:用于选择 Redux store 中感兴趣的状态。它接受一个选择器函数作为参数,并返回选择器函数返回的值。
使用该钩子可以避免在组件中订阅整个状态树,提供了更好的性能。
示例用法:
import { useSelector } from 'react-redux'; const MyComponent = () => { const counter = useSelector(state => state.counter); // 在这里使用 counter return ( // 组件的 JSX ); };
useDispatch
useDispatch
:用于获取 Redux store 中的 dispatch 函数。dispatch
用于派发操作(dispatch actions)改变 Redux 中的状态。
示例用法:
import { useDispatch } from 'react-redux'; const MyComponent = () => { const dispatch = useDispatch(); // 在这里使用 dispatch const handleClick = () => { dispatch({ type: 'INCREMENT' }); }; return ( <button onClick={handleClick}>Increment</button> ); };
useStore
useStore
:用于获取 Redux store 对象。通过这个钩子可以直接访问 Redux store 的内部方法和数据。
示例用法:
import { useStore } from 'react-redux'; const MyComponent = () => { const store = useStore(); const state = store.getState(); // 在这里使用 store 和 state return ( // 组件的 JSX ); };
useActions
useActions
:用于绑定动作创建函数(action creators),以便在组件中使用。它接受一个包含动作创建函数的对象作为参数,并返回已绑定到Redux store的动作创建函数。
示例用法:
import { useActions } from 'react-redux'; import { increment, decrement } from './actions'; const MyComponent = () => { const { incrementAction, decrementAction } = useActions({ increment, decrement }); // 在这里使用 incrementAction 和 decrementAction return ( // 组件的 JSX ); };
搭配react hooks
useEffect
useEffect
:React自带的钩子函数,用于在组件渲染完成后执行副作用操作。在React Redux中,如果你想在组件挂载后执行异步操作或订阅状态变化,可以使用该钩子函数。
示例用法:
import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchData } from './actions'; const MyComponent = () => { const dispatch = useDispatch(); const data = useSelector(state => state.data); useEffect(() => { dispatch(fetchData()); }, []); // 在这里使用 data return ( // 组件的 JSX ); };
useMemo
useMemo
:React自带的钩子函数,用于在组件渲染过程中进行记忆化计算,以提高性能。在React Redux中,可以使用该钩子函数对选择器函数进行记忆化,以避免不必要的重复计算。
示例用法:
import { useSelector } from 'react-redux'; import { createSelector } from 'reselect'; const selectData = state => state.data; const memoizedSelector = createSelector( selectData, data => data.filter(item => item.completed) ); const MyComponent = () => { const filteredData = useSelector(memoizedSelector); // 在这里使用 filteredData return ( // 组件的 JSX ); };
总结
对比是一种非常棒的学习编程方法,用已知的经验代入到新的知识上,帮助我们加深理解,促进内化。
下面用vuex和redux进行对比,会发现两者除了在语法上不同,但在功能、设计、理念、用法上如此一致,
功能
无论redux还是vuex,本质作用都是一个状态管理的工具,用于共享数据的仓库。
区别:
1、 redux可以适用于任何JavaScript框架中,无论react还是angluar 或者vue,当然vue有自己的仓库工具vuex。
2、vuex只适用于 vue框架之中
设计上
1、redux
- redux 中不可以直接修改原始state数据,需要拷贝原数据进行修改
- 不可执行异步操作,但可以通过中间件处理异步操作
2、vuex
- vuex 不能直接修改store数据,需要通过提交mutaions来修改。
- 提供了 actions 来处理异步函数,Actions 类似于 mutations,但可以包含异步代码
使用步骤:
vuex和react在语法上各有不同,但在步骤都可以统一为3步:
1、创建仓库;2、获取仓库;3、修改仓库、
在具体实现上如下:
Redux:使用 Redux 的步骤包括定义 action 类型、创建 action 创建函数、编写 reducer 处理器,以及创建和配置 store。
Vuex:在使用 Vuex 时,需要定义 state,然后编写 mutations 来修改 state,接着可以定义 actions 来处理异步操作,最后创建一个 Vuex 的实例并配置它。
优缺点:
redux
Redux 的优点:
- 可预测性:通过 action 和 reducer 明确描述数据变化。
- 可追溯性:记录所有的 action,便于调试和错误处理。
- 可测试性:纯函数 reducer 和 action 创建函数易于测试。
Redux 的缺点:
- 学习曲线较陡:相对于简单的状态管理需求,使用 Redux 可能有些繁琐。
- 需要编写大量的模板代码。
- 需要使用第三方中间件来处理异步操作。
vuex
Vuex 的优点:
- 与 Vue.js 集成:作为 Vue.js 的官方状态管理库,与 Vue.js 的集成非常方便。
- 简单易用:相对于 Redux,使用 Vuex 更加简单和直观。
- 适合中小型项目:对于中小型单页面应用,Vuex 提供了足够的功能,而且使用起来更加轻量。
Vuex 的缺点:
- 对于小型项目可能过于繁琐。
- 在大型项目中,过度使用 Vuex 可能导致较为复杂的代码结构。