Redux、React-Redux 及Redux中间件

Redux、React-Redux 及Redux中间件

一、Redux、React-Redux 分别是什么,有什么联系?

①、Redux 是 JavaScript 状态容器,提供可预测化的状态管理(保证程序行为一致性且易于测试)。由Flux演变而来。

Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。

②、React-Redux则是Redux提供的React绑定库,(有点类似Vuex专门服务Vue)需要另外安装依赖。

二、Redux

1)   三大原则

单一数据源,state只读,使用纯函数来执行修改是Redux的三大原则。

  • 单一数据源指的是,整个应用的state被存储在唯一的一个store中。
  • state只读指的是,唯一改变state的方法就是触发action。
  • 使用纯函数来执行修改指的是,编写纯函数reducers来描述action如何改变state。      

那什么是reducer?

         reducer是一个纯函数,接收旧的state及action,返回新的state.

而reducer为什么必须是一个纯函数?

因为redux的核心是提供可预测化的状态管理,即无论何时特定的action触发的行为永远保持一致。如果是非纯函数则无法保证可预测性。

保证reducer是一个纯函数需要满足如下条件:

  • 不得修改传入的参数
  • 不得调用非纯函数,如Date.now(),Math.random()等
  • 不得执行有副作用的操作,如API请求和路由跳转。

API请求该如何执行?

上面说到,reducer中不能执行api请求操作,那应该如何做呢?有以下两个方法

  • 在dispatch方法之前执行api请求。先进行api请求,收到结果后再dispatch不同的action
  • 选用redux-thunk,redux-saga,redux-promise等中间件,使得dispatch函数中可以直接执行异步操作。

2)  安装及使用

直接上代码,边看边说吧

npm install --save redux

store.js

import { createStore,combineReducers} from 'redux'

// 初始化reducer
const caclReducer = (state = 0, { type, payload = 1 }) => {   
   switch (type) {
     case 'ADD':
        return state + payload
     case 'MINUS':
        return state - payload   
     default:
       return state;
   }
}

// 初始化reducer
const todoReducers = (state = [], action) => { 
  switch (action.type) { 
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          complete:false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => { 
        if (index === action.index) {
          return Object.assign({}, todo, {complete:true})
        }
        return todo
      })
    default:
      return state
  }
}

// 使用combineReducers组合多个reducer
let reducers = combineReducers({ caclReducer, todoReducers })

// 创建store
const store = createStore(reducers)

export default store

视图 index.js

import React, { PureComponent } from 'react'; 
import store from '../store';
class Demo01 extends PureComponent {
 
  constructor(props) {
    super(props);
    this.state = {
      todoContent: ""     
    };
  }
  componentDidMount() {   
    // 视图subscribe订阅
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }
  componentWillUnmount() {
    // 取消订阅
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  // dispatch提交更新
  add = () => {
    store.dispatch({ type: 'ADD', payload: 200 });
  };
  minus = () => {
    store.dispatch({ type: 'MINUS', payload: 100 });
  };
 
  getInputVal = e => {   
    this.setState({
      todoContent:e.target.value
    })
  };
  addTodo = () => {
    const { todoContent} = this.state
    store.dispatch({ type: 'ADD_TODO', text: todoContent })
    this.setState({todoContent:""})
  }
  markTodo (index) {    
    store.dispatch({type:'COMPLETE_TODO',index})
  }
  render() {
    return (
      <div>
        <h3>redux示例</h3>
        {/* getState()获取store中状态值 */}
        <div>当前数值:{store.getState().caclReducer}</div>
        <button onClick={this.add}>+200</button>
        <button onClick={this.minus}>-100</button>
      
        <hr />
        <h3>todo list</h3>
        <input type="text" onChange={this.getInputVal} value={this.state.todoContent}/>
        <button onClick={this.addTodo}>添加</button>
        <ul>
          {
            store.getState().todoReducers.map((item, index) => {               
                return !item.complete?<li key={index}>
                    <span>{item.text}</span>
                    <button onClick={() => { this.markTodo(index) }}>标记</button>
                  </li>:undefined                                  
            })
          }          
        </ul>       
      </div>
    );
  }
}

export default Demo01;

3)  redux使用要点总结

  • 通过createStore创建唯一store。
  • store里的reducer初始化state,并定义state修改规则
  • combineReducers可以连接多个reducer
  • 通过dispatch action来提交对数据的修改
  • action提交到reducer函数里,根据传入的action type,返回新的state
  • 通过store.getState()获取状态
  • 视图通过subscribe订阅,更新state到视图层

