深入理解redux原理,从零开始实现一个简单的redux(包括react-redux, redux-thunk)

目录

前言

Redux作为React的状态管理工具, 在开发大型应用时已不可缺少, 为了更深入的了解Redux的整个实现机制, 决定从头开始, 实现实现一个具有基础功能的Redux

项目地址 欢迎star/fork

预览

初始化项目

1.全局安装脚手架
npm install -g create-react-app复制代码
2.创建项目
create-react-app mini-redux复制代码
3.项目目录
mini-react
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   └── favicon.ico
│   └── index.html
│   └── manifest.json
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js复制代码

实现Redux基础功能

1.实现Redux

新建~/src/mini-redux/mini-redux.js, redux会对外暴露一个createStore的方法,接受reducer作为参数

export function createStore(reducer) {
  let currentState = {}
  let currentListeners = []

  function getState() {
    return currentState
  }
  function subscribe(listener) {
    currentListeners.push(listener)
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    currentListeners.forEach(v => v())
    return action
  }
  dispatch({type: '@REACT_FIRST_ACTION'})  //初始化state
  return { getState, subscribe, dispatch}
}复制代码

以上, 我们就已经实现了redux的基础功能, 下面来调用我们实现的mini-redux, 检验是否达到预期. 新建~/src/index.redux.js

import { createStore } from './mini-redux/mini-redux'

const ADD = 'ADD'
const REMOVE = 'REMOVE'

// reducer
export function counter(state=0, action) {
  switch (action.type) {
    case ADD:
        return state + 1
    case REMOVE:
        return state - 1
    default:
        return 10
  }
}

export function add() {
  return {type: 'ADD'}
}
export function remove() {
  return {type: 'REMOVE'}
}

const store = createStore(counter)
const init = store.getState()
console.log(`开始数值:${init}`)

function listener(){
  const current = store.getState()
  console.log(`现在数值:${current}`)
}
// 订阅,每次state修改,都会执行listener
store.subscribe(listener)
// 提交状态变更的申请
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'REMOVE' })
store.dispatch({ type: 'REMOVE' })复制代码

index.js中引入以上文件以执行, 查看控制台,可以看到如下log信息

开始数值:10       index.redux.js:27
现在数值:11       index.redux.js:31 
现在数值:12       index.redux.js:31 
现在数值:11       index.redux.js:31 
现在数值:10       index.redux.js:31复制代码

至此,我们已经实现了redux的功能, 但是离我们的预期还差的很远, 因为我们需要结合react来使用

2.结合React使用

下面将mini-reactreact组件结合使用, 修改index.redux.js如下

const ADD = 'ADD'
const REMOVE = 'REMOVE'

// reducer
export function counter(state=0, action) {
  switch (action.type) {
    case ADD:
        return state + 1
    case REMOVE:
        return state - 1
    default:
        return 10
  }
}

export function add() {
  return {type: 'ADD'}
}
export function remove() {
  return {type: 'REMOVE'}
}复制代码

index.js文件初始化redux

import { createStore } from './mini-redux/mini-redux'
import { counter } from './index.redux'

// 初始化redux
const store = createStore(counter)

function render() {
  ReactDOM.render(<App store={store} />, document.getElementById('root'));
}
render()
// 每次修改状态,从新渲染页面
store.subscribe(render)复制代码

App.js文件中我们就可以调用redux

import {add, remove} from './index.redux'

class App extends Component {
    render() {
        const store = this.props.store
        // 获取当前值
        const num = store.getState()
        return (
            <div className="App">
                <p>初始值为{num}</p>
                <button onClick={() => store.dispatch(add())}>Add</button>
                <button onClick={() => store.dispatch(remove())}>Remove</button>
            </div>
        );
    }
}

export default App;复制代码

如上图, 我们就可以在React组件中修改mini-redux的状态了

实现React-Redux

上面我们已经,实现了Redux的功能,并且且可以和React结合使用了, 但是这种与React的链接的方式非常繁琐,高度耦合, 在日常开发中不会这样用, 我们会使用 react-redux库来连接React(如果不了解react-redux可以看看这篇博客), 下面我们就来实现一个简易的react-redux

1.context

实现react-redux前, 我们要了解一下reactcontext(不了解可以查看文档), react-redux的实现就利用了context机制. 下面通过一个例子,了解context的用法.

新建~/src/mini-redux/context.test.js

import React from 'react'
import PropTypes from 'prop-types'
// context是全局的, 组件里声明, 所有子元素可以直接获取

