从零手动实现redux/react-redux

浅谈对redux/react-redux的理解以及手动实现redux/react-redux

什么是redux?为什么我们要用redux?

引用官方的原话:Redux is a predictable state container for JavaScript apps.,用大白话说redux就一个容器,这个容器为我们提供了一种管理公共状态的方案,方便不同组件之间的状态共享。摆脱了层层透传数据的繁琐,利于项目数据的维护

怎么自己实现redux

既然redux是管理公共状态的方案,那么组件肯定有使用甚至更新这些状态的需求,想要实现这些需求需要通过:获取状态(getState),改变状态(dispatch),通知组件响应状态改变(subscribe)这三个方法,那么我们需要创建一个store.js文件,用于统一管理,

1. store.js

export function createStore(reducer){
  let State = { count:0 }
  let observers = []     
  // 获取当前的state数据
  const getState = function() {
    return State
  }
  // 派发action改变state
  const dispatch = function(action) {
    State = reducer(State, action)
    observers.forEach(fn => fn())
  }
  // 监听state的改变,通知组件,组件响应视图更新
  function subscribe(fn) {
    observers.push(fn)
  }
  return { getState, subscribe, dispatch }
}

2.reducer.js

export function reducer(state,action){
    switch(action.type){
        case 'add':
            return {
                ...state,
                count:state.count+1
            }
        default:
        return state;
    }
}

这个时候你可以在index.js文件中尝试看看这个store成功没

3.index.js

import { createStore } from './store'
import { reducer } from './reducer'
const store = createStore(reducer);

console.log('before----',store.getState())
store.subscribe(() => { console.log('组件收到改变了') })
store.dispatch({type:'add'})
console.log('after----',store.getState())

结果如下:

在这里插入图片描述

以上就是一个简单的redux实现方案,当然我相信仅仅这样并不能满足大家的需求,因为这样虽然实现了状态共享但是我们再实际运用中。每个组件都需要引入store,然后在去调用它的getState,dispatch,subscribe,这样显得代码比较冗杂,我们希望的是通过一些巧妙的设计和方法,不用每一个组件都引入store就可以达到共享里状态的目的,这个时候,react-redux就登场啦

什么是react-redux?

我理解的react-redux就是连接组件和store的一个工具,其中他提供的方法Provider,connect两个API,使我们不用每一个组件都引入store就可以共享store里面的状态,原理大致就是:Provider包裹根组件,将store放进this.context里,connect将store里面的state、dispatch合并进了this.props,并自动订阅更新,下面我们来看一下如何实现这两个API:

怎么自己实现react-redux

1.Provider

provider的实现依赖context这个API,他有一些固定写法,如果不太懂可以自行查阅,这里就不赘述。

import React,{Component} from 'react'
import PropTypes from 'prop-types'

export class Provider extends Component {
  // childContextTypes来指定context对象的属性,固定写法 
    static childContextTypes = {
      store: PropTypes.object
    }
  // getChildContext方法,返回context对象,固定写法  
    getChildContext() {
      return { store: this.props.store }
    }
  // 渲染被provider包裹的组件
    render() {
      return this.props.children
    }
}

完成Provider后,我们就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。

2.connect

connect是一个方法,两个参数mapStateToProps,mapDispatchToProps,然后返回一个高阶组件,这个高阶组件接受一个组件Com作为参数,然后返回一个新的组件,这个高阶组件的目的就是通过mapStateToProps,mapDispatchToProps给你传入的组件Com添加一些props,相当于把state和dispatch映射到了Com组件的props上,在组件里面就乐意通过this.props获取这些state和dispatch

import React,{Component} from 'react'
import PropTypes from 'prop-types'

export function connect(mapStateToProps,mapDispatchToProps){
    return function(Com){
        class Connect extends Component{
            static contextTypes = {
                store: PropTypes.object
            }
            componentDidMount() {  
                const { store:{ subscribe } } = this.context;
                subscribe(this.handleChangeView);
            }

            handleChangeView = () => {
                // 用你想要的方法去更新视图,这里我就要setState方法
                this.setState({})
            }

            render(){
                const { store:{ getState,dispatch },props } = this.context;
                return(
                    <Com 
                        {...mapStateToProps(getState())}
                        {...mapDispatchToProps(dispatch)}
                    />
                )
            }
        }
        return Connect;
    }
}

