Redux状态管理

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-26

安装

npm i redux -S #是一个类似于vuex的状态托管插件库

使用

import { createStore } from 'redux'

// 1.定义reducer 相当于mutation 
// 必须是同步的 因为必须要有return 一个新的state 不是修改state

// 数组新增
// return [
//   ...state,
//   action.payload
// ]

// 数组删除
// return [
//   ...state.slice(0,action.index),
//   ...state.slice(action.index+1)
// ]

// 切换某一项
// return [
//   ...state.slice(0, action.index),
//   {
//     ...state[action.index],
//     isFlag: !state[action.index].isFlag,
//   }
//   ...state.slice(action.index+1)
// ]

// store 里面只存了一个数据就是0
function reducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state; // 这一行必不可少 因为初始化时,switch语句就执行的是default 返回初始值
  }
}

// 2.创建容器 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(reducer);

// 3.使用状态
class App extends React.Component {
  render(){
    return (
      <div>
        获取容器中的值{store.getState()}
        <button onClick={this.add.bind(this)}>增加</button> 
        <button onClick={this.sub}>减少</button>
        {/* 如果函数的调用没有使用当前组件中的数据可以不bind*/}
      </div>
    )
  }

  add(){
    store.dispatch({type:'INCREMENT'})
    // 此时不会立即刷新视图

    // 1.如果想刷新可以直接调用forceUpdate
    // this.forceUpdate()

    // 也可以使用下面的subscribe 监听状态
  }

  sub() {
    store.dispatch({type:'DECREMENT'})
  }
}

ReactDOM.render(<App/>. document.getElementById('app'))

// 监听变化 重新渲染
store.subscribe(() =>
  console.log(store.getState())
  // 重新调用ReactDOM.render() render重新渲染就会更新视图
  ReactDOM.render(<App/>.document.getElementById('app'))
);

// store.subscribe 会返回一个函数 调用一下这个函数就可以移除监听
// var unSub = store.subscribe(() =>{});
// unSub()

规范 设计Action Creator

import { createStore } from 'redux'

function reducer(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + action.num;
  case 'DECREMENT':
    return state - action.num;
  default:
    return state; 
  }
}

// 抽离action对象 别的组件也可以方便调用 方便灵活传参

// 设计Action Creator
function addCount(num){
  return {
    type: 'INCREMENT',
    num
  }
}
// 简写
const subCount = (num) => ({ type:'DECREMENT', num });

let store = createStore(reducer);

// 3.使用状态
class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      num: 1
    }
  }

  render() {
    return (
      <div>
        获取容器中的值{store.getState()}
        <input type="text" value={this.state.num} onChange={(e) => this.setState({num: +e.target.value})}/>
        <button onClick={this.add.bind(this)}>增加</button> 
        <button onClick={this.sub.bind(this)}>减少</button>
      </div>
    )
  }

  add(){
    store.dispatch(addCount(this.state.num))
  }
  sub(){
    store.dispatch(subCount(this.state.num))
  }
}

ReactDOM.render(<App/>, document.getElementById('app'))

// 状态发生变化 就会触发
store.subscribe(() =>
  ReactDOM.render(<App/>, document.getElementById('app'))
);

多个状态 合并reducer

import  { createStore, combineReducers } from 'redux'

// 1.定义count的reducer  相当于 vuex 中的{state:{count:0}}
function count(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.num;
    case 'DECREMENT':
      return state - action.num;
    default:
      return state;
  }
}

// 2.定义flag的reducer  相当于 vuex 中的{state:{count:0,flag:false}}
function flag(state = false, action) {
  switch (action.type) {
    case 'toggle':
      return !state
    default:
      return state; 
  }
}

// 3.使用combine合并
const reducers = combineReducers({
  count, flag
})

let store = createStore(reducers);

// 一般在reducer里面设置默认值
// 多个的时候也可以在这里设置默认值,不建议使用 
// let store = createStore(reducers, {
//     count:0,
//     flag:false
// });

class App extends React.Component {
  render(){
    return (
      <div>
        test {store.getState().count}
        <hr/>
        <p> {store.getState().flag} </p>
      </div>
    )
  }
}

ReactDOM.render(<App/>, document.getElementById('app'))

// 状态发生变化 就会触发
store.subscribe(() =>
  ReactDOM.render(<App/>, document.getElementById('app'))
);

使用react-redux来优化刷新视图

安装:npm i react-redux -S

是连接React和Redux的桥梁,所以要先引入这两个,在引入react-redux

API

  • Provider: 用于挂载store对象的根组件
  • Connect:将store中的状态和方法给所有组件去访问。是一个高阶组件的思想,共享数据加强组件。

使用

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'

import { Provider, Connect} from 'react-redux'