class Sidebar extends React.Component {
  render(){
    return (
      <div>
        <p>Sidebar</p>
        <Navbar></Navbar>
      </div>
    )
  }
}

class Navbar extends React.Component {
  // 限制类型, 必须
  static contextTypes = {
    user: PropTypes.string
  }
  render() {
    console.log(this.context)
    return (
      <div>{this.context.user} Navbar</div>
    )
  }
}


class Page extends React.Component {
  // 限制类型, 必须
  static childContextTypes = {
    user: PropTypes.string
  }
  constructor(props){
    super(props)
    this.state = {user: 'Jack'}
  }
  getChildContext() {
    return this.state
  }
  render() {
    return (
      <div>
        <p>我是{this.state.user}</p>
        <Sidebar/>
      </div>
    )
  }
}

export default Page复制代码
2.react-readux

react-redux中有两个是我们常用的组件, 分别是connectProvider, connect用于组件获取redux里面的数据(stateaction), Provider用于把store置于context, 让所有的子元素可以获取到store, 下面分别实现connectprovider

实现Provider

新建~/src/mini-redux/mini-react-redux, 代码如下

import React from 'react'
import PropTypes from 'prop-types'


// 把store放到context里, 所有的子元素可以直接取到store
export class Provider extends React.Component{
  // 限制数据类型
    static childContextTypes = {
    store: PropTypes.object
  }
  getChildContext(){
    return { store:this.store }
  }
  constructor(props, context){
    super(props, context)
    this.store = props.store
  }
  render(){
    // 返回所有子元素
    return this.props.children
  }
}复制代码

下面验证Provider是否能实现预期功能, 修改~/src/index.js文件如下

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import { createStore } from './mini-redux/mini-redux'
import { Provider } from './mini-redux/mini-react-redux'
import { counter } from './index.redux'

const store = createStore(counter)

ReactDOM.render(
  (<Provider store={store}><App/></Provider>), 
  document.getElementById('root')
)复制代码

最后我们还要修改~/src/App.js文件中获取store数据的方式, 改成使用connect获取, 但是因为还没有实现connect, 所有我们暂使用原react-reduxconnect组件验证上面实现的Provider

import React, { Component } from 'react';
import { connect } from 'react-redux'
import {add, remove} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>初始值为{this.props.num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
            </div>
        );
    }
}

App = connect(state => ({num: state}), {add, remove})(App)

export default App;复制代码

验证结果, 上面实现的Provider成功对接connect

实现connect

上面我们实现了Provider, 但是connect仍然用的是原版react-reduxconnect, 下面就来在~/src/mini-redux/mini-react-redux.js文件中添加一个connect方法

import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './mini-redux'

// connect 负责链接组件,给到redux里的数据放到组件的属性里
// 1. 负责接受一个组件,把state里的一些数据放进去,返回一个组件
// 2. 数据变化的时候,能够通知组件

export const connect = (mapStateToProps = state=>state, mapDispatchToProps = {}) => (WrapComponent) => {
  return class ConnectComponent extends React.Component{
    static contextTypes = {
      store:PropTypes.object
    }
    constructor(props, context){
      super(props, context)
      this.state = {
        props:{}
      }
    }
    componentDidMount(){
      const {store} = this.context
      store.subscribe(()=>this.update())
      this.update()
    }
    update(){
      // 获取mapStateToProps和mapDispatchToProps 放入this.props里
      const {store} = this.context
      const stateProps = mapStateToProps(store.getState())
      // 方法不能直接给,因为需要dispatch
      const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      this.setState({
        props:{
          ...this.state.props,
          ...stateProps,
          ...dispatchProps  
        }
      })
    }
    render(){
      return <WrapComponent {...this.state.props}></WrapComponent>
    }
  }
}复制代码

在上面代码中, 我们还需要在mini-redux.js中添加一个bindActionCreators方法, 用于使用dispatch包裹包裹actionCreator方法, 代码如下

......
function bindActionCreator(creator, dispatch){
  return (...args) => dispatch(creator(...args))
}
export function bindActionCreators(creators,dispatch){
  let bound = {}
  Object.keys(creators).forEach(v=>{
    let creator = creators[v]
    bound[v] = bindActionCreator(creator, dispatch)
  })
  return bound
}
......复制代码

最后我们将~/src/App.js中的connect换成上面完成的connect, 完成测试

import { connect } from './mini-redux/mini-react-redux'复制代码

实现redux中间件机制

实现applyMiddleware

