初探Redux

100 阅读8分钟

1.什么是Redux

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理

  • JavaScript开发的应用程序,状态变得难以管理,包括:

    1. 需要管理的状态越来越多,越来越复杂
    2. 服务器返回的数据,缓存数据,用户操作的数据,UI状态的数据等等
  • 管理不断变化的状态是十分困难的:

    1. 状态之间存在相互依赖,状态同样也受视图的影响
    2. 对于状态的改变原因和方式难以追踪
  • React负责视图帮助解决DOM的渲染过程,但是state依然需要我们自己管理

    1. 通信可以通过props,Context等方式
  • Redux就是一个帮助管理state的容器

2.工作流程

3. Redux的三个核心概念

3.1 action

- action 就是一个普通 JavaScript 对象,用来描述更新的类型和内容
- 包括两个属性:
    1. type: 标识属性,值是一个字符串,用于标识需要使用的action,唯一值,必传属性
    2. data: 数据属性, 值可以使任意数据类型,用来传递数据给action操作,可选属性
-   例子:
{ type: 'CHNAGE_COUNT', data: { count: 1 } }
- Redux要求通过action来更新数据
    1. 所有数据的变化必须通过dispatch派发action来更新

3.2 reducer

- reducer就是一个纯函数,负责将state和action联系在一起
- reducer就是将传入的state和action结合起来生成新的state

3.3 Store

- 将state、action、reducer联系在一起的对象
- Store的功能:
    1. 可以通过getState()得到state
    2. dispatch(action): 分发action, 触发reducer调用, 产生新的state
    3. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

4.redux的三大原则

  • 单一数据源

    1. 整个应用程序的state被存储在一颗object tree中,并且这个object tree值存储在一个store中
    2. redux并没有强制不能创建多个store,但是多个store不利于数据的维护
    3. 单一的数据源可以让整个程序的state变得方便维护,追踪,修改
  • State是只读的

    1. 唯一修改state的方式一定是触发action,不要试图通过其他方式修改state
    2. 这样可以确保View或网络请求都不能直接修改state,只能通过action来描述如何修改state
    3. 这样可以保证所有的修改都被集中处理,并且按照严格的顺序来执行,所以可以不用担心race condition(竞态)的问题
  • 使用纯函数来执行修改

    1. 通过reducer将旧的state和action联系在一起,并且返回一个新的state
    2. 随着应用程序的复杂程度,我们可以将reducer拆分成多个小的reducer,分别操作不同的state 部分: 7.4 combineReducers
    3. 所有的reducer都应该是纯函数,不能产生任何副作用

5.示例一

  • redux的单独例子(不结合react等框架)
mkdir just-redux

cd just-redux

npm init -y

npm install redux

touch index.js
# index.js

// 导入redux (由于直接使用node运行,不能直接使用ESModule方式引入)
/* commonJS引入 */
// const Redux = require('redux') 

/* ESModule引入 */
import Redux from 'redux'
// node v13.2.0之前 package.json 加入 type: 'module' 执行命令 node --experimental-modules ./index.js
// node v13.2.0开始 package.json 加入 type: 'module' 执行命令 node index.js

const initialState = {
  counter: 0
}

// reducer
function reducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT': 
      return {...state, counter: state.counter + 1 }
    case 'DECREMENT':
      return {...state, counter: state.counter - 1 }
    case 'ADD_COUNTER':
      return {...state, counter: state.counter + action.num }
    case 'SUB_COUNTER':
      return {...state, counter: state.counter - action.num }
    default:
      return state
  }
}

// store(创建时传入reducer)
const store = Redux.createStore(reducer)

// 订阅store(应在派发前订阅)
store.subscribe(() => {
  console.log('state发生了变化', store.getState())
})

// actions
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'DECREMENT' }
const action3 = { type: 'ADD_COUNTER', num: 5 }
const action4 = { type: 'SUB_COUNTER', num: 10 }

// 派发dispatch
store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)

通过 node运行

node index.js

6.示例二(结构划分)

