Redux

一、概念

1. 解决什么问题?

React:Redux简介_中间件

2. 基本概念

  1. Redux主要作用就是统一状态管理,将分散在各个组件中的状态全部放在统一的store对象中进行管理,适用于状态特别分散、状态共用较多、状态传递较深的情形。
  2. Redux 是一个提供可预测化状态管理的容器,主要包含store,reducer,action
    ① 应用中的 state 以对象树的形式存储在 store 中
    ② state是只读的,惟一改变 state 的方法就是触发 action
    ③ action 是一个用于描述已发生事件的普通对象
    ④ 通过 dispatch action来改变 state,描述 action如何改变 state 的函数叫 reducer

3. 分解

  1. action
    可参考action设计规范, type,payload,error.meta,其中 type 是必须的
    异步action,通过中间件实现
  2. reducer
    reducer必须是纯函数
    可以拆分成多个reducer, 通过combineReducers合并
  3. store
    单一Store
    根据reducer创建 store.createStore(reducer,initialState)

二、Redux工作流程

1. 图示

React:Redux简介_中间件_02

2. 步骤分解

  1. 用户发出 Action
    store.dispatch(action);
    
  2. Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action, Reducer 会返回新的 State
    let nextState = todoApp(previousState, action);
    
  3. State 一旦有变化,Store 就会调用监听函数
    // 设置监听函数
    store.subscribe(listener);
    
  4. listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View

3. redux的原则

  1. state 是只读的,唯一修改它的方式是 actions
  2. 更新的唯一方式:dispatch(action) -> reducer -> new state
  3. Reducer 函数必须是“纯”的 —— 不能修改它的参数,也不能有副作用(不能对函数作用域之外的变量做任何更改。不要改变函数作用域以外的变量,不要调用其他会改变的函数,也不要dispatch actions等)

三、Redux改造TodoList

  1. 将分散在各个组件的状态统一放在store全局对象上,这样在每个组件需要状态时可以直接引入store取出状态,避免了状态之间的多层传递
  2. 对state的所有操作必须通过dispatch发出action给reducer,由reducer根据action的type对state进行操作并返回新的state,这使state的改变可容易追踪和可控
  3. 因为store是全局的,因此子组件不需要组件向父组件开放过多的接口(不需要传入那么多的变量给prop)

四、TodoList改造步骤

1. 目录结构规划

src |
     -| commponents
     -| store
App.js
index.css
index.js
store |
        -| index.js
        -| reducers.js
        -| actionCreators
        -| actionTypes
commponents |
                      -| Foot.jsx
                      -| Item.jsx
                      -| List.jsx
                      -| Top.jsx

2. store/index.js

  1. 说明:全局状态对象store创建和导出,它需要提供一个返回 state 的函数——reducers
  2. 代码
    import {createStore, applyMiddleware, compose} from 'redux'
    import reducers from './reducers'
    import ReduxThunk from  'redux-thunk'
    
    // 处理redux-thunk的兼容问题
    const composeEnhancers =
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
    
    const enhancer = composeEnhancers(
        applyMiddleware(ReduxThunk)
    );
    
    //创建store状态管理对象
    const store = createStore(reducers, enhancer);
    export default store;
    

3. store/actionTypes

  1. 说明:
    ① Action对象描述你想做出的改变或者将触发的事件,它必须带有一个type 属性,为action提供type描述(说明),约定action在reducers中的匹配,这个文件就是将所有的type定义为常量,避免编写时出错
    ② actionCreators中定义的每个回调函数返回的action对象上都有一个type属性,用以标识每个action
    ③ 在界面通过store.dispatch(action)(这里的action都是从actionCreators中引入的)派发到renducers时,renducers函数是通过actions.type来判断传过来的是哪个action
  2. 代码
    export const GET_ALL_ITEM = 'get_all_item'; // 存TODO
    export const CHANGE_TODO_ITEM = 'change_todo_item'; // 修改一条TODO的选中状态
    export const DEL_TODO_ITEM = 'del_todo_item'; //删除一条TODO
    export const ADD_TODO_ITEM = 'add_todo_item'; //添加一条TODO
    export const DEL_FINISHED_TODO_ITEM = 'del_finished_todo_item'; //删除所有已经完成的TODO
    export const IS_CHECKED_ALL_TODO_ITEM = 'is_checked_all_todo_item'; //删除所有已经完成的TODO
    

