什么是Redux?官网文档是这么说的:Redux 是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 ,它以集中式Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。简单来说,Redux就是一个管理全局状态的JS库,把一些在项目的很多页面中都使用到的状态,集中进行管理,并且能很方便的在整个项目的任何页面使用,类似于Vue的Vuex。
Redux可以应用于React、Vue、Angular等框架,但基本上只在React中与React-Redux结合使用。
什么情况下使用Redux?总体原则: 能不用就不用, 如果不用比较吃力才考虑使用,Redux 并非适用所有项目,只有当项目出现以下情况时,你才可以考虑使用它:① 在项目的很多组件中,都重复使用了某组件的某个状态。 ② 重复使用的状态会频繁更新,更新状态的逻辑很复杂。③ 一个组件要改变另一个组件的状态。④ 某个状态需要在任何地方都能拿到。
Redux Toolkit 是我们推荐的编写 Redux 逻辑的方法。 它包含我们认为对于构建 Redux 应用程序必不可少的包和函数,并简化了大多数 Redux 任务,使编写 Redux 应用程序变得更加容易。
action 是Redux 用来描述应用发生什么事件,是一个具有 type
字段的JS对象。 type
字段是字符串类型的,相当于描述这个action的名字,通常形式为 域/事件名称,第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。
action 还可以有一个payload字段,用来包含有关发生事件的附加信息。
// 一个典型的 action对象
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
创建并返回一个 action 对象的函数,让我们不需要每次都手动编写action。
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
action的type字段在reducer和action creator很多地方都会用到,当项目过大,内容很多,就会导致type的字符串容易写错,而且一一修改也很麻烦。所以我们可以统一处理常用的type字段,比如在 store 目录中创建 actionTypes
目录文件或 constants
目录文件,集中处理使用常量来存储 action type。用到存储的type时,只需要引入对应文件即可,这样修改也只需要修改该文件就能影响整个项目。
export const SET_NAME = 'user/setName'
export const SUB_MORE = 'money/subMore'
reducer 是一个纯函数,接收当前的 state 和一个 action 对象,决定如何更新状态,并返回更新后的状态。类似于一个事件监听器,根据接收到的action(事件)来决定处理事件的逻辑,如果reducer关心这个action,则复制 state,并使用新值更新 state 副本,然后返回新 state。如果不关心,则返回原来的state不变。
Reducer的使用规则:① 仅使用state和action参数计算最新的状态值。② 禁止直接修改state,必须通过复制当前state,然后对复制的值进行更新的方式来做 不可变更新。③ 禁止任何一步逻辑、依赖随机值或导致其他副作用的代码。
// 当前state
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// 检查 reducer 是否关心这个 action
if (action.type === 'counter/increment') {
// 如果关心,则复制 `state`
return {
...state,
// 使用新值更新 state 副本
value: state.value + 1
}
}
// 如果不关心,则返回原来的 state 不变
return state
}
store 对象是存储当前Redux应用 state 状态的仓库,可以把它看成一个数据源,对所有的数据进行统一管理,一个应用只能有一个store。store 需要通过createStore 或 Redux Toolkit 的configureStore(推荐)来创建,创建时需要传入一个reducer 作为参数,并具有一个名为 getState() 的方法来获取当前状态值。
// 导入创建方法
import { configureStore } from '@reduxjs/toolkit'
// 创建store
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
/*------------ 或者 -------------*/
// 导入创建方法
import { createStore } from 'redux'
// 创建store
const store = createStore(counterReducer)
console.log(store.getState())
store 还有一个方法 叫做 dispatch(),更新 store 中的 state 的唯一方法是调用 store.dispatch(action) 方法,参数为一个action对象。调用该方法之后,store 将执行所有 reducer 函数并计算出更新后的 state,然后调用 getState()
可以获取最新 state。
dispatch 一个 action 可以理解为触发了一个”事件“,而Reducer就像一个事件监听器,当收到了关注的 action 之后,就会更新state。
// dispatch 一个 action
store.dispatch({ type: 'counter/increment' })
// 获取最新的state
console.log(store.getState())
// {value: 1}
Selector 函数可以从 store 状态库中提取指定的 state ,因为 getState() 获取的是全部的 state ,想要获取其中部分 state ,就需要从全部的 state 进行读取操作,如果我们在项目的很多地方都用到了相同的那部分state,此时就可以通过设置 Selector 函数来避免重复的读取逻辑。
// 将获取部分状态的逻辑封装到 Selector 函数中
const selectCounterValue = state => state.value
// 调用 Selector 函数 并将全部state作为参数传入
const currentValue = selectCounterValue(store.getState())
console.log(currentValue) // 2
首先我们需要了解三个概念:
了解以上概念之后,所谓的单向数据流就是:应用基于当前的state渲染出View,当用户对应用做出某种行为(如点击事件等),state就会根据行为进行更新,生成新的state,然后应用会基于新的state 重新渲染View。
对于 Redux 我们可以将单向数据流进一步细化:
① 使用最顶层的 root reducer 函数创建 Redux store。
② store 调用一次 root reducer,并将返回值保存为初始值。
③ UI首次渲染时,UI组件访问 store 的当前 state,获取数据渲染页面。同时订阅监听 store 的更新。
① 用户做出某种行为(如点击事件),就会 dispatch 一个 action 到 store。
② action 触发关心它type的 reducer,reducer根据当前state和 action,更新 state 副本,然后返回新 state。
③ store会通知所有订阅 store 的UI组件,通知他们store发生更新。
④ 接收到通知的UI组件会去检查自己依赖的 state 部分是否发生更新,如果更新,则相应的UI组件也会使用新 state 来渲染视图。没更新则不会重新渲染。
“Mutable” 意为 “可改变的”,而 "immutable 意为永不可改变。可改变的是指在内存引用地址不变的前提下,可以改变其内容。而不可改变是指,在内存引用地址不变的前提下,不可改变其内容,只能通过修改内存引用地址的方式,来修改内容。
在JS中,对象和数组默认是可改变的,但Redux 期望所有状态更新都是使用不可变的方式。所以我们需要先复制原来的对象或数组,然后更新复制体,可结合展开运算符、或数组的相关方法实现。
const obj = {
a: {
c: 3
},
b: 2
}
const obj2 = {
// obj 的备份
...obj,
// 覆盖 a
a: {
// obj.a 的备份
...obj.a,
// 覆盖 c
c: 42
}
}
const arr = ['a', 'b']
// 创建 arr 的备份,并把 c 拼接到最后。
const arr2 = arr.concat('c')
// 或者,可以对原来的数组创建复制体
const arr3 = arr.slice()
// 修改复制体
arr3.push('c')
react-redux 是 Redux 官方提供的 React 绑定库,简化了在 React 中使用 Redux 进行全局状态管理的操作。 通过 npm install react-redux
,就能将 react-redux 安装到项目中。使用时,首先按照redux的要求,创建好store、reducer、action等基础文件,然后从react-redux中引入provider、useSelector、useDispatch等工具来操作 redux。
该组件用来直接包裹在根组件外,并将创建的 store 作为属性,这样就可在项目的任何地方访问 store ,而不必在每个组件引入 store。
// 项目入口文件 index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
ReactDom.render(
,
document.getElementById('root')
)
该工具是用来获取store中的公共数据,相当于 store.getState(),当关注的state发生变化时,会自动更新组件中的对应数据,引发页面重新渲染。
语法:const n = useSelector((store的数据) => { return 你需要的部分 })
import React from 'react'
import { useSelector } from 'react-redux'
export default function Uncle () {
// 获取数据
// state:就是 store 中的全部数据
const num = useSelector((state) => {
// 返回我们需要的部分
return state.num
})
}
该工具用来创建 dispatch,通过dispatch 派发 action,触发对应的 reducer,修改 store 中的state。
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function Uncle () {
// 创建 dispath
const dispatch = useDispatch()
// 派发 action 修改state
dispath({ type: 'counter/increment' });
// 获取数据
// state:就是 store 中的全部数据
const num = useSelector((state) => {
// 返回我们需要的部分
return state.num
})
// ....
redux-thunk 是一个 中间件,执行时机是在 dispatch 发送 action 之后,到达 reducer 之前。也就是:dispatch(action) => 执行redux-thunk => reducer
。redux-thunk 的作用主要是处理函数形式的action,以及执行函数形式的action中的异步操作代码,完成异步操作。
const action1 = async (dispatch) =>{
const res = await 异步动作()
dispatch({type: 'todos/add', payload: res.data})
}
dispatch(action1)
安装redux: npm i redux
安装: npm i react-redux
thunk中间件: npm i redux-thunk
查看redux的操作日志: npm i redux-logger
chrome开发者工具调试跟踪redux状态(需要先安装浏览器redux插件):
npm i redux-devtools-extension -D
// 合并一次性下载
npm i redux react-redux redux-thunk redux-devtools-extension
├── src
├── store # redux目录,一般约定叫store
| ├── index.js # 定义并导出store. 其中会导入reducer
|
| └── actions # 多个模块的action
| ├── action1.js # 模块1的 相关action creator
| ├── action2.js # 模块2的 相关action creator
| └── index.js # 合并 action creator
|
| └── reducers # 多个模块的reducer
| ├── reducer1.js # 模块1的reducer
| ├── reducer2.js # 模块2的reducer
| └── index.js # reducer的整体入口,会导入reducer1, reducer2
|
| └── actionTypes
| ├── actionType1.js # 模块1的actionType
| ├── actionType2.js # 模块2的actionType
|
├── index.js # 项目的入口文件,会导入并渲染App.js
├── App.js # 根组件,引入模块1 和模块2 组件
|
|── Pages
├── 模块1.js # 模块1 组件
└── 模块2.js # 模块2 组件
// applyMiddleware(中间件1,中间件2)使用中间件
import { createStore, applyMiddleware } from 'redux'
// composeWithDevTools() redux调试工具
import { composeWithDevTools } from 'redux-devtools-extension'
// thunk中间件 dispath()能够传入函数执行异步请求
import thunk from 'redux-thunk'
// 合并后的reducer
import reducer from './reducers'
// 创建store 传入合并后的reducer
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
export default store
// channel和newList组件的reducer
import channel from './channel'
import newList from './newList'
// combineReducers 合并各个reducer
import { combineReducers } from 'redux'
const rootReducer = combineReducers({
channel,
newList
})
export default rootReducer
import App from './App'
import ReactDOM from 'react-dom'
import './styles/index.css'
// Provider包裹根组件App就不需要每个组件都引入store了
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
,
document.getElementById('root')
)