实现一个简单的react-redux

91 阅读3分钟

react-redux为我们做了什么?

在没有react-redux的情况下使用redux,你需要在每一个需要使用redux的组件中引入store,如果需要修改store中的数据还需要引入action,在某一个事件的回调函数中去dispatch,如下(经典计数器案例)

import React, { PureComponent } from 'react'
import store from '../store'
import { increamentAction } from '../store/counter/actionCreators'export default class Home extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      // 第一次获取counter值
      counter: store.getState().counter.counter
    }
  }
​
  componentDidMount() {
    // 订阅store,发生变化以后重新设置counter值
    store.subscribe(() => {
      this.setState({ counter: store.getState().counter.counter })
    })
  }
​
  increament(adder) {
    // 在click的回调事件中dispatch increament的action
    store.dispatch(increamentAction(adder))
  }
​
  render() {
    const { counter } = this.state
    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={() => this.increament(1)}>+1</button>
      </div>
    )
  }
}
​

两个问题

  1. 每一个组件都需要引入store,并且要订阅数据,首次渲染还需要手动获取,十分麻烦
  2. 每一个action都需要包裹在一个回调中

react-redux将store和组件之间建立起了联系

具体使用不说了,下面是上面案例使用react-redux的重构版本

import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { increamentAction } from '../store/counter/actionCreators'class Home extends PureComponent {
  render() {
    const { counter, increament } = this.props
    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={() => increament(1)}>+1</button>
      </div>
    )
  }
}
​
const mapStateToProps = state => ({
  counter: state.counter.counter
})
​
const mapDispatchToProps = dispatch => ({
  increament: adder => dispatch(increamentAction)
})
​
export default connect(mapStateToProps, mapDispatchToProps)(Home)

当然了,这里的counter不是平白无故来的,还需要给App组件包裹一个Provider组件,具体可以看看官方文档

分析原理

Provider

你可以发现react-redux为每一个组件提供的store出现在了props里面,这明显是使用了Context,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法

connect

connect明显是一个函数,而且是一个高阶函数,因为它调用的结果又再次调用了。并且再次调用传入的参数是组件,如果要渲染传入的组件,那么返回值也需要是一个组件,所以connect返回的函数是一个高阶组件。如果是高阶组件的话,我们就可以对传入的组件进行进一步的传值,高阶组件服用逻辑还是很方便的。

基本实现

connect

src
├── App.jsx
├── index.js
├── my-react-redux
│   └── connect.js +
├── components
│   └── Home.jsx
└── store
    ├── counter
    │   ├── actionCreators.js
    │   ├── constants.js
    │   └── reducer.js
    └── index.js

connect.js

我们之所以能够从props中取到数据,正是高阶组件的功劳

import { useEffect, useState } from "react";
import store from "../store";
​
export default function(mapStateToProps, mapDispatchToProps) {
  // 返回一个高阶组件
  return function(WrapperComponent) {
    // 返回一个组件
    return function(props) {
      // 处理state以及actions传入props
      const [state, setState] = useState(mapStateToProps(store.getState()))
      const dispatch = mapDispatchToProps(store.dispatch)
      useEffect(() => {
        const unsubscribe = store.subscribe(() => setState(store.getState()))
        return () => unsubscribe()
      })
      return (
        <WrapperComponent {...props} {...state} {...dispatch}/>
      )
    }
  }
}

但是现在这个connect函数还是太依赖store了,所以Provider的作用就来了

Provider

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
​
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App/>
  </Provider>
);

Context是通过一个Context.Provider来包裹组件,并且传值属性名是value,所以这里Provider组件是做了进一步的封装

src
├── App.jsx
├── index.js
├── my-react-redux
│   ├── Provider.jsx +
│   ├── storeContext.js +
│ └── connect.js
├── components
│   ├── Home.jsx
└── store
    ├── counter
    │   ├── actionCreators.js
    │   ├── constants.js
    │   └── reducer.js
    └── index.js
​

storeContext.js

import { createContext } from 'react'
export const storeContext = createContext()

Provider.jsx

import React from "react";
import storeContext from "./storeContext";
​
export default (props) => (
  <storeContext.Provider value={props.store}>
    {props.children}
  </storeContext.Provider>
);

Provider就这么简单

本文代码已经放在CodeSandBox啦!