4)遗留问题:每次都要重新调用render、store.getState()。太不方便啦!!为此,react-redux应运而生。这个部分,稍后第四部分重点讲。

我们先来聊聊redux中间件。

三、Redux中间件

1)为什么要使用中间件?

Redux只是个纯粹的状态管理器,默认只支持同步。

如果需要实现异步任务,如延时,网络请求等,就需要中间件的支持。

2)什么是redux中间件?

中间件是一个函数,对store.dispacth方法进行了升级。在发出Action和执行Reducer两步之间,添加了新的功能。

若传递的action是一个对象,dispatch直接传给store

若传递的action是一个函数,dispatch通过中间件自动执行函数,依据函数的执行情况判断是否直接传递给store。

3)如何使用中间件? 

下面例举redux-thunk,redux-logger,redux-promise这三个常用中间件的使用方法

(还有个redux-saga中间件,我另外开个专题讲)

1)安装

npm install --save redux-thunk 
npm install --save redux-logger
npm install --save redux-promise

2)store.js

//  1、引入applyMiddleware
import {createStore,applyMiddleware,combineReducers} from 'redux'
//  2、引入相关中间件
import thunk from 'redux-thunk' // 异步解决方案
import logger from 'redux-logger' // 打印日志
import promise from 'redux-promise' // 处理promise


// 初始化reducer
const caclReducer = (state = 0, { type, payload = 1 }) => {   
   switch (type) {
     case 'ADD':
        return state + payload
     case 'MINUS':
        return state - payload   
     default:
       return state;
   }
}

const todoReducers = (state = [], action) => { 
  switch (action.type) { 
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          complete:false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => { 
        if (index === action.index) {
          return Object.assign({}, todo, {complete:true})
        }
        return todo
      })
    default:
      return state
  }
}

// 组合reducer
let reducers = combineReducers({ caclReducer, todoReducers })

// 3、创建store,第一个参数是reducers,第二个参数是中间件
const store = createStore(
  reducers,
  applyMiddleware(thunk,promise,logger)
)
 
export default store

3)view.js

import React, { PureComponent } from 'react'; 
import store from '../store/index1';
class Demo01 extends PureComponent {  
  constructor(props) {
    super(props);
    this.state = {
       
    };
  }
  componentDidMount() {   
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }
  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }
  // dispatch提交更新
  add = () => {
    store.dispatch({ type: 'ADD', payload: 200 });
  };
  minus = () => {
    store.dispatch({ type: 'MINUS', payload: 100 });
  };
  // 4、模拟提交异步操作
  asyncAdd = () => {
    store.dispatch((dispatch, getState) => {
      setTimeout(() => {
        dispatch({ type: 'ADD', payload: 300 });
      }, 2000);
    });
  };
  // 5、模拟promise
  handlePromise = () => { 
    store.dispatch(
      Promise.resolve({
        type: 'MINUS',
        payload:5
      })
    )
  }

  render() {
    return (
      <div>
        <h3>redux示例</h3>
        {/* getState()获取store中状态值 */}
        <div>当前数值:{store.getState().caclReducer}</div>
        <button onClick={this.add}>+200</button>
        <button onClick={this.minus}>-100</button>
        <button onClick={this.asyncAdd}>异步操作</button>
        <button onClick={this.handlePromise}>处理promise</button>
         
      </div>
    );
  }
}

export default Demo01;

四、React-Redux

前面说到,单纯靠redux实现步骤过于繁琐。redux作者给react量身定做了一套React-Redux。

1)安装

npm install --save react-redux

2)使用

   index.js

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

