官方文档
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

Redux三大原则

  1. 单一数据源
    1. redux应用维护了唯一的一个全局状态对象,存储在store中。唯一数据源是一种集中式管理应用状态的方式,便于监控任意时刻应用状态和调试应用。
  2. 状态不可变
    1. 任何时候都不能修改应用状态
    2. 必须通过action修改应用状态,根据action的不同描述,触发dispatch修改唯一数据源。
    3. Redux 期望所有状态更新都是使用不可变immutable的方式
  3. 纯函数修改状态
    1. action修改应用状态的意图,其目的是修改reducer,reducer必须是纯函数,所以reducer在接收action时不能直接修改原来的状态。
    2. 如果state是引用类型,可以使用es6的 …obj 扩展运算符,或者Object.assign({}, state, {change})

      Redux主要组成

      action

      action是一个具有type字段的JavaScript对象,可以将 action 称为描述应用程序发生了什么事件。
  • type 是一个字符串字段,定义action的动作描述,比如”todos/add”、”todos/remove”;把 action 的type 定义为 “域/事件名称”,/前第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。
  • action 的其他附加信息,放在第二个参数 payload 中。

action 对象可以如下所示:

  1. const addTodoAction = {
  2. type: 'todos/todoAdded',
  3. payload: 'Buy milk'
  4. }

action creator

action creator 是创建并返回 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象:
项目中定义使用

  1. // action types
  2. export const ADD_TODO = "TODOS/ADD_TODO";
  3. export const SET_VISIBILITY_FILTER = "TODOS/SET_VISIBILITY_FILTER";
  4. export const TOGGLE_TODO = "TODOS/TOGGLE_TODO";
  5. //action creators
  6. export const addTodo = (text) => ({
  7. type: ADD_TODO,
  8. text
  9. })
  10. export const setVisibilityFilter = (filter) => ({
  11. type: SET_VISIBILITY_FILTER,
  12. filter
  13. })
  14. export const toggleTodo = (id) => ({
  15. type: TOGGLE_TODO,
  16. id
  17. })

reducer

action用于描述应用发生什么操作,reducer是根据action做出不同响应,决定如何修改应用状态state。
reducer 是一个纯函数,接收当前的 state 和一个 action 对象,共2个参数,返回经过处理后的新的newState,newState必须是一个全新的数据对象,如果state是引用数据类型,需要在原来state的基础上复制出来一个对象并进行修改,通常使用es6的扩展运算符或Object.assign()。
reducer函数签名: (state, action) => newState;

reducer使用规则

  • 仅使用state和action计算新的状态值
  • 禁止直接修改state
  • 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码,必须是纯函数。
  • 如果有异步逻辑加入,需要使用中间件。

reducer 的小例子

  1. const initialState = { value: 0 }
  2. function counterReducer(state = initialState, action) {
  3. // 检查 reducer 是否关心这个 action
  4. if (action.type === 'counter/increment') {
  5. // 如果是,复制 `state`
  6. return {
  7. ...state,
  8. // 使用新值更新 state 副本
  9. value: state.value + 1
  10. }
  11. }
  12. // 返回原来的 state 不变
  13. return state
  14. }

拆分reducer

随着应用的复杂,会把reducer拆分成多个reducer

  1. // 定义2个reducer,并且设置初始状态
  2. // reducer todos的初始状态为空数组
  3. function todos(state = [], action){
  4. // ...
  5. }
  6. // reducer visibilityFilter的初始状态为 show_all
  7. function visibilityFilter(state = "show_all", action){
  8. // ...
  9. }

合并reducer

Redux提供combineReducers 方法,方便把多个拆分的reducer组合到一起。

  1. import { combineReducers } from "redux";
  2. const todoApp = combineReducers({
  3. todos,
  4. visibilityFilter
  5. })
  6. // ==> 等价于
  7. function todoApp(state = {}, action){
  8. return {
  9. todos: todos(state.todos, action),
  10. visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  11. }
  12. }

store

Redux应用唯一状态数据源store对象

  • store 是通过传入 reducer 创建,代码第8行。const store = createStore(reducer);
  • getState 的方法,返回当前状态值。
  • dispatch(action) 方法,发送更新状态的意图
  • subscribe(listener) 方法,注册监听函数,监听应用状态的改变

一个Redux应用只有一个store,store保存唯一数据源。store依赖reducer创建。

  1. import { createStore } from "redux";
  2. import reducer from './reducers'
  3. // 将reducer作为参数传入
  4. let store = createStore(reducer);
  5. // 创建store时,还可以设置初始状态
  6. // let store = createStore(reducer, initialState);

store创建完成,可以通过getState() 获取当前应用的状态state

  1. const state = store.getState();