function count(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.num;
    case 'DECREMENT':
      return state - action.num;
    default:
      return state;
  }
}

const reducers = combineReducers({
  count
})

let store = createStore(reducers);

// 组件要取store中的数据 或者修改其中的数据 都需要拆分组件
// 拆分为UI组件和容器组件,因为要通过Connect来加强组件 才能共享状态
// UI组件负责视图,而容器组件就是通过Connect加强后的组件

class UIComponent extends React.Component {
  render(){
    return (
      <div>
        使用共享的状态{this.props.count}
        <hr/>
        使用App传来的数据{this.props.appData}
        <hr/>
        <button onClick={this.props.add}>增加</button>
        <button onClick={this.props.show}>show</button>
      </div>
    )
  }
}
// 这个函数作用:给UIComponent共享哪些状态 
// 将 store中的state 映射为UIComponent中的props
// 如上 在UIComponent中使用props调用
function mapStateToProps(state, parentProps){
  // 第一个参数就是state == store.getState
  // 第二个参数就是StoreComponent的props对象 用于App传值给UIComponent
  return {   
    count: state.count,
    appData: parentProps.appData
  }
}

// 这个函数作用:给UIComponent共享哪些操作状态的函数
// 将store中的dispatch映射为UIComponent中的props
function mapDispatchToProps(dispatch, parentProps){
  // 第一个参数就是dispatch == store.dispatch
  // 第二个参数也一样
  return {
    add = () => dispatch('INCREMENT'),
    show: parentProps.show
  }
  // 调用this.props.add 就会分发dispatch('INCREMENT')
}

// Connect 接受2个参数 都需要是函数,返回一个函数,这个函数去加强UIComponent
const StoreComponent = Connect(mapStateToProps, mapDispatchToProps)(UIComponent)

// StoreComponent就是加强后的容器组件挂载后,共享状态的时候就会自动更新了,
// 而不在需要监听store.subcribe

class App extends React.Component {
  render(){
    return (
      <Provider store={store}>
        test {store.getState().count}
        <hr/>
        <StoreComponent appData="我是app" show={this.show} />
      </Provider>
    )
  }

  show = ()=>{
    console.log('我是app中的show方法')
  }
}

ReactDOM.render(<App/>, document.getElementById('app'))

中间件

如果状态的修改是异步操作,reducer只能做同步的(同mutation),就需要定义一个函数,去完成异步操作然后去dispatch,去触发reducer改变更新数据。

问题在于这个函数怎么才能去调用store.dispatch? vuex里面actions里面直接能访问store,而react呢

这一个异步操作就需要一个中间件来完成,类似于vuex里面的actions。

vuex:组件 store.dispatch actions里面的函数 => actions里面的异步操作函数 commit mutations里面的函数 => 改变更新数据

redux:组件 store.dispatch => 中间件拦截住 然后完成异步操作之后再dispatch触发reducer => 改变更新数据

import { applyMiddleware, createStore } from 'redux'

function count(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

function fn1(){

}

function fn2(){
    
}

let store = createStore(count, 100, applyMiddleware(fn1, fn2));
// 第一个参数:reducer
// 第二个参数可选:reducer的默认值
// 第三个参数可选:一个回调函数,参数是createStore函数本身,用于处理中间件返回的值

class App extends React.Component {
  render() {
    return (
      <div>
        获取容器中的值{store.getState()}
        <button onClick={this.add}>增加</button> 
        <button onClick={this.sub}>减少</button>
      </div>
    )
  }

  add(){
    store.dispatch({type: 'INCREMENT'})
  }

  sub(){
    store.dispatch({type: 'DECREMENT'})
  }
}

ReactDOM.render(<App/>, document.getElementById('app'))

// 状态发生变化 就会触发
store.subscribe(() =>
  ReactDOM.render(<App/>, document.getElementById('app'))
);

理解

function fn1(){

}

function fn2(){
    
}

let store = createStore(count, 100, Redux.applyMiddleware(fn1, fn2));

Redux.applyMiddleware(fn1,fn2) 就会返回一个这样的函数 function(createStore){}

这个函数(function(createStore){})肯定是可以访问到fn1 和 fn2,主要的是这个函数什么情况下调用 fn1 和 fn2 又能做什么?

function(createStore){} 这个函数参数是createStore,就可以在这个函数创建一个初始store,然后经过一些处理,返回新的store容器

代码如下:

const applyMiddleware = (...middlewares) => {
  return function(createStore) {
    /* 返回一个函数签名跟 createStore 一模一样的函数,亦即返回的是一个增强版的 createStore */
    return function(reducer, preloadedState, enhancer) {

      // 用原 createStore 先生成一个 store,其包含 getState / dispatch / subscribe / replaceReducer 四个 API
      var store = createStore(reducer, preloadedState, enhancer)
      
      var dispatch = store.dispatch // 指向原 dispatch
      var chain = [] // 存储中间件的数组

      // 提供给中间件的 API(其实都是 store 的 API)
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      }
      
      // 给中间件“装上” API,即fn1 和 fn2 就可以访问到getState 和 dispatch 了
      // fn1 fn2 就是 ...middlewares 去接受的 
      chain = middlewares.map(middleware => middleware(middlewareAPI))

      // chain = [fn1(store),fn2(store)] 这里的store是简易版的
      
      // 串联所有中间件 所以 chain存储的是 fn1 和 fn2 这两个中间件的返回值
      // (这两个中间件也一定返回的是一个函数)
      // 然后会把dispatch先传给fn2中间调用 
      // 然后将调用后返回的值 在交给fn1中间件调用 
      dispatch = compose(...chain)(store.dispatch)
      // compose(f, g, h)(action) => f(g(h(action)))
      // 例如,chain 为 [M1, M2],而 compose 是从右到左进行“包裹”的
      // 最后,我们得到串联后的中间件链:(M1(M2(store.dispatch)))
      // 返回一个最新的dispatch函数

      // 返回新的容器对象
      return {
        ...store, // store 的 API 中保留 getState / subsribe / replaceReducer
        dispatch  // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
      }
    }
  } 
}

