带你实现 react-redux

前言

之前我们实现了 redux 的功能,这次我们来实现一下配合 redux 开发中经常会用到的一个库—— react-redux。本文不会详细介绍 react-redux 的使用,另外需要了解 Context API, 再看此文就很容易理解了。可以看看我之前写的几篇文章

react-redux 基本使用

用个简单的加减数字作例子, 把代码贴出来:

  • redux.js
import { createStore } from 'redux'

// actions
const ADD_NUM = 'ADD_NUM'
const DESC_NUM = 'DESC_NUM'

// action creators
export function addNumAction() {
  return {
    type: ADD_NUM,
  }
}

export function reduceNumAction() {
  return {
    type: DESC_NUM,
  }
}

const defaultState = {
  num: 0,
}

// reducer
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case ADD_NUM:
      return { num: state.num + 1 }
    case DESC_NUM:
      return { num: state.num - 1 }
    default:
      return state
  }
}

const store = createStore(reducer)

export default store

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import Demo from './Demo'
import { Provider } from 'react-redux'
import store from './redux.js'

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

Demo.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addNumAction, reduceNumAction } from './redux.js'

class Demo extends Component {
  render() {
    console.log(this.props)
    return (
      <div>
        <p>{this.props.num}</p>
        <button onClick={this.props.addNum}>增加1</button>
        <button onClick={this.props.reduceNum}>减少1</button>
      </div>
    )
  }
}

const mapStateToProps = state => {
  return {
    num: state.num,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addNum() {
      const action = addNumAction()
      dispatch(action)
    },
    reduceNum() {
      const action = reduceNumAction()
      dispatch(action)
    },
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Demo)

就可以实现 num 的增减:


其实一个简单的 react-redux, 主要也就是实现 connect 和 Provider 的基本功能

  • connect:可以把 state 和 dispatch 绑定到 react 组件,使得组件可以访问到 redux 的数据
  • Provider:提供的是一个顶层容器的作用,实现 store 的上下文传递

使用旧版 Context API 实现

实现Provider

首先我们看它的用法,就知道它不是一个函数,而是一个组件:

<Provider store={store}>
    <Demo />
</Provider>

React 的 Context API 提供了一种通过组件树传递数据的方法,无需在每个级别手动传递 props 属性。

Provider 的实现比较简单,核心就是把 store 放到 context 里面,所有的子元素可以直接取到 store

export class Provider extends Component {
  static childContextTypes = {
    store: PropTypes.object
  }

  constructor(props) {
    super(props)
    this.store = props.store // 也就是 Provider 组件从外部传入的 store
  }

  getChildContext() {
    return { store: this.store }
  }

  render() {
    return this.props.children // 中间包的组件传入了 context,其余原封不动返回组件
  }
}

还有个地方大家知道就好,两种写法一样的,对context type 的约束

export class Provider extends Component {
  // 不用 static 来声明...
}
Provider.childContextTypes = {
   store: PropTypes.object  
}

实现 connect

connect用法

export default connect(mapStateToProps, mapDispatchToProps)(Demo)

connect 是一个高阶组件,就是以组件作为参数,返回一个组件。

connect 负责连接组件,给到 redux 的数据放到组件的属性里

  1. 负责接收一个组件,把 state 的一些数据放进去,返回一个组件
  2. 数据变化的时候,能够通知组件(需要进行监听)
export function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrapComponent) {
    return class ConnectComponent extends Component {
      static contextTypes = { 
        store: PropTypes.object,
      }

      constructor(props) {
        super(props)
        this.state = {
          props: {},
        }
      }

      componentDidMount() {
        const { store } = this.context
        store.subscribe(() => this.update()) // 监听更新事件
        this.update()
      }

      update() {
        const { store } = this.context
        let stateToProps = mapStateToProps(store.getState()) // 传入 redux 的 state
        let dispatchToProps = mapDispatchToProps(store.dispatch) // 传入 redux 的 dispatch

        this.setState({
          props: {
            ...this.state.props,
            ...stateToProps,
            ...dispatchToProps,
          },
        })
      }

      render() {
        return <WrapComponent {...this.state.props} />
      }
    }
  }
}

这样 connect 就实现了,但还有一个问题,像上面的例子,我们其实可以直接传入 action creators, 而不用自己定义函数传入 dispatch

export default connect(mapStateToProps, { addNumAction, reduceNumAction })(Demo)

调用的时候:

<button onClick={this.props.addNumAction}>增加1</button>
<button onClick={this.props.reduceNumAction}>减少1</button>

那它的 dispatch 哪里来的,其实是用了 redux 的 bindActionCreators 函数,在我介绍 redux 的文章有提到,它作用是将 actionCreator 转化成 dispatch 形式,即

{ addNumAction }  =>  (...args) => dispatch(addNumAction(args))

所以我们需要再更改 connect 函数,同时,这次我们用箭头函数的形式简化代码

import { bindActionCreators } from 'redux'