完成connect后,只需要在组件中用connec包裹我们的组件,我们就能在组件中通过this.props这样的形式取store里面的信息,并且视图跟着响应。

最后用一个demo来测试一下这个react-redux,我是自己从零手动搭建的,如果觉得麻烦,可以用react的脚手架创建。

项目目录如下:
在这里插入图片描述

关键代码:

// package.json
{
    "name": "react-redux",
    "version": "1.0.0",
    "author": "ymx",
    "description": "react-redux原理理解",
    "keywords": [
        "react-redux"
    ],
    "scripts": {
        "start": "webpack-dev-server --config webpack.config.js"
    },
    "dependencies": {
        "@babel/core": "^7.13.8",
        "@babel/plugin-proposal-class-properties": "^7.13.0",
        "@babel/preset-env": "^7.13.9",
        "@babel/preset-react": "^7.12.13",
        "babel-loader": "^8.2.2",
        "html-webpack-plugin": "^5.2.0",
        "prop-types": "^15.7.2",
        "react": "^17.0.1",
        "react-dom": "^17.0.1",
        "webpack": "^5.24.3",
        "webpack-cli": "^3.3.12",
        "webpack-dev-server": "^3.11.2"
    }
}

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); 
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({template: './index.html'})
  ],
  module: {
    rules: [
        {
            test: /\.(js|jsx)$/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env', '@babel/preset-react'],
                    plugins: ['@babel/plugin-proposal-class-properties']
                }
            }
        },
    ]}
}
// store.js
export function createStore(reducer){
  let State = { count:0 }
  let observers = []               
  const getState = function() {
    return State
  }

  const dispatch = function(action) {
    State = reducer(State, action)
    observers.forEach(fn => fn())
  }

  function subscribe(fn) {
    observers.push(fn)
  }

  return { getState, subscribe, dispatch }
}
// reducer.js
export function reducer(state,action){
    switch(action.type){
        case 'add':
            return {
                ...state,
                count:state.count+1
            }
        default:
        return state;
    }
}
// react-redux.js
import React,{Component} from 'react'
import PropTypes from 'prop-types'

// Provider
export class Provider extends Component {
    static childContextTypes = {
      store: PropTypes.object
    }
  
    getChildContext() {
      return { store: this.props.store }
    }
  
    render() {
      return this.props.children
    }
}

// connect
export function connect(mapStateToProps,mapDispatchToProps){
    return function(Com){
        class Connect extends Component{
            static contextTypes = {
                store: PropTypes.object
            }
            componentDidMount() {  
                const { store:{ subscribe } } = this.context;
                subscribe(this.handleChangeView);
            }

            handleChangeView = () => {
                // 用你想要得方法去更新视图,这里我就要setState方法
                this.setState({})
            }

            render(){
                const { store:{ getState,dispatch },props } = this.context;
                return(
                    <Com 
                        {...mapStateToProps(getState())}
                        {...mapDispatchToProps(dispatch)}
                    />
                )
            }
        }
        return Connect;
    }
}
// App.jsx
import React,{ Component } from 'react';
import { connect } from './react-redux'
import PropTypes from 'prop-types';

class App extends Component{
    static contextTypes = {
        store: PropTypes.object
    }

    Add = () => {
        this.props.addCount()
    }

    render(){
        const { count } = this.props;
        return <h2 style={{ cursor:'pointer' }} onClick={this.Add}>AppCount---{count}</h2>
    }
}
const mapStateToProps = state => {
    return {
        count: state.count
    }
}
const mapDispatchToProps = dispatch => {
    return {
        addCount: () => {
        dispatch({type:'add'})
        }
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './react-redux';
import { createStore } from './store'
import { reducer } from './reducer'
import App from './App.jsx'

const store = createStore(reducer);

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

源码地址:react-redux

参考文章:

聊一聊我对 React Context 的理解以及应用

Redux/react-redux/redux中间件设计实现剖析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值