4. store/actionCreators.js

  1. 说明:
    ① 这里定义了所有的action,通过回调函数的形式返回action对象,使用回调函数的目的是为了让界面组件可以调用
    ② 每个action都有一个type属性,它定义了该action的类型;同时接受一个参数,是界面组件需要的状态,由action带给reducer
    ③ 界面上调用这里定义的回调函数(返回一个对象,对象的第一个属性type是,第二个参数是从界面接收的参数),通过store.dispatch(action)将action派发到reducers,reducres根据action.type属性对state进行相应处理
    ④ action充当了界面与reducers的桥梁,将界面中组件需要的数据和对数据进行的处理传给renducers,由reducers负责更新state
  2. 代码
    import {
        GET_ALL_ITEM,
        CHANGE_TODO_ITEM,
        DEL_TODO_ITEM,
        ADD_TODO_ITEM,
        DEL_FINISHED_TODO_ITEM,
        IS_CHECKED_ALL_TODO_ITEM
    } from './actionTypes'
    
    // 1. 存Todo
    export const getAllItemAction = (todos)=>({
        type: GET_ALL_ITEM,
        todos
    });
    
    // 2. 单个TODO选中与否
    export const getChangeItemFinishedAction = (todoId, flag)=>({
        type: CHANGE_TODO_ITEM,
        todoId,
        flag
    });
    
    // 3. 单个TODO删除
    export const getDelItemAction = (todoId)=>({
        type: DEL_TODO_ITEM,
        todoId
    });
    
    // 4. 添加一条记录
    export const getAddItemAction = (todo)=>({
        type: ADD_TODO_ITEM,
        todo
    });
    
    // 5. 删除所有已经完成的记录
    export const getRemoveFinishedItemAction = ()=>({
        type: DEL_FINISHED_TODO_ITEM
    });
    
    // 6. 全选与非全选
    export const getIsCheckedAllAction = (flag)=>({
        type: IS_CHECKED_ALL_TODO_ITEM,
        flag
    });
    

5. store/reducers

  1. 说明:
    ① reducer 的职责是接收当前 state 和一个 action 然后返回新的 state
    ② 状态state以及操作状态的方法都在这里,符合了数据和操作数据的方法在同一个文件的原则
    ③ 根据在界面组件中通过store.dispatch(action)派发过来的action.type,匹配到相应的操作状态的方法,并根据组件传入的 参数,对state进行相应操作,将当前state直接进行替换,并合并旧的state返回给store
    ④ reducer是直接用新的state替换旧的state,而不是更新旧的state,就是说旧的state还是保留的;返回最新的state给界面组件,同时保留了新state和旧state存入store,使state可以回溯和方便进行时间旅行调试
  2. 代码
    import {
        GET_ALL_ITEM,
        CHANGE_TODO_ITEM,
        DEL_TODO_ITEM,
        ADD_TODO_ITEM,
        DEL_FINISHED_TODO_ITEM,
        IS_CHECKED_ALL_TODO_ITEM
    } from './actionTypes'
    
    
    // 初始状态数据
    const defaultState = {
       todos: [],
       finishedCount: 0
    };
    
    //根据action的type,对state进行相应操作,并返回新的state
    export default (state = defaultState, action)=>{
        console.log(state, action);
         // 1. 存所有的Todo
        if(action.type === GET_ALL_ITEM){
             const  newState = JSON.parse(JSON.stringify(state));
             newState.todos = action.todos;
            return newState;
        }
        // 2. 选中与取消选中
        if(action.type === CHANGE_TODO_ITEM){
            const  newState = JSON.parse(JSON.stringify(state));
            // 1. 遍历
            let tempFinishedCount = 0;
            newState.todos.forEach((todo, index)=>{
                if(action.todoId === todo.id){
                    todo.finished = action.flag;
                }
                if(todo.finished){
                    tempFinishedCount += 1;
                }
            });
            // 2. 返回一个新的状态
            newState.finishedCount = tempFinishedCount;
            return newState;
        }
        // 3. 单个TODO删除
        if(action.type === DEL_TODO_ITEM){
            const  newState = JSON.parse(JSON.stringify(state));
            // 1. 遍历
            let tempFinishedCount = 0;
            newState.todos.forEach((todo, index)=>{
                if(action.todoId === todo.id){
                    newState.todos.splice(index, 1);
                }
            });
            // 处理选中的
            newState.todos.forEach((todo, index)=>{
                if(todo.finished){
                    tempFinishedCount += 1;
                }
            });
            // 2. 返回新状态
            newState.finishedCount = tempFinishedCount;
            return newState;
        }
        // 4. 添加一条记录
        if(action.type === ADD_TODO_ITEM){
            console.log(action);
            const  newState = JSON.parse(JSON.stringify(state));
            newState.todos.push(action.todo);
            return newState;
        }
        // 5. 删除所有已经完成的记录
        if(action.type === DEL_FINISHED_TODO_ITEM){
            const  newState = JSON.parse(JSON.stringify(state));
            let tempArr = [];
            newState.todos.forEach((todo, index)=>{
                if(!todo.finished){ // 没有完成的任务
                    tempArr.push(todo);
                }
            });
    
            // 2. 返回新状态
            newState.finishedCount = 0;
            newState.todos = tempArr;
            return newState;
        }
        // 6. 全选与非全选
        if(action.type === IS_CHECKED_ALL_TODO_ITEM){
            const  newState = JSON.parse(JSON.stringify(state));
    
            // 6.1. 遍历
            let tempFinishedCount = 0;
            newState.todos.forEach((todo, index)=>{
                todo.finished = action.flag;
            });
    
            // 处理选中的
            newState.todos.forEach((todo, index)=>{
                if(todo.finished){
                    tempFinishedCount += 1;
                }
            });
    
            // 6.2. 返回新状态
            newState.finishedCount = tempFinishedCount;
            return newState;
        }
    
        return state;
    }
    