在平常使用redux时, 我们会利用各种中间件来扩展redux功能, 比如使用redux-thunk实现异步提交action, 现在来给我们的mini-redux添加中间件机制

修改~/src/mini-redux/mini-redux.js代码如下

export function createStore(reducer, enhancer) {
  if (enhancer) {
    return enhancer(createStore)(reducer)
  }

  let currentState = {}
  let currentListeners = []

  function getState() {
    return currentState
  }
  function subscribe(listener) {
    currentListeners.push(listener)
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    currentListeners.forEach(v => v())
    return action
  }
  //初始化state
  dispatch({type: '@REACT_FIRST_ACTION'})
  return { getState, subscribe, dispatch}
}

export function applyMiddleware(middleware) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    dispatch = middleware(midApi)(store.dispatch)

    return {
      ...store,
      dispatch
    }

  } 
}
......复制代码

以上我们就给mini-redux添加了中间件机制了, 下面我们就来使用中间件, 进行验证. 由于我们开没有自己的中间件, 现在使用redux-thunk来实现一个异步提交action

修改~/src/index.js

......
import { createStore, applyMiddleware } from './mini-redux/mini-redux'
import thunk from 'redux-thunk'

const store = createStore(counter, applyMiddleware(thunk))
......复制代码

修改~/src/index.redux.js, 添加一个异步方法

export function addAsync() {
    return dispatch => {
    setTimeout(() => {
        dispatch(add());
    }, 2000);
  };
}复制代码

最后我们要~/src/App.js中引入这个异步方法, 修改如下

......
import React, { Component } from 'react';
import { connect } from './mini-redux/mini-react-redux'
import {add, remove, addAsync} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>初始值为{this.props.num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
                <button onClick={this.props.addAsync}>AddAsync</button>
            </div>
        );
    }
}

App = connect(state => ({num: state}), {add, remove, addAsync})(App)
export default App;复制代码

然后就可以验证啦

实现redux中间件

上面我们使用了redux-thunk中间件, 为何不自己写一个呢

新建~/src/mini-redux/mini-redux-thunk.js

const thunk = ({dispatch, getState}) => next => action => {
  // 如果是函数,执行一下,参数是dispatch和getState
  if (typeof action=='function') {
    return action(dispatch,getState)
  }
  // 默认,什么都没干,
  return next(action)
}
export default thunk复制代码

~/src/index.js中的thunk换成上面实现的thunk, 看看程序是否还能正确运行

在上面的基础上, 我们再实现一个arrThunk中间件, 中间件提供提交一个action数组的功能

新建~/src/mini-redux/mini-redux-arrayThunk.js

const arrayThunk = ({dispatch,getState})=>next=>action=>{
  if (Array.isArray(action)) {
    return action.forEach(v=>dispatch(v))
  }
  return next(action)
}
export default arrayThunk复制代码
添加多个中间件处理

上面我们实现的中间件机制,只允许添加一个中间件, 这不能满足我们日常开发的需要

修改~/src/mini-redux/mini-redux.js文件

......
// 接收中间件
export function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const middlewareChain = middlewares.map(middleware=>middleware(midApi))
    dispatch = compose(...middlewareChain)(store.dispatch)

    return {
      ...store,
      dispatch
    }

  } 
}
// compose(fn1,fn2,fn3)  ==> fn1(fn2(fn3))
export function compose(...funcs){
  if (funcs.length==0) {
    return arg=>arg
  }
  if (funcs.length==1) {
    return funcs[0]
  }
  return funcs.reduce((ret,item)=> (...args)=>ret(item(...args)))
}
......复制代码

最后我们将之前实现的两个中间件thunk,arrThunk同时使用, 看看上面实现的多中间件合并是否完成

修改~/src/index.js

...
import arrThunk from './mini-redux/mini-redux-arrThunk'
const store = createStore(counter, applyMiddleware(thunk, arrThunk))
...复制代码

~/src/index.redux.js中添加一个addTwice action生成器

...
export function addTwice() {
  return [{type: 'ADD'}, {type: 'ADD'}]
}
...复制代码

~/src/App.js中增加一个addTwice的按钮, 修改相应代码

import {add, remove, addAsync, addTwice} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>now num is {this.props.num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
                <button onClick={this.props.addAsync}>AddAsync</button>
                <button onClick={this.props.addTwice}>addTwice</button>
            </div>
        );
    }
}

App = connect(state => ({num: state}), {add, remove, addAsync, addTwice})(App)复制代码

大功告成!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值