# 文件结构

- node_modules
- store
  - actionCreators.js
  - constants
  - index.js
  - reducer.js
- index.js
- package-lock.json
- package.json
  • actionCreators: 定义action
# store/actionCreators.js

import { ADD_COUNTER, SUB_COUNTER, INCREMENT, DECREMENT } from './constants.js'

export const addAction = num => ({ type: ADD_COUNTER, num })

export const subAction = num => ({ type: SUB_COUNTER, num })

export const add = () => ({ type: INCREMENT })

export const sub = () => ({ type: DECREMENT })
  • constants.js: 统一变量文件
# store/constants.js

export const ADD_COUNTER = 'ADD_COUNTER'
export const SUB_COUNTER = 'SUB_COUNTER'
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
  • store入口文件
# store/index.js

import redux from 'redux'
import Reducer from './reducer.js'

const store = redux.createStore(Reducer)

export default store
  • reducer.js: 定义reducer
#  store/reducer.js

import { ADD_COUNTER, SUB_COUNTER, INCREMENT, DECREMENT } from './constants.js'

const initialState = {
  counter: 0
}

function reducer(state = initialState, action) {
  switch(action.type) {
    case ADD_COUNTER:
      return { ...state, counter: state.counter + action.num }
    case SUB_COUNTER:
      return { ...state, counter: state.counter - action.num }
    case INCREMENT: 
      return { ...state, counter: state.counter + 1 }
    case DECREMENT:
      return { ...state, counter: state.counter - 1 }
    default:
      return state
  }
}

export default reducer
  • index.js执行入口文件
# index.js

import Store from './store/index.js'
import { addAction, subAction, add, sub } from './store/actionCreators.js'


Store.subscribe(() => {
  console.log('state发生了变化:', Store.getState())
})

Store.dispatch(addAction(10))
Store.dispatch(subAction(5))
Store.dispatch(add())
Store.dispatch(sub())
  • package.json: 配置运行命令
# package.json

"scripts": {
    "start": "node ./index.js"
}

7.Redux的核心API

7.1 createstore

  • 作用:创建包含指定reducer的store对象

7.2 store

  • 作用: redux库最核心的管理对象

  • 在其内部维护着state和reducer

  • 核心方法

    1. getState(): 获取state
    2. dispatch(action):派发action
    3. subscribe(listener):订阅store,返回取消订阅的函数

7.3 applyMiddleware

  • 作用:应用上基于redux的中间件(插件库)
import { createStore, applyMiddleware, compose } from 'redux'
import rootReducer from './reducer'
import thunk from 'redux-thunk'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  rootReducer, 
  composeEnhancers(applyMiddleware(thunk)) // 使用react-thunk
)


export default store

7.4 combineReducers

  • 作用:合并多个reducer函数
import { combineReducers } from 'redux'
// import {combineReducers} from 'redux-immutable' 从这里取  state 是一个immutable对象
import recommendReducer from '@/pages/discover/list/recommend/store/reducer'
import playerReducer from '@/pages/player/store/reducer'

const rootReducer = combineReducers({ // 组合多个reducer
  recommend: recommendReducer,
  player: playerReducer
})

export default rootReducer

7.5 compose

  • 作用:从右到左来组合多个函数,返回合成后的最终函数
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers'

const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    DevTools.instrument()
  )
)

7.6 bindActionCreators

  • 作用:把一个 value 为不同 action creator的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们: bindActionCreators链接

7.7 Redux异步编程问题

  • redux默认是不能进行异步处理的,
  • 某些时候应用中需要在redux中执行异步任务(ajax, 定时器),可以使用异步中间件(redux-thunk、redux-saga) 后续章节详细说明:9. Redux异步编程

8. Redux 与React的结合(react-redux)

8.1 react-redux

  • react-redux 是React 的官方 Redux UI 绑定库,它可以使得react组件能够操作redux的store中的数据
  • 简化redux的使用方式,不需要手动订阅更新,你需要在订阅内部获取当前store状态,订阅store、检查更新数据和触发重新渲染的过程可以变得更加通用和可重用。像 React Redux 这样的 UI 绑定库处理商店交互逻辑,因此不必自己编写这些代码
  • 安装方式