Redux中间件

一、redux-thunk

  1. 概念
    ① redux-thunk是一个中间件,需要配合redux提供的applyMiddleware一起使用
    ② 主要是将常规的对象类型的action扩展为可接受函数类型的action
    ③ 它可以让原本只支持同步方式的redux扩展为支持异步的方式
    ④ 操作流程

    1. 将需要修改的state都存入到store里
    2. 发起一个action用来描述发生了什么,用reducers描述action如何改变state tree
    3. 创建store的时候需要传入reducer,真正能改变store中数据的是store.dispatch API
  2. 使用
    ① 安装yarn add redux redux-thunk
    ② 步骤1

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    

    ③ 步骤2

    const store = createStore(
      rootReducer,
      applyMiddleware(thunk)
    );
    

    React:Redux简介_C_03
    ④ 在actionCretors中使用
    React:Redux简介_中间件_04

  3. redux-dev-tools和redux-thunk兼容:处理网址

    const composeEnhancers =
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
    
    const enhancer = composeEnhancers(
        applyMiddleware(thunk),
    );
    

    React:Redux简介_数据_05

二、Redux-saga

  1. 概念
    ① redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)
    ② redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件
    ③ 表现形式

    1. reducer负责处理action的stage更新
    2. sagas负责协调那些复杂或者异步的操作
  2. 原理
    ① sagas是通过generator函数来创建的
    ② sagas监听发起的action,然后决定基于这个action来做什么
    比如:是发起一个异步请求,还是发起其他的action到store,还是调用其他的sagas 等
    ③ 在redux-saga中,所有的任务都通过用 yield Effects 来完成
    Effects 都是简单的 javascript对象,包含了要被 saga middleware 执行的信息
    ④ redux-saga 为各项任务提供了各种 Effects创建器,让我们可以用同步的方式来写异步代码
    ⑤ 名词

    1. put(action)
      ① 发起一个 action 到 store
      ② 创建一条 Effect 描述信息,指示 middleware 发起一个 action 到 Store
      ③ put 是异步的,不会立即发生
    2. akeEvery(actionTypes, 方法)
      ① 如果有对应type的action触发,就执行后面的方法
      ② 然后由ui组件从reducer中获取数据,并显示
  3. 运行
    ① 流程:ui组件触发action创建函数 —> action创建函数返回一个action ------> action被传入redux中间件(被 saga等中间件处理) ,产生新的action,传入reducer-------> reducer把数据传给ui组件显示 -----> mapStateToProps ------> ui组件显示
    ② 图示:
    React:Redux简介_中间件_06

  4. 使用
    ① 安装yarn add redux-saga
    ② 引入

    import {createStore,combineReducers, applyMiddleware} from 'redux';
    import userNameReducer from '../username/reducer.js';
    import createSagaMiddleware from 'redux-saga';       // 引入redux-saga中的createSagaMiddleware函数
    import rootSaga from './saga.js';                    // 引入saga.js
    				export const store = createStore(
        combineReducers({...reducerAll}),               // 合并reducer
        window.devToolsExtension ? window.devToolsExtension() : undefined,    // dev-tools
        applyMiddleware(sagaMiddleware)                 // 中间件,加载sagaMiddleware
    )
    
    sagaMiddleware.run(rootSaga)                        // 执行rootSaga
    

    ③ 界面配置

三、react-redux

  1. 简介
    ① Redux 官方提供的 React 绑定库, 具有高效且灵活的特性
    ② 把store直接集成到React应用的顶层props里面,各个子组件都能访问到顶层props
    ③ 示例

    <顶层组件 store={store}>
      <App />
    </顶层组件>
    
  2. Provider组件
    ① 使用

    1. 一般我们都将顶层组件包裹在Provider组件之中
    2. 这样的话,所有组件就都可以在react-redux的控制之下了
    3. 但是store必须作为参数放到Provider组件中去

    ② 代码

    <Provider store = {store}>
        <App />
    <Provider>
    

    React:Redux简介_C_07

    这个组件的目的是让所有组件都能够访问到Redux中的数据

  3. connect
    ① 使用

    connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    

    ② 剖解

    1. mapStateToProps:把state映射到props中去 ,其实也就是把Redux中的数据映射到React中的props中去
    2. mapDispatchToProps:把各种dispatch也变成了props让你可以直接使用
      React:Redux简介_中间件_08