修改state时,通过store的dispatch方法,发送action。

  1. // 定义action
  2. function addTodo(text){
  3. return {type: "ADD_TODO", text}
  4. }
  5. // 发送action
  6. store.dispatch(addTodo('todo ...'))

使用subscribe监听函数,可以观察到state的变化

  1. let unsubscribe = store.subscribe(()=>console.log(store.getState()));

每次应用状态更新,最新的应用状态就会被打印出来;需要取消监听是,可以直接调用unsubscribe();

dispatch

store对象有一个dispatch方法,用来发送action,然后执行reducer更新state对象。dispatch是更新store数据变化的唯一方法。

dispatch一个action可以理解为触发了一个事件。发生一些事情,我们希望 store 知道这件事。 Reducer 就像事件监听器一样,当reducer收到关注的 action 后,它就会更新 state 作为响应。
通常调用 action creator 来调用 action

  1. const increment = () => {
  2. return {
  3. type: 'counter/increment'
  4. }
  5. }
  6. store.dispatch(increment())
  7. console.log(store.getState())
  8. // {value: 2}

redux数据流程示意图

ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif

在React中使用Redux

react和redux并无直接关联,通过react-redux进行关联。
react-reudx.jpeg

connect函数

react-redux提供了connect函数,用于把react组件和redux的store连接起来,生成容器组件。
代码示例

  1. import { connect } from "react-redux";
  2. import TodoList from '../components/TodoList'
  3. const TodoList = connect()(TodoList);

创建出TodoList组件,可以把TodoList和Redux连接起来。创建处理的TodoList组件需要承担2项任务:

  • 从Redux的store中获取展示组件所需要的应用状态
  • 把展示组件的状态变化同步到Redux的store中

通过为connect传递2个参数可以让TodoList具备这两个功能。

  1. import { connect } from "react-redux";
  2. import TodoList from '../components/TodoList';
  3. const mapStateToProps = (state) => ({
  4. todos: getVisibleTodos(state.todos, state.visibilityFilter)
  5. })
  6. const mapDispatchToProps = {
  7. onTodoClick: toggleTodo
  8. }
  9. const TodoList = connect(
  10. mapStateToProps,
  11. mapDispatchToProps
  12. )(TodoList);

mapStateToProps和mapDispatchToProps都是函数

  • mapStateToProps负责从全局应用状态state取出所需数据,映射到展示组件的props
  • mapDispatchToProps负责把需要用到的action映射到展示组件的props上

    mapStateToProps

    mapStateToProps作用把state转换成props。
    state就是Redux store中保存的应用状态,会作为参数传递给mapStateToProps,props就是展示组件中的props
    每当store的state更新,mapStateToProps都会重新执行,重新计算并返回props,从而触发组件的重新渲染。
    1. const mapStateToProps = (state) => ({
    2. todos: getVisibleTodos(state.todos, state.visibilityFilter)
    3. })

    mapDispatchToProps

    mapDispatchToProps接收store.dispatch 方法作为参数,返回 给组件用于修改state的函数。 ```javascript const toggleTodo = (id) => ({ type: ‘TOGGLE_TODO’, id })

const mapDispatchToProps = (dispatch) => { onTodoClick: (id) => dispatch(toggleTodo(id)) }

  1. <a name="VqCPZ"></a>
  2. ### Provider
  3. 通过connect函数创建出的容器组件,怎么获取到Redux的store呢?<br />react-redux提供Provider组件,Provider简化代码如下
  4. ```javascript
  5. // Provider 内部实现也是依赖react 的context
  6. class Provider extends Component{
  7. getChildContext(){
  8. return { store: this.props.store}
  9. }
  10. render(){
  11. return this.props.children
  12. }
  13. }
  14. Provider.childContextTypes = {
  15. store: React.PropTypes.object
  16. }

Provider组件正是通过context 把store传递给子组件,使用Provider时,把它做为根组件,这样内层的任意组件才可以从context 中获取store对象。
项目中使用Provider

  1. import { createStore } from 'redux'
  2. import { Provider } from 'react-redux'
  3. import App from './components/App'
  4. import reducer from './reducers'
  5. const store = createStore(reducer)
  6. render(
  7. <Provider store={store}>
  8. <App />
  9. </Provider>,
  10. document.getElementById('root')
  11. )

redux的中间件

Redux中间件和node框架express类似,redux的action可类比web框架接收到请求,reducer可类比web框架的业务逻辑层,因此redux中间价代表action在到达reducer前的处理程序。
实际上,redux中间件就是一个函数。
redux中间件增强了store功能,可以利用中间件为action添加一些通用功能,例如日志输入、异常获取,可以通过store.dispatch日志输出的功能:

  1. let next = store.dispatch
  2. store.dispatch = function dispatchAndLog(action){
  3. console.log('dispatching', action);
  4. let result = next(action);
  5. console.log('next state', store.getState());
  6. return result;
  7. }

