1.Redux定义
Redux 是 JavaScript
应用的状态容器,提供可预测的状态管理。
当遇到如下问题时,建议开始使用 Redux:
- 你有很多数据随时间而变化
- 你希望状态有一个唯一确定的来源
- 你发现将所有状态放在顶层组件中管理已不可维护
2. 为什么React要用 Redux
React 是 DOM 的一个抽象层(UI 库),并不是 Web 应用的完整解决方案。因此react在涉及到数据的处理以及组件之间的通信时会比较复杂。
对于大型的复杂应用来说,这两方面恰恰是最关键的。因此,只用 React,写大型应用比较吃力。
- 2014 年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。
- 2015 年,Redux 出现,将 Flux 与函数式编程(reducer)结合一起,很短时间内就成为了最热门的前端架构。
- Flux 是最早的状态管理 工具,它提供了状态管理的思想,也提供对应的实现
- 除了 Flux、Redux 之外,还有:Mobx 等状态管理工具
-
主要的区别:组件之间的通讯问题
-
不使用 Redux (图左边) :
- 只能使用父子组件通讯、状态提升等 React 自带机制
- 处理远房亲戚(非父子)关系的组件通讯时乏力
- 组件之间的数据流混乱,出现 Bug 时难定位
-
使用 Redux (图右边):
- 集中式存储和管理应用的状态
- 处理组件通讯问题时,无视组件之间的层级关系
- 简化大型复杂应用中组件之间的通讯问题
- 数据流清晰,易于定位 Bug
3.Redux 三个核心概念
1.action
-
动作。
-
一个js对象,包含两个属性:
- type: 标识属性,值是字符串。多个type用action分开
- payload:数据属性,可选。表示本次动作携带的数据
-
actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
-
特点:
- 只描述做什么
- JS 对象,必须带有
type
属性,用于区分动作的类型 - 根据功能的不同,可以携带额外的数据,配合该数据来完成相应功能
{ type: 'add1' }
{ type: 'addN', payload:10 }
复制代码
2.reducer
-
一个纯函数
-
作用
-
- 初始化状态
- 修改状态
-
-
修改状态
根据传入的旧状态和action,返回新状态
公式:
(previousState, action) => newState
const reducer = (state = 0, action) => {
switch (action.type) {
// 执行动作add 让state 加1
case 'add':
// 返回新的state
return state + 1
case 'addN':
// 返回新的state
return state + action.payload
default:
return state
}
}
复制代码
3.store
-
store:仓库,Redux 的核心,整合 action 和 reducer
-
特点:
- 一个应用只有一个 store
- 维护应用的状态,获取状态:
store.getState()
- 创建 store 时接收 reducer 作为参数:
const store = createStore(reducer)
- 发起状态更新时,需要分发 action:
store.dispatch(action)
其他 API:这里一般使用react-redux
库代替):
— 订阅(监听)状态变化:const unSubscribe = store.subscribe(() => {})
— 取消订阅状态变化:
unSubscribe()
import { createStore } from 'redux'
import reducer from './reducer'
export default createStore(reducer)
复制代码
4.reducer拆分与合并
随着项目功能变得越来越复杂,需要 Redux 管理的状态也会越来越多。导致reducer函数越来越重。
这时需要按业务模块拆分,把大的reducer拆成小文件。
示例:
├── src
├── store # redux目录,一般约定叫store
| ├── index.js # 定义并导出store. 其中会导入reducer
| └── reducers # 多个模块的reducer
| ├── reducer1.js # 模块1的reducer
| ├── reducer2.js # 模块2的reducer
| └── index.js # reducer的整体入口,会导入reducer1, reducer2
├── index.js # 项目的入口文件,会导入并渲染App.js
├── App.js # 根组件,引入模块1 和模块2 组件
├── 模块1.js # 模块1 组件
└── 模块2.js # 模块2 组件
复制代码
src/store/reducers/index.js
中导入 combineReducers
合并reducer
import book from './book'
import user from './user'
import { combineReducers } from 'redux'
const rootReducer = combineReducers({
book,
user
})
export default rootReducer
复制代码
5.React-redux
react-redux 库
react-redux是 Redux 官方提供的 React 绑定库
- React 和 Redux 是两个独立的库,两者之间职责独立。
- Redux可以和其他的js库,框架一起使用,而并不专门用于react。
- 为了实现在 React 中使用 Redux 进行状态管理 ,就需要一种机制,将这两个独立的库关联在一起。这时候就用到 React-Redux 这个绑定库了
- 作用: 为 React 接入 Redux,实现在 React 中使用 Redux 进行状态管理。
react-redux-基本使用
步骤
-
安装
npm i react-redux
-
使用
- 按redux的要求,创建好store, reducer,action等等
- 从react-redux中引入 provider, useSelector, useDispatch来 操作 redux
API
Provider
- 用法:直接包装在根组件上。
<Provider store={store}>
- 好处:相比react + redux,这样就不需要每个组件都引入store了
useSelector
-
用法:获取公共状态
-
好处:
- 相比react + redux,不需要使用store.getState()了
- state变化了,它会自动更新
-
格式:
const 状态 = useSelector(store的数据 => 你需要的部分)
useDispatch
- 用法:派发action,修改数据
- 格式:
const dispatch = useDispatch(); dispatch(action)
代码: 入口文件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(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
复制代码
需要使用和修改数据的组件中:
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function Uncle () {
// 拿到dispath修改数据
const dispatch = useDispatch()
// 获取数据
// state:就是 redux中的数据
const num = useSelector((state) => {
return state
})
// ....
复制代码
6.action creator
因为我们在创建 Action 的时候,有时候有些内容是固定了,我们可能会要在多个地方可以 dispatch
这个 Action,那么我们每次都需要写下面长长的一串 :
dispatch({type: 'book/add', payload: '三国演义'})
dispatch({type: 'book/add', payload: '红楼梦'})
复制代码
此时我们只需要定义一个函数,然后传入需要变化的参数就可以了。这种接收一些需要修改的参数,返回一个 Action 的函数在 Redux 中被称为 Action Creators(动作创建器)。
当我们使用 Action Creators 来创建 Action 之后,我们再想要修改 Store 的状态就变成了下面这样:
// Action Creators
const addAction = (payload) => ({type: 'book/add', payload})
// 调用函数传入需要变化的参数
dispatch(addAction('三国演义'))
dispatch(addAction('红楼梦'))
复制代码
同样的,当项目不断增大需求不断增多,我们也可以对action creators进行划分不同的模块:
├── src
├── store # redux目录,一般约定叫store
| ├── index.js # 定义并导出store. 其中会导入reducer
| └── actions # 多个模块的action
| ├── action1.js # 模块1的 相关action creator
| ├── action2.js # 模块2的 相关action creator
| └── reducers # 多个模块的reducer
| ├── reducer1.js # 模块1的reducer
| ├── reducer2.js # 模块2的reducer
| └── index.js # reducer的整体入口,会导入reducer1, reducer2
├── index.js # 项目的入口文件,会导入并渲染App.js
├── App.js # 根组件,引入模块1 和模块2 组件
├── 模块1.js # 模块1 组件
└── 模块2.js # 模块2 组件
复制代码
7. Action Type的使用
在reducer和action createor中都用到了一个字符串的 action type,当项目过大,内容过多,就会导致这些字符串容易写错,并且不利于统一修改。
可以集中处理 action type,保持项目中 action type 的一致性
处理方式:
- 在 store 目录中创建
actionTypes
目录或者constants
目录,集中处理 - 使用常量来存储 action type。例如:
export const SET_NAME = 'user/setName'
export const SUB_MORE = 'money/subMore'
复制代码
3.将项目中用到 action type 的地方替换为这些常量,从而保持项目中 action type 的一致性
8.redux-thunk中间件
Redux 中间件执行时机:在 dispatching action 和 到达 reducer 之间。
使用中间件:
dispatch(action) => 执行中间件代码 => reducer
。dispatch() 就是 中间件 封装处理后的 dispatch,但是,最终一定会调用 Redux 库自己提供的 dispatch 方法
redux-thunk-基本使用
redux-thunk
中间件可以处理函数形式的 action
。因此,在函数形式的 action 中就可以执行异步操作代码,完成异步操作。
const action1 = async (dispatch) =>{
const res = await 异步动作()
dispatch({type: 'todos/add', payload: res.data})
}
dispatch(action1)
复制代码
完整目录结构
├── 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 组件
复制代码
安装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
复制代码
入口文件index.js
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(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
复制代码
store/index.js
// 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
复制代码
reducer/index.js
// 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
复制代码