首页 > 技术文章 > React-Redux 实现伪代码

GongYaLei 2020-12-25 16:43 原文

前言

React 相信大家都有过接触,React 一套MVC框架,根据 React 的框架诞生的一套使用方便的 处理数据状态的插件,redux。redux 也并不是就只是单纯使用于React , Vue 同样适用,它只是解决了一种处理数据的方式。

一、Redux

  • 1.1 设计思想
    Redux 将组价中的状态,数据存储到顶层 Store 里,通过 provider 从顶层注入。
    组件通过 dispatch 触发 action, action 去覆盖之前的 reducer 中的 state 值。
    此时 store 值做到了更替,所以传入组件中的 state 值修改,页面 View 更新。



  • 1.2 三大原则

整个应用用只有一个store, 其内部state tree 存储整个应用 state。
修改数据通过 action 去派发。
单一数据源让 react 组件之间的 数据通信简单方便,更有利于管理。

  • 1.3 createStore
  • 1.3.1 store
     import { create } from 'react';
            
     function reducer() {}
     let store = createStore(reducer)

通过 creatstore 创建一个 store, 创建的store 有以下几个方法:

       * store.getState()  // 获取最新state
       * store.dispatch()  // 触发 aciton
       * store.subscribe() // 订阅 store 中 state 变化 
  • 1.3.2 reducer

reducer 是一个纯函数,作用只是接收数据,数据覆盖,其他什么也不操作,state 值替换作用。

      const reducer = (state={count: 0}, action) => {
           switch (action.type) {
                case ADD:
                     return { count: state.count + 1 }
                case MINUS: 
                     return { count: state.count - 1 }
                default: 
                     return state;
           }
      }
  • 1.3.3 getState、dispatch、subscribe
     import React from 'react'
     import store from '../store'

      class Counter extends React.Component{
          constructor(props){
                super(props)
                this.state = {
                  number: store.getState().count
          }
        }
        render () {
                return <div>
                  <p>{this.state.number}</p>
                  <button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
                  <button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>
                </div>
        }
      }

      export default Counter
      ```
可以看到,reducer 函数中已经接受到了 action, 此时 store 中的 state 已经发生了变化,而页面不更新的原因在于 Counter 没有订阅 store 中 state 的变化,可在代码中加入下面代码

```jsx
class Counter extends React.Component{
  componentDidMount () {
    this.unSubscribe = store.subscribe(() => {
      this.setState({
        number: store.getState().count
      })
    })
  }
  componentWillUnmount () {
    this.unSubscribe && this.unSubscribe()
  }
}

使用 store.subscribe 就可实现订阅,该方法接受一函数,当 store 中 state 中状态发生变化,就会执行传入的函数,同时 store.subscribe 方法返回一个函数,用于取消订阅。
至此,Counter组件已基本实现了。可能有些小伙伴发现应用首次加载后,控制台输出了

  • 1.3.4 createStore 伪代码

function createStore(reducer){
	let state
  const listeners = []
  
  // 返回最新的 state
  function getState () {
  	return state
  }
  
  // 派发 action
  function dispatch(action){
  	state = reducer(state, action)
    listeners.forEach(listener => listener())
  }
  
  // 订阅,返回取消订阅函数
  function subscribe(listener){
  	listeners.push(listener)
    return function () {
    	const index = listeners.indexOf(listener)
      listeners.splice(index, 1)
    }
  }
  
  // 获取state默认值
  dispatch({type: "@@redux/INIT1.s.m.m.c.n"})
  
  // 返回 store, 一个对象
  return {
  	getState,
    dispatch,
    subscribe
  }
}

export default createStore

  • 1.4 combineReducers
    • 1.4.1 原理及使用

      当一个应用包含多个模块,将所以模块的 state 放在并不合理,更好的做法是按照模块进行划分,每个模块有各自的 reducer、action,最终通过 Redux 中的 combineReducers 合并成一个大的 reducer.

// src\store\reducers\index.js
import {combineReducers} from 'redux';
import counter1 from './counterReducer1';
import counter2 from './counterReducer2';
export default combineReducers({
    x: counter1,
    y: counter2
});

// src/store/reducers/counterReducer1.js
import * as types from '../action-types';
export default function (state= {count: 0},action){
    switch(action.type){
        case types.ADD1:
            return state.count + 1;
        case types.MINUS1:
            return state.count - 1;
        default:
            return state;
    }
}