上面示例代码重新定义store.dispatch,在发送action前后都添加了日志输出,对store.dispatch进行改造,在发送action和执行reducer这两步之间添加其他功能。

redux-logger中间件使用

  1. import {applyMiddleware, createStore} from 'redux';
  2. import logger from 'redux-logger';
  3. import reducer from './reducers'
  4. const store = createStore(
  5. reducer,
  6. applyMiddleware(logger)
  7. )

代码从redux-logger中引入日志中间件logger,然后将它放入applyMiddleware函数中并传给createStore,完成store.dispatch 功能的加强。

applyMiddleware函数

  1. import compose from './compose'
  2. export default function applyMiddleware(...middlewares) {
  3. return (createStore) => (...args) => {
  4. const store = createStore(...args)
  5. let dispatch = () => {
  6. throw new Error(
  7. 'Dispatching while constructing your middleware is not allowed. ' +
  8. 'Other middleware would not be applied to this dispatch.'
  9. )
  10. }
  11. const middlewareAPI = {
  12. getState: store.getState,
  13. dispatch: (...args) => dispatch(...args),
  14. }
  15. const chain = middlewares.map((middleware) => middleware(middlewareAPI))
  16. dispatch = compose(...chain)(store.dispatch)
  17. return {
  18. ...store,
  19. dispatch,
  20. }
  21. }
  22. }
  1. export default function compose(...funcs) {
  2. if (funcs.length === 0) {
  3. return (arg) => arg
  4. }
  5. if (funcs.length === 1) {
  6. return funcs[0]
  7. }
  8. return funcs.reduce((a, b) => (...args) => a(b(...args)))
  9. }

applyMiddleware把接收到的中间件放入数组 chain中,通过 compose(…chain)(store.dispatch)定义加强版的dispatch方法。
每个中间件都接收一个包含getState和dispatch的参数对象,在利用中间件执行异步操作时,会使用这两个方法

redux-thunk异步操作中间件

异步操作在web应用中必不可少,最常见的发送网络请求。由于redux的工作流,发送action,reducer处理接收到的action,reducer返回新的state。这个流程不涉及异步操作,需要使用Redux 中间件处理异步操作。
redux-thunk时处理异步操作最常用的中间件。使用redux-thunk的代码如下:

  1. import {applyMiddleware, createStore} from 'redux';
  2. import thunk from 'redux-thunk';
  3. import reducer from './reducers'
  4. const store = createStore(
  5. reducer,
  6. applyMiddleware(thunk)
  7. );

现在定义异步action模拟向服务器请求数据

  1. // 异步action
  2. function getData(url){
  3. return function (dispatch){
  4. return fetch(url)
  5. .then(
  6. response => response.json(),
  7. error => console.log('error', error)
  8. )
  9. .then(json => dispatch({type:'recive_data', data: json}))
  10. }
  11. }
  12. // 发送action
  13. store.dispatch(getData("http://xxx"))

如果不使用redux-thunk中间件,上面代码会报错,因为store.dispatch只能接收普通JavaScript对象代表的action,使用redux-thunk后,store.dispatch就能接收函数作为参数。
异步action会先经过redux-thunk的处理,请求返回后,再次发送一个action: {type:’recive_data’, data: json}
这时的action就是普通JavaScript对象,处理流程和不使用中间件的流程一样。

redux-saga中间件

redux-saga也是处理异步的中间件。
学习文档