所以真正的fn1 和 fn2 就应该这么来用

function fn2(store){
  // 返回一个函数 会在上面compose的时候被调用 且会传进来一个dispatch
  return (dispatch)=>{
    // dispatch 返回的函数(第三层函数) 才是真正store.dispatch需要调用的函数

    // 组件中 store.dipatch(addCount(num)) 
    // addCount(num) == 就是上面说的 Action Creator {type:xxx,payload:xxx}  == 也就是这里的action

    return (action) => {
      return dispatch(action) // 将{type:xxx,payload:xxx} 最终交给 dispatch去分发 
      // dispatch(action)  就会触发 fn1 中第三层(最里层函数)函数的调用
    }
  }
}

function fn1(store) {
  return (dispatch)=>{
    return (action) => { // 这个函数会被上面fn2最后的dispatch(action)触发
      // 如果fn1之前还有中间件 就 return dispatch(action) 没有就直接触发 dispatch(action)
      dispatch(action) 
      // fn1 左边没有中间件了 那么这个dispatch就是最终的的dispatch 就会触发reducer 然后改变更新数据
      // 上面那个不会触发reducer  因为它之前(左边)还有中间件

      // 这样 我们就可以在这个 dispatch 和 render数据更新 之间做我们想做的事 
      // 所以可以做一个 logger 如下
    }

    // return (action)=>{
    //   console.log(action.type + '触发前',store.getState())
    //   dispatch(action)
    //   console.log(action.type + '触发后',store.getState())
    // }
  }
}

let store = createStore(count, 100, Redux.applyMiddleware(fn1, fn2));

异步请求 更新状态

import { applyMiddleware, createStore } from 'redux'

function count(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - action.num;
    default:
      return state;
  }
}

const myThunk = (store) => (dispatch) => (action)=>{
  typeof action == 'function' ? action(dispatch) : dispatch(action)
}

let store = createStore(count, 100, applyMiddleware(myThunk));

class App extends React.Component {
  render() {
    return (
      <div>
        获取容器中的值{store.getState()}
        <button onClick={this.add}>增加</button> 
        <button onClick={this.sub}>模拟异步减num</button>
      </div>
    )
  }

  add(){
    store.dispatch({type:'INCREMENT'})
  }

  sub(){
    store.dispatch(this.asyncGetData(1)) 
  }

  asyncGetData(num){ 
    return function(dispatch){
      setTimeout(()=>{
        dispatch({
          type:'DECREMENT',
          num
        })
      }, 100)
    } 
    // 分发一个函数 之前都是分发一个对象

    // return的这个函数 被store.dispatch调用了 就会被myThunk的第三层拦截
    // 通过验证 这次的action是一个函数 就会把dispatch 传给这个函数action并调用了== action(dispatch)
    // 就会执行这个 setTimeout, 这个setTimeout就会在1000毫秒之后,再去dispatch 这个对象action
    // 然后又会被myThunk 拦截,这次验证发现是一个对象 ,就会dispatch这个action去触发reducer的更新

    // 如果 myThunk之间还有中间件, 那么最后验证是一个对象的时候就应该是return dispatch(action) 而不是直接调用了
  }
}

ReactDOM.render(<App/>, document.getElementById('app'))

// 状态发生变化 就会触发
store.subscribe(() =>
  ReactDOM.render(<App/>, document.getElementById('app'))
);