// src/store/reducers/counterReducer2.js
import * as types from '../action-types';
export default function (state= {count: 0},action){
    switch(action.type){
        case types.ADD2:
            return state.count + 1;
        case types.MINUS2:
            return state.count - 1;
        default:
            return state;
    }
}


  • 1.4.2 combineReducers 伪代码
function combineReducers(reducers){
  // 返回合并之后的 reducer 函数
  return function (state, action){
  	const nextState = {}
    Object.keys(reducers).forEach(key => {
    	nextState[key] = reducers[key](state[key], action)
    })
    return nextState
  }
}


  • 1.5 总结

可以看出,在 React 组件中使用 store, 都需要手动去引入 store 文件, 手动订阅 store 中状态的变化,这是不合理的,接下来,我们看下 react-redux 是如何解决的

二、React-Redux

  • 2.1 原理及使用
    react-redux 提供一个 Provider 组件,通过 Provider 组件,可以向其子组件、孙组件传递 store, 而不需要每个组件都手动引入
import { Provider } from 'react-redux'
import store from './store'

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

在后代组件 Counter1 中,可使用 react-redux 提供 connect 函数,将 store 与 Counter1 组件的 props 进行关联

import React from 'react'
import { connect } from 'react-redux'
import action from '../store/actions/Counter1'

class Counter1 extends React.Component{
  render () {
    return <div>
      <p>{ this.props.count }</p>
      <button onClick={ this.props.add }>+</button>
      <button onClick={ this.props.minus }>-</button>
    </div>
  }
}

const mapStateToProps = state => state
const mapDispatchToProps = {
  ...action
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter1)

从上面代码中,可以看出在 Counter1 组件内部,属性或方法都是通过 props 访问,我们完全可以将 Counter1 组件转换成函数组件(无状态组件),通过函数组件外部都是一个容器组件(有状态组件)进行包裹,所有 connect(mapStateToProps, mapDispatchToProps)(Counter1) 最终返回的就是一个容器组件,接下来我们看下如何手写一个 react-redux。

  • 2.2 react-redux 伪代码

创建一个 ReactReduxContext 上下文对象

import React from 'react'
export const ReactReduxContext = React.createContext(null)
export default ReactReduxContext

在 Proveider 组件中,需要使用 ReactReduxContext 对象中提供的 Provider 组件

// src/react-redux/Provider.js
import React from 'react'
import {ReactReduxContext} from './Context'

class Provider extends React.Component{
  constructor(props) {
  	super(props)
  }
  render () {
  	return <ReactReduxContext.Provider value={{ store: this.props.store }}>
      {this.props.children}
    </ReactReduxContext.Provider>
  }
}
export default Provider

而 connect 方法,接收 mapStateToProps, mapDispatchToProps 两个参数,返回一个函数,返回的函数接收 自定义组件(例如 Counter1 ),函数执行后,返回最终的容器组件

// src/react-redux/connect.js
import React from 'react'
import {bindActionCreators} from 'redux'
import {ReactReduxContext} from './Context'

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    // 返回最终的容器组件
     return class extends React.Component{
    	static contextType = ReactReduxContext
    	constructor(props, context){
      	  super(props)
          this.state = mapStateToProps(context.store.getState())
        }
    	shouldComponentUpdate() {
          if (this.state === mapStateToProps(this.context.store.getState())) {
            return false;
          }
          return true;
        }
    	componentDidMount () {
      	  this.unsubscribe = this.context.subscribe(() => {
        	this.setState(mapStateToProps(this.context.store.getState()))
          })
        }
    	componentWillUnmount (){
      	  this.unsubscribe && this.unsubscribe()
        }
        render(){
          const actions = bindActionCreators(
            mapDispatchToProps,
            this.context.store.dispatch
          )
      	  return <WrappedComponent {...this.state} {...this.props} {...actions}/>
       }
     }
   }
}

export default connect

可以看出,connect 方法中,有 bindActionCreators 绑定 action 与 store.dispatch, 有订阅 store 中的 state 变化,这些都是我们只使用 redux ,需要在 react 组件中需要手动去写的,幸运的是,现在 react-redux 帮我们去干了.

总结

redux 主要应用在项目中的状态变化存储,组件之间的数据共享,当数据需要使用时,不存在层层传递到当前组件问题, 实现了类似于数据的传送门,哪里使用哪里取得。希望对您有所帮助吧。

点击关注,谢谢您的点赞~~~

推荐阅读