常用API:

  • take:创建一个 Effect 描述信息,用来命令 middleware 在 Store 上等待指定的 action。 在发起与 pattern 匹配的 action 之前,Generator 将暂停。
  • takeEvery: 允许处理并发的 action, 但是不会对多个任务的响应进行排序,并且不保证任务将会以它们启动的顺序结束。
  • takeLatest: 如果要对响应进行排序,可以使用takeLatest。在发起到 Store 并且匹配 pattern 的每一个 action 上派生一个 saga。并自动取消之前所有已经启动但仍在执行中的 saga 任务。takeLatest 将会在后台启动一个新的 saga 任务。

    获取远程数据示例

    ```javascript import { all, call, put, takeEvery } from ‘redux-saga/effects’; import { LOAD_TODO_LIST, RENDER_TODO_LIST } from ‘../actions’;

export function* fetchToDoList() { const endpoint = ‘https://gist.githubusercontent.com/brunokrebs/‘ + ‘f1cacbacd53be83940e1e85860b6c65b/raw/to-do-items.json’; const response = yield call(fetch, endpoint); const data = yield response.json(); yield put({ type: RENDER_TODO_LIST, toDoList: data }); }

export function* loadToDoList() { yield takeEvery(LOAD_TODO_LIST, fetchToDoList); }

export default function* rootSaga() { yield all([loadToDoList()]); }

  1. <a name="YXRAn"></a>
  2. #### 案例:
  3. 首先创建一个关联react和redux的组件
  4. ```javascript
  5. import React from "react";
  6. import ReactDOM from "react-dom/client";
  7. import { Provider, connect } from "react-redux";
  8. // 引入被增强的store,即添加了 applyMiddleware(sagaMiddleware)
  9. import store from "./store";
  10. import actions, { addActionCreator, addAsyncActionCreator } from "./store/actions";
  11. function Counter(props) {
  12. return (
  13. <div>
  14. <p>{props.count}</p>
  15. <button onClick={() => props.add(3)}>add</button>
  16. <button onClick={() => props.addAsync()}>async increment</button>
  17. </div>
  18. );
  19. }
  20. const mapStateToProps = (state) => state;
  21. const mapDispatchToProps = (dispatch) => {
  22. console.log('click');
  23. return {
  24. add: (payload) => dispatch(addActionCreator(payload)),
  25. addAsync: () => dispatch(addAsyncActionCreator()),
  26. // add: (payload) => dispatch(actions.add(payload)),
  27. };
  28. };
  29. // 通过connect,连接react组件和redux数据
  30. const NEWCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
  31. // 组件渲染
  32. const root = ReactDOM.createRoot(document.getElementById("root"));
  33. root.render(
  34. // 数据提供 Provider
  35. <Provider store={store}>
  36. <NEWCounter />
  37. </Provider>
  38. );

为了运行Saga,我们需要:

  • 创建一个 Saga middleware 和要运行的 Sagas
  • 将这个 Saga middleware 连接至 Redux store ```javascript import { createStore, applyMiddleware} from “redux”; import createSagaMiddleware from “redux-saga”; import reducer from “./reducer”; import {rootSaga} from “./saga”;

// 创建sagaMiddleware 实例,该实例被传递到applyMiddleware let sagaMiddleware = createSagaMiddleware(); // 创建全局store,使用applyMiddleware,对store进行加强 let store = createStore(reducer, applyMiddleware(sagaMiddleware)); // sagaMiddleware 的run方法,根saga sagaMiddleware.run(rootSaga) export default store;

  1. ```javascript
  2. import * as actionTypes from "./action-types";
  3. const reducer = (state = { count: 0 }, action) => {
  4. switch (action.type) {
  5. case actionTypes.ADD:
  6. return { count: state.count + action.payload };
  7. case actionTypes.INCREMENT:
  8. return { count: state.count + 1 };
  9. default:
  10. return state;
  11. }
  12. };
  13. export default reducer;
  1. import { put, take, takeEvery, call, all, delay } from "redux-saga/effects";
  2. import * as actionTypes from "./action-types";
  3. export function* incrementAsync() {
  4. yield delay(2000);
  5. yield put({ type: "INCREMENT" });
  6. }
  7. export function* watchIncrementAsync() {
  8. yield takeEvery(actionTypes.INCREMENT_ASYNC, incrementAsync);
  9. }
  10. function* addAsync() {
  11. // 延时2秒
  12. yield delay(2000);
  13. // 触发reducer更新
  14. yield put({ type: "ADD", payload: 5 });
  15. }
  16. function* watchAddAsync() {
  17. // takeEvery持续监听每次 action的触发
  18. yield takeEvery(actionTypes.ASYNC_ADD, addAsync);
  19. }
  20. // 创建rootSaga
  21. export function* rootSaga() {
  22. for (let i = 0; i < 3; i++) {
  23. console.log(`wait ${actionTypes.ASYNC_ADD} action`);
  24. // 事件订阅者 take,等待事件的触发,take只是一次
  25. const action = yield take(actionTypes.ASYNC_ADD);
  26. // console.log(action, "wait ok");
  27. // 触发take 事件后,执行 put 派发事件
  28. yield put({ type: actionTypes.ADD, payload: action.payload });
  29. console.log("continue run");
  30. }
  31. console.log("for game over,执行完成后,后面的程序才能执行");
  32. yield all([call(watchAddAsync), call(watchIncrementAsync)]);
  33. }
  1. export const ADD = "ADD";
  2. export const ASYNC_ADD = "ASYNC_ADD";
  3. export const INCREMENT = "INCREMENT";
  4. export const INCREMENT_ASYNC = "INCREMENT_ASYNC";

项目代码

react redux关系