ReactDOM.render(
  <React.StrictMode>
{/*1、使用Provider为后代组件提供store*/}
    <Provider store={store}>
      <App />
   </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

  store.js

import { createStore } from 'redux'

const caclReducer = (state = 0, { type, payload = 1 }) => {   
  switch (type) {
    case 'ADD':
       return state + payload
    case 'MINUS':
       return state - payload   
    default:
      return state;
  }
}

// 创建store,传入reducer
const store = createStore(caclReducer)

export default store

  view.js

import React, { Component } from 'react'
import { connect } from 'react-redux'

// connect是一个高阶组件,用于连接React组件和Redux store.
// 返回一个新的已与Redux store连接的组件类
class ReactReduxClassView extends Component {
  constructor(props) {
    super(props)  
    this.state = {
       
    }
  }
  
  render () {  
    const { num,add,minus} = this.props
    return (
      <div>
        <h3>React-redux Class组件示例 Caculator</h3>
        <p>当前数值是:{num}</p>
        <button onClick={add}>+200</button>
        <button onClick={minus}>-100</button>
      </div>
    )
  }
}
// 将需要的state的节点注入到与此视图数据相关的组件(props)上
const mapStateToProps = state => {
  return {
    num:state
  }
}
// 将需要绑定的响应事件注入到组件上(props上)
const mapDispatchToProps = {
  add: () => { 
    return {type:'ADD',payload:200}
  },
  minus: () => { 
    return {type:'MINUS',payload:100}
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxClassView)

上面的写法还是有点复杂,有个更高级的装饰器可以使用,我们来看下

1)用create-react-app创建的项目,先npm run eject后,在package.json里配置以下babel 选项

     同时,在项目根目录下新建 jsconfig.json,添加图2内容,以消除警告

  "babel": {
    "presets": [ "react-app" ],
    "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ]
  }
{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

2)改写view.jsx

import React, { Component } from 'react'
import { connect } from 'react-redux'

// 第一个参数就是mapStateToProps,第二个参数是mapDispatchToProps(可以是object,也可以是function)
@connect(
  (({ cacl }) => ({ num: cacl })),
  {
    add: () => ({ 
       type:'ADD',payload:200
    }),
    minus: () => ({ 
       type:'MINUS',payload:100
    })
  }
)
class ReactReduxClassView extends Component {
  constructor(props) {
    super(props)  
    this.state = {
       
    }
  }
  
  render () {    
    const { num,add,minus} = this.props
    return (
      <div>
        <h3>React-redux Class组件示例 Caculator</h3>
        <p>当前数值是:{num}</p>
        <button onClick={add}>+200</button>
        <button onClick={minus}>-100</button>
      </div>
    )
  }
}

export default ReactReduxClassView

上面connect第二个参数也可以是一个function,也可以改成下面写法

@connect(
  (({ cacl }) => ({ num: cacl })),  
  dispatch => {
    const add = () => dispatch({ type: 'ADD', payload: 200 })
    const minus = () => dispatch({type:'MINUS',payload:100})
    return {dispatch,add,minus}
  }
)

但是方法多了,return要一个个添加也显得很冗余

import {bindActionCreators } from 'redux'

// 第一个参数就是mapStateToProps,第二个参数是mapDispatchToProps(object | function)
@connect(
  (({ cacl }) => ({ num: cacl })),
 
  dispatch => {
    let creators = {
      add: () => ({ 
             type:'ADD',payload:200
      }),
      minus: () => ({ 
             type:'MINUS',payload:100
      })
    }
    creators = bindActionCreators(creators,dispatch)
    return {dispatch,...creators}
  }
)

(我怎么觉得还是object返回最优呢)

随着React 16.8版本增加了hooks,React-Redux也“与时俱进”添加了新的hooks,看下hooks又是怎么写的。

import React from 'react'
import { useSelector,useDispatch} from 'react-redux'

const ReactReduxHookView = (props) => { 
 
  const num = useSelector(state => state.cacl)
 
  const dispatch = useDispatch()
 
  const add = () => { 
    dispatch({type:'ADD',payload:200})
  }
  const minus = () => { 
    dispatch({type:'MIMUS',payload:100})
  }
  
  return <div>
    <h3>React-Redux hook</h3>
    <p>当前数值是{num}</p>
    <button onClick={add}>+200</button>
    <button onClick={minus}>-100</button>
  </div>
}

export default ReactReduxHookView

hooks真的是很简洁了

总结下React-Redux的使用要点

  • Provider、Connect两个核心api。其中Provider用于为后代组件提供store。connect则为组件提供数据和变更方法。正常情况下,根组件嵌套在<Provider>中才能使用connect方法。
  • connect是一个高阶组件,用于连接React组件和Redux store,返回一个已与redux建立连接的组件类。
  • connect的第一个参数mapStateToProps 是一个函数,用于建立组件和store.state的映射关系。
  • connect的第二个参数mapDispatchToProps可以是object,也可以是function,用于建立组件和store.dispatch的映射关系。
  • 函数组件中则可以使用useSelector,和useDispatch这两个hooks.

五、Redux数据流

view-->action-->reducer-->store-->view

view -> action -> middleware -> reducer -> store

  • 首先,用户通过View发出Action,发出方式就用到了dispatch方法
  • 如果使用了中间件,可以在action发起之后,到达reducer之前执行一些副作用操作,例如api请求。
  • 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
  • State—旦有变化,Store就会调用监听函数,来更新View

六、Redux,React-Redux实现原理及源码解读

(待补充)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值