redux
redux 是 react 中全局状态管理用的插件
文档地址:cn.redux.js.org/
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
- 代码结构
- 组件之间的通信
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
需要使用Redux的项目:
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要Redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
Redux的设计思想:
- Web 应用是一个状态机,视图与状态是一一对应的。
- 所有的状态,保存在一个对象里面(唯一数据源)。
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
Redux的使用的三大原则:
- Single Source of Truth(唯一的数据源)
- State is read-only(状态是只读的)
- Changes are made with pure function(数据的改变必须通过纯函数完成)
容器组件和展示组件
容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)
安装
安装命令: cnpm install redux
创建一个 react 项目,然后安装 redux
删除 src 中的所有文件,保留 index.js
创建一个 store 文件夹,然后再该文件夹下新建一个 index.js
创建一个 App.jsx 文件
使用 redux
redux 接入
-
先安装 cnpm install redux
-
在项目的 src 中先创建 store/index.js
-
在index.js 中导入 创建状态的方法 import { legacy_createStore as createStore } from 'redux'
-
定义一个 reducer ,是一个纯函数,该函数有两个参数
参数一 就是 state,该 state 就是全局状态中的数据
参数二 action 中有两个属性,第一个 type 标记我们要做的事情, payload 就是参数
-
创建 store 对象 const store = createStore(reducer)
-
将 store 暴露出去 export default store
-
在入口文件 index.js 中导入 store
-
使用 store 订阅状态管理器改变是执行的操作
-
在组件中导入 store,通过 store.getState 获取全局状态中的数据
-
可以在 store/index.js 文件中,判断 type 是什么来决定做什么事情
-
在组件中通过 store.dispatch({type: 'add'}) 触发改变
-
需要传递参数的时候可以使用 store.dispatch({type: 'addNum', payload: 10})
redux-分模块
- 将原来的 reducer 提取到一个单独的文件中 store/modules/app.js
- 在 app.js 文件中将 reducer 导出
- 在 store/index.js 中导入 app 状态模块
- 在 store/modules/pro.js 中创建 reducer 是我们第二个状态模块
- 将 pro.js 中的 reducer 导出
- 在 store/index.js 中导入 pro状态模块
- 在 store/index.js 导入 combineReducers 方法
- 通过 combineReducers 将 app 模块和 pro 模块合并成一个 reducer
- {store.getState().app.count} 使用的时候需要先 .模块名 然后才能拿到状态
添加 types
因为全局状态的事件名很多,而且事件名有些还会非常长
导致我们记不住,而且书写还会容易出错
所以我们想要让事件名称可以提示
首先在 store/types.js 文件
在该文件中定义我的事件常量
导出所有的常量
需要使用的时候直接导入即可
定义全局状态中的事件用名的
可以帮助我们写代码的时候有对应的提示功能
可以减少代码的出错次数
react-redux
react-redux 连接 状态组件 和展示组件 用的插件 cn.react-redux.js.org/ 0. 安装 npm install react-redux 0. 在入口 index.js 文件中导入 import { Provider } from 'react-redux' 0. 使用 Provider 组件将 App 组件包裹,并在 Provider 组件中添加一个属性 store
root.render(<Provider store={store}> <App /> </Provider> );
-
在组件中先解构出 import { connect } from 'react-redux'
-
使用 connect ,connect 接收两个参数
第一个:是我们当做组件所需要的状态
第二个:是我们组件所需要修改全局状态的方法
connect 的返回值是一个高阶组件
redux-异步
redux-thunk
使用步骤
- 安装: cnpm i redux-thunk
1 创建文件 store/action/pro.js
// store/action/pro.js
import axios from "axios";
import * as types from '../types'
// 定义异步操作的地方
const action = {
getBannerListAction (dispatch) {
axios.get('/banner/list').then(res => {
// 需要将请求到的数据放在全局状态管理中
dispatch({ type: types.CHANGE_BANNER_LIST, payload: res.data.data })
})
},
// 若果没有参数,那么我们函数的参数就是 dispatch
// 如果有参数,那么该区域的参数就是传递过来的值
getProListAction (data) {
console.log(data);
// 如果有传递参数,那么就需要一个返回值
// 返回的是一个函数,该函数中有一个参数是 dispatch
return (dispatch) => {
axios.get('/pro/list', { params: data }).then(res => {
dispatch({ type: types.CHANGE_PRO_LIST, payload: res.data.data })
})
}
}
}
export default action
应用异步模块,在 store/index.js 中做以下配置
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux'
import app from './modules/app'
import pro from './modules/pro'
// 导入异步 thunk
import thunk from 'redux-thunk'
// 将两个或者多个 reducer 合并为一个 reducer
const reducer = combineReducers({
app: app,
pro
})
// applyMiddleware(thunk) 应用异步模块
const store = createStore(reducer, applyMiddleware(thunk))
export default store
在组件中调用异步方法
// 导入异步操作
import action from './store/action/pro'
// 将修改全局状态的方法添加到 props
const mapDispatchToProps = (dispatch)=>{
return {
getBannerList(){
// 触发异步事件用的
dispatch(action.getBannerListAction)
},
getProList(){
dispatch(action.getProListAction({limitNum: 8}))
}
}
}
redux-saga
使用步骤
- 安装 cnpm i redux-saga
- 创建 api/pro.js 文件且封装数据请求方法
// 数据请求封装 api/pro.js
import axios from "axios";
export function getBannerListData () {
return axios.get('/banner/list')
}
export function getProListData (params) {
return axios.get('/pro/list', { params })
}
在 store 下面创建一个 mySaga.js 文件
// 该文件是用来写异步的地方
import { call, put, takeLatest } from 'redux-saga/effects'
/*
call: 用来触发请求用的方法
put: 类似 dispatch 方法,用来触发事件用的
takeLatest: 监听事件执行的函数
*/
import { getBannerListData, getProListData } from '../api/pro'
import * as types from './types'
// 定义一个异步操作
function* getBannerListAction () {
// 1. 请求得到数据
const res = yield call(getBannerListData)
// 2. 修改全局状态的方法
yield put({
type: types.CHANGE_BANNER_LIST,
payload: res.data.data
})
}
// 定义一个异步操作
function* getProListAction (action) {
// console.log(111111, action);
// 请求参数可以直接放在 action 中,写在触发请求的后面
const res = yield call(getProListData, action.payload)
yield put({
type: types.CHANGE_PRO_LIST,
payload: res.data.data
})
}
function* mySaga () {
// takeLatest 用来监听执行一个任务用的
// takeLatest 是一个函数,该函数有两个参数
// 参数一: 是监听任务的名称
// 参数二: 是监听到任务执行后要调用的异步方法
yield takeLatest(types.REQUEST_BANNER_LIST, getBannerListAction)
yield takeLatest(types.REQUEST_PRO_LIST, getProListAction)
}
export default mySaga
在 store/index.js 文件中,导入 mySaga 和创建中间件的方法
// store/index.js 配置saga生效
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux'
import app from './modules/app'
import pro from './modules/pro'
// createSagaMiddleware 创建 saga 中间件的方法
import createSagaMiddleware from 'redux-saga'
import mySaga from './mySaga'
// 创建中间件
const sagaMiddleware = createSagaMiddleware()
// 将两个或者多个 reducer 合并为一个 reducer
const reducer = combineReducers({
app: app,
pro
})
// 应用中间件
const store = createStore(reducer, applyMiddleware(sagaMiddleware))
// 是 saga 生效
sagaMiddleware.run(mySaga)
export default store
在组件中触发 mySaga 中定义的事件
无需导入任何东西,直接触发异步事件即可
// 将修改全局状态的方法添加到 props
const mapDispatchToProps = (dispatch)=>{
return {
getBannerList(){
// 调用 saga 中定义的方法
dispatch({type: types.REQUEST_BANNER_LIST})
},
getProList(){
// 执行 mySaga 中监听的事件
dispatch({type: types.REQUEST_PRO_LIST, payload: {limitNum: 5}})
}
}
}