export const connect = (mapStateToProps, mapDispatchToProps) => WrapComponent => {
  return class ConnectComponent extends Component {
    static contextTypes = {
      store: PropTypes.object,
    }

    constructor(props) {
      super(props)
      this.state = {
        props: {},
      }
    }

    componentDidMount() {
      const { store } = this.context
      store.subscribe(() => this.update())
      this.update()
    }

    update() {
      const { store } = this.context
      let stateToProps = mapStateToProps(store.getState())
      let dispatchToProps
      if (typeof mapDispatchToProps === 'function') {
        dispatchToProps = mapDispatchToProps(store.dispatch)
      } else {
        // 传递了一个 actionCreator 对象过来
        dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      }

      this.setState({
        props: {
          ...this.state.props,
          ...stateToProps,
          ...dispatchToProps,
        },
      })
    }

    render() {
      return <WrapComponent {...this.state.props} />
    }
  }
}

以上,我们实现了最基本版的 react-redux,然后接下来,我们用新版的 Context API 再写一次

使用新版 Context API 实现

import React, { Component } from 'react'
import { bindActionCreators } from 'redux'

const StoreContext = React.createContext(null)

// 这次 Provider 采取更简洁的形式写
export class Provider extends Component {
  render() {
    return (
      <StoreContext.Provider value={this.props.store}>
        {this.props.children}
      </StoreContext.Provider>
    )
  }
}

export function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrapComponent) {
    class ConnectComponent extends Component {
      constructor(props) {
        super(props)
        this.state = {
          props: {},
        }
      }

      componentDidMount() {
        const { store } = this.props
        store.subscribe(() => this.update())
        this.update()
      }

      update() {
        const { store } = this.props
        let stateToProps = mapStateToProps(store.getState())
        let dispatchToProps
        if (typeof mapDispatchToProps === 'function') {
          dispatchToProps = mapDispatchToProps(store.dispatch)
        } else {
          // 传递了一个 actionCreator 对象过来
          dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)
        }

        this.setState({
          props: {
            ...this.state.props,
            ...stateToProps,
            ...dispatchToProps,
          },
        })
      }

      render() {
        return <WrapComponent {...this.state.props} />
      }
    }

    return () => (
      <StoreContext.Consumer>
        {value => <ConnectComponent store={value} />}
      </StoreContext.Consumer>
    )
  }
}
原文链接:segmentfault.com

上一篇:ERROR in Entry module not found: Error: Can't resolve './src' in XXX的一个解决方案
下一篇:转行学前端的第 46 天 : 了解 ECMAScript RegExp 对象属性和实例属性

相关推荐

  • 🔥 Promise|async|Generator 实现&amp;原理大解析 | 9k字

    笔者刚接触async/await时,就被其暂停执行的特性吸引了,心想在没有原生API支持的情况下,await居然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的...

    2 个月前
  • (转载)如何通俗易懂的理解Redux

    作者:Wang Namelos 链接:https://www.zhihu.com/questio...(https://www.zhihu.com/question/41312576/answer/9...

    2 年前
  • (小小黑科技)vue+echarts实现半圆图表

    如何用echarts实现半圆图表?在echarts官方实例倒腾一波,发现官方并没有提供半圆图表的写法,那怎么办呢?官方没提供,但需求还是要实现的。 半圆图表其实就是饼图的一半,那么简单的思路如下:设...

    1 年前
  • 龙骨换装游戏系统设计与实现(基于Egret+DragonBones龙骨动画)

    如何开发一个可维护性,可扩展,跨平台的换装游戏呢,本文从产品设计、龙骨动画、前端再到后端如何管理等角度,来介绍骨骼换装游戏从0到1的实现过程。 项目背景   我们一直在思考,如何能激励学员自主学习的积...

    2 个月前
  • 鼠标事件实现拖拽

    为什么需要拖拽 当前的互联网用户早已习惯了拖拽,习惯了拖拽带来的便利。任何一个前端项目都有加入拖拽这个功能的可能性。 拖拽的实现方案 鼠标事件实现 drag and drop api 这两种实现方...

    4 个月前
  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没fluttersock...

    2 年前
  • 高性能迷你React框架 anu1.3.0 发布

    anujs1.3.0是一款高性能Reactlike框架,是目前世界上对React16兼容最好的迷你库。 自React16起,相继推出createContext,createPortal, creat...

    2 年前
  • 高德地图实现(附案例)

    需求:基于高德地图实现地图展示实现步骤:1.首先注册登录并创建一个应用 image.png(/public/upload/db7478b92a72da60c0e15d99f5cd83d7) 2....

    1 天前
  • 高德地图+vue实现页面点击绘制多边形及多边形切割拆分

    最终效果 (/public/upload/7732e1e729bfcd99f99f56cd04ac17b1) 技术栈 项目中使用到的技术 高德基于vue的vueamap,组件使用的elemen...

    15 天前
  • 高德地图 react-amap 实战

    clipboard.png(https://img.javascriptcn.com/5a33946ad8c0ea8ee7870f74f331d0c0 "clipboard.png") reacta...

    9 个月前

官方社区

扫码加入 JavaScript 社区