# npm
npm install --save react-redux

# yarn
yarn add react-redux
  • 基本使用流程

  • react-redux将组件分成UI组件和容器组件
  1. UI组件:通过React构建的视图组件
  2. 容器组件:react-redux提供用于连接reudx和UI组件的组件,负责管理数据和业务逻辑
  • 目录
# 项目目录结构

- node_modules
- src
  - Home
    - index.js
  - store
    - actionCreators.js
    - constants
    - index.js
    - reducer.js
- App.js
- index.js
- package-lock.json
- package.json
  • store文件部分代码 — 6.示例二(结构划分)
# index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import './index.css';
import App from './App';
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
# App.js

import React from 'react'
import Home from './Home'

function App() {
  return ( <Home /> )
}


export default App;
# Home/index.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { add } from '../store/actionCreators'
class Home extends Component {
  render() {
    return (
      <div style={{ margin: '10px 10px' }}>
        <h4>{ this.props.counter }</h4>
        <button onClick={this.props.addCounter}>add</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    counter: state.counter
  }
}

const mapDispatchToProps = (dispatch) => ({
  addCounter() { 
    dispatch(add()) 
  }
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)

8.2 Provider

  • react-redux提供的容器组件:能够通过Provider将redux的store与UI组件进行连接,与react-redux提供的connect一同使用
# 项目中使用方式

ReactDOM.render(
  <Provider store={store}> // store为redux中向外暴露的store
    <App />
  </Provider>,
  document.getElementById('root')
)

8.3 connect

  • react-redux 提供的方法,用于连接容器组件和UI组件
# 连接组件

connect(mapStateToProps, mapDispatchToProps)(Home)
  1. mapStateToProps

    • mapStateToProps是一个函数,用于建立一个从store中的state对象到(UI 组件的)props对象的映射关系

    • mapStateToProps(state, ownProps)可传入两个参数

      • state: 整个redux store state对象
      • ownProps:包含传递给connect生成的包装组件的props
      • 返回值:返回一个包含了组件用到的数据的纯对象
    • 一般用法

const mapStateToProps = (state) => { // state 接受store中的state
  return {
    counter: state.counter
  }
}
  1. mapDispatchToProps

    • mapDispatchToProps是connect函数的另一个参数,用来建立 UI 组件的参数到store.dispatch方法的映射,它定义了传给 Store的action, 可以是一个函数(应该返回一个对象)或一个对象

    • mapDispatchToProps(dispatch, ownProps )可传入两个参数

      • dispatch: 分发action的函数
      • ownProps :传递给连接组件的props
      • 返回值: 返回一个纯对象
    • 一般用法

# 函数形式
const mapDispatchToProps = (dispatch) => ({
  addCounter() { 
    dispatch(add()) 
  }
})


# 对象形式, react-redux会在mapDispatchToProps对象中的函数调用时进行自动分发
# 而不需要手动调用dispatch(推荐写法)
const mapDispatchToProps = {
  add
}

9. Redux异步编程

9.1 action 的同步与异步

  • 同步:当dispatch的action为一个一般对象时,action为同步的
  • 异步:当dispatch的action为一个函数时,action是异步的,异步的action中也会调用同步的action,但redux的store默认只能接受action为对象,需要借助中间件实现函数类型的action,可以在使用中间件redux-thunk

9.2 redux-thunk

  • 用于redux支持异步action和网络请求的中间件

  • redux-thunk可以让action为一个函数,也就是dispatch(action函数),该函数会被调用,并且会传给这个函数一个dispatch函数getState函数

    1. dispatch函数:用于之后在此派发action
    2. getState函数用于一些依赖于原来状态的操作,可以让我们获取到之前的一些状态
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import reducer from './store/reducer'

const store = createStore(reducer, applyMiddleware(thunk))