React状态管理篇之redux

145 阅读7分钟

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的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面(唯一数据源)。

注意: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)

2cc5fa527e609acf86c956087393e2f.png

安装

安装命令: cnpm install redux

创建一个 react 项目,然后安装 redux

删除 src 中的所有文件,保留 index.js

创建一个 store 文件夹,然后再该文件夹下新建一个 index.js

创建一个 App.jsx 文件

使用 redux

redux 接入

  1. 先安装 cnpm install redux

  2. 在项目的 src 中先创建 store/index.js

  3. 在index.js 中导入 创建状态的方法 import { legacy_createStore as createStore } from 'redux'

  4. 定义一个 reducer ,是一个纯函数,该函数有两个参数

    参数一 就是 state,该 state 就是全局状态中的数据

    参数二 action 中有两个属性,第一个 type 标记我们要做的事情, payload 就是参数

  5. 创建 store 对象 const store = createStore(reducer)

  6. 将 store 暴露出去 export default store

  7. 在入口文件 index.js 中导入 store

  8. 使用 store 订阅状态管理器改变是执行的操作

  9. 在组件中导入 store,通过 store.getState 获取全局状态中的数据

  10. 可以在 store/index.js 文件中,判断 type 是什么来决定做什么事情

  11. 在组件中通过 store.dispatch({type: 'add'}) 触发改变

  12. 需要传递参数的时候可以使用 store.dispatch({type: 'addNum', payload: 10})

redux-分模块

  1. 将原来的 reducer 提取到一个单独的文件中 store/modules/app.js
  2. 在 app.js 文件中将 reducer 导出
  3. 在 store/index.js 中导入 app 状态模块
  4. 在 store/modules/pro.js 中创建 reducer 是我们第二个状态模块
  5. 将 pro.js 中的 reducer 导出
  6. 在 store/index.js 中导入 pro状态模块
  7. 在 store/index.js 导入 combineReducers 方法
  8. 通过 combineReducers 将 app 模块和 pro 模块合并成一个 reducer
  9. {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> );
  1. 在组件中先解构出 import { connect } from 'react-redux'

  2. 使用 connect ,connect 接收两个参数

    第一个:是我们当做组件所需要的状态

    第二个:是我们组件所需要修改全局状态的方法

    connect 的返回值是一个高阶组件

    redux-异步

    redux-thunk

    使用步骤

    1. 安装: 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

使用步骤

  1. 安装 cnpm i redux-saga
  2. 创建 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}})
    }
  }
}