剖析react-redux原理以及手写实现

前奏

使用react-redux

为什么要使用?

每次都重新调用render跟getState不是很高级, 想使用更react的方式来写,需要react-redux的支持.

安装

yarn add react-redux

提供了两个api

  1. Provider为后代组件提供store
  2. connect为组件提供数据和变更方法

API

  • <Provider store>

使组件层级中的connect()方法都能够获得Redux store. 正常情况下,你的根组件应该嵌套在<Provider>中才能使用connect()的方法.

  • connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])

连接react组件与Redux store. 返回一个新的已与Redux store连接的组件类.

使用

全局提供store, index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
//import {Provider} from './reactKredux';
import store from './store';


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

获取状态数据, ReactReduxPage.js

import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// 函数组件中使用react-redux,connect
const ReactReduxPage = (props) => {
    let {count, add, minus} = props;
    return (
        <div>
            <h3>ReactReduxPage</h3>
            <p>{count}</p>
            <button onClick={() => {
                add()
            }}>
                加
            </button>
            <button onClick={() => {
                minus()
            }}>
                减
            </button>

        </div>
    )
}

export default connect(
    //mapStateToProps把state map(映射)props上一份
    ({count}) => ({count}),
    //mapDispatchToProps object|function
    (dispatch) => {
        let result = {
            add: () => ({type: 'ADD'}),
            minus: () => ({type: 'MINUS'})
        }
        result = bindActionCreators(result, dispatch);

        return {dispatch, ...result}
    }
)(ReactReduxPage)

bindActionCreators是个函数, 这里作用是: 给action绑定dispatch方法, 相当于dispatch({type: ‘ADD’}), 否则没有触发dispatch因此数据就不会更新. connect相当于一个高阶组件, 传入一个组件,经过传入参数加工, 返回一个有store的新组件.
具体细节请移步 : redux文档
效果如下:

Video_2021-09-17_111123.gif

核心

手写实现react-redux

创建reactKredux.js的文件, 去实现:

  • [ bindActionCreators ]
  • [ Provider ]
  • [ connect ]

bindActionCreators

先看一下bindActionCreators的实现,reactKredux.js

const bindActionCreator = (action, dispatch) => {
    return (...args) => (dispatch(action(...args)))
}

const bindActionCreators = (actions, dispatch) => {
    let bindActions = {};
    for (let key in actions) {
        bindActions[key] = bindActionCreator(actions[key], dispatch);
    }
    console.log(bindActions);
    return bindActions
}

代码很短, 实现也很简单, 就是定义一个函数,里面有两个参数:actions,dispatch ,返回一个内部属性返回值被dispatch包裹的对象,如下:

image.png

Provider

Provider的实现,reactKredux.js

import {useContext, createContext, useEffect, useReducer,useState,useCallback} from 'react'
//react跨层级传递数据  context;
// 1.创建Context;
let Context = createContext();
//2.Provider 传递value
const Provider = ({store, children}) => {
    return <Context.Provider value={store}> {children}</Context.Provider>
}

里面需要用到Context: react跨层级传递数据
使用Context一共三个步骤:

  1. 创建Context, createContext()
  2. Provider 传递value, <Context.Provider value={}>
  3. 子组件消费Context value
    消费方式有3种:
    1. contextType(只能用在类组件中,只能订阅单一的context的来源)
    2. useContext(只能用在函数组件以及自定义hook中)
    3. Consumer (没有限制, 两种组件中都可以使用)
      使用Provider,传递数据store, index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// import {Provider} from 'react-redux';
import {Provider} from './reactKredux';
import store from './store';
ReactDOM.render(
    <Provider store={store}><App/></Provider>,
    // <App/>,
    document.getElementById('root')
);

App页面, App.js

import './App.css';
import  ReactRudexPage from './pages/ReactReduxPage';
import  ReactRudxHookPage from './pages/ReactReduxHookPage';
// import store from './store';

function App() {
  return (
    <div className="App">
      {/*<ReactRudxHookPage/>*/}
        <ReactRudexPage/>

    </div>
  );
}

export default App;

Connect

Connect的实现,reactKredux.js

const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => (props) => {
    //因为是函数组件, 所以使用useContext拿到Context的value值,store.
    let store = useContext(Context);
    //store里有getState获取数据,dispatch触发Action,subscribe订阅
    const {getState, dispatch, subscribe} = store;
    let mapStates, mapdispatch = {dispatch};
    mapStates = mapStateToProps(getState());
    if (typeof mapDispatchToProps === 'object') {
        mapdispatch = bindActionCreators(mapDispatchToProps, dispatch);
    } else {
        mapdispatch = mapDispatchToProps(dispatch);
    }

    // const [count, forceUpdate] = useReducer(x => x + 1, 0);
    //强制更新
    const forceUpdate=useForceUpdata();
    //订阅state的变更
    useEffect(() => {
        const unSubscribe = subscribe(() => forceUpdate());
        //组件在卸载前取消订阅
        return () => {
            unSubscribe()
        }
    }, [store])

    return <NewComponent {...props} {...mapStates} {...mapdispatch} />

}
const useForceUpdata=()=>{
  const [state,setState]=useState(0);
  const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
  return forceUpdate;

}
  • connect 是个函数, 外部接受参数:mapStateToProps,mapDispatchToProps,内部接受参数:WrappedComponent(当前的使用store的组件名),WrappedComponent接受参数:props(组件自己的props).最终返回一个经过加工的新组件.

  • mapStateToProps 是个函数, 接受一个Store里面的所有状态值, 用store.getState拿到.最后返回想要的state值,({count}) => ({count}) 因此直接调用mapStateToProps(getState()) 就可以了.

  • mapDispatchToProps 可以是个函数也可以是个对象, 所以先进行类型判断, 如果是个对象, 就使用bindActionCreators返回一个经过dispatch加工的新对象. 如果是个函数, 就直接调用函数, 传入参数dispatch

  • 订阅 如果没有这一步, 则界面无法更新状态值; store里的subscribe接受一个函数,函数里返回一个强制更新函数的调用.subscribe(() => forceUpdate()) , 每当dispatch时, 也就是state改变时, store就触发forceUpdate(),这时候组件就会更新.

  • 取消订阅 最后, 组件卸载前需要取消订阅, 也就是触发 unSubscribe(), 这个其实就是store的subscribe返回操作, 里面会删除对应的订阅.
    index.js和ReactReduxPage.js里面对connect,bindActionCreators,Provider的引入, 换成我们自己写的,如下:
    index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// import {Provider} from 'react-redux';
import {Provider} from './reactKredux';
import store from './store';


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

ReactReduxPage.js

// import {connect} from 'react-redux';
// import {bindActionCreators} from 'redux';

import {bindActionCreators,connect} from '../reactKredux'  
// 函数组件中使用react-redux,connect
const ReactReduxPage = (props) => {
    let {count, add, minus} = props;
    return (
        <div>
            <h3>ReactReduxPage</h3>
            <p>{count}</p>
            <button onClick={() => {
                add()
            }}>
                加
            </button>
            <button onClick={() => {
                minus()
            }}>
                减
            </button>

        </div>
    )
}

export default connect(
    //mapStateToProps把state map(映射)props上一份
    ({count}) => ({count}),
    //mapDispatchToProps object|function
    (dispatch) => {
        let result = {
            add: () => ({type: 'ADD'}),
            minus: () => ({type: 'MINUS'})
        }
        result = bindActionCreators(result, dispatch);

        return {dispatch, ...result}
    }
)(ReactReduxPage)

这是store的文件, store/index.js

import {createStore, combineReducers} from 'redux';

export const countReducer = (state = 0, action) => {
    switch (action.type) {
        case 'ADD':
            return state + 1;
        case 'MINUS':
            return state-1;
        default:
            return state;
    }
}
//
const store = createStore(combineReducers({count:countReducer}));
// const store = createStore(countReducer);
export default store;

看下最终效果:
Video_2021-09-17_111123.gif
最终效果一样

react-redux Hooks API及手写实现

使用

两个API:
useSelector 获取store state
useDispatch 获取dispatch
ReactReduxHookPage.js

import {useCallback} from "react";
import {useDispatch, useSelector} from "react-redux";
// import {useDispatch, useSelector} from "../reactKredux";
export default({value})=>{
 const dispatch=useDispatch();
 const count=useSelector(({count})=>count);
   console.log(count);
   return (
       <div>
           <h3>ReactReduxHookPage</h3>
           <p>{count}</p>
           <button onClick={useCallback(()=>{dispatch({type:'ADD'})},[])}>
               加
           </button>
           <button onClick={useCallback(()=>{dispatch({type:'MINUS'})},[])}>
               减
           </button>

       </div>
   )
}

因为上面组件的react-redux引用的是官方的, 因此index.js里面的react的redux也要换成官方的"react-redux",不然会报错,因为不是同一个store以及找不到对应的Provider, App.js里面的页面引用ReactReduxHookPage 组件. 如下:
ReactReduxHookPage.js

import './App.css';
import  ReactRudexPage from './pages/ReactReduxPage';
import  ReactRudxHookPage from './pages/ReactReduxHookPage';
// import store from './store';

function App() {
  return (
    <div className="App">
      <ReactRudxHookPage/>
      {/*  <ReactRudexPage/>*/}

    </div>
  );
}

export default App;

最后下过如下:

Video_2021-09-22_153757.gif

手写实现

reactKredux.js

//hooks
//get
const useSelector = (select) => {
    const store = useStore();
    const getState=store.getState();
    //需要订阅(否则不会更新视图)
    // const [count,forceUpdate]=useReducer((x)=>x+1,0);
    const forceUpdate=useForceUpdata();
    useEffect(()=>{
        const unSubScribe=store.subscribe(()=>forceUpdate());
        return ()=>{
            unSubScribe();
        }

    },[store])
    return select(getState);
}

//set
const useDispatch = () => {
    const store = useStore();
    return store.dispatch;
}
//获取store
const useStore = () => {
    //返回store
    const store = useContext(Context);
    return store;

}
const useForceUpdata=()=>{
  const [state,setState]=useState(0);
  const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
  return forceUpdate;

}

里面的useSelector和useDispatch分别相当于,get和set.
useSelector: 接受一个函数参数, 而这个函数里面又接受getState参数, 返回想要的state值. getState通过store获取,如:store.getState,store又通过useContent获取. 因此里面做的事情就是, 直接调用这个函数参数select, 并传入参数getState. 最后返回. 并且里面要做订阅和取消订阅.

useDispatch: 就是拿到store里面的dispatch , 很简单, store.dispatch获取并返回就可以了.
reactKredux完整代码:
reactKredux.js

import {useContext, createContext, useEffect, useReducer,useState,useCallback} from 'react'
//react跨层级传递数据  context;
// 1.创建Context;
let Context = createContext();
//2.Provider 传递value
const Provider = ({store, children}) => {
    return <Context.Provider value={store}> {children}</Context.Provider>
}
// 3.消费Context value
// 三种消费方式:
// 1)只能用在类组件 :contextType ,只能订阅单一的context
// 2) 只能用在函数组件  useContext
// 3)类组件函数组件皆可 Consumer
const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => (props) => {
    //因为是函数组件, 所以使用useContext拿到Context的value值,store.
    let store = useContext(Context);
    //store里有getState获取数据,dispatch触发Action,subscribe订阅
    const {getState, dispatch, subscribe} = store;
    let mapStates, mapdispatch = {dispatch};
    mapStates = mapStateToProps(getState());
    if (typeof mapDispatchToProps === 'object') {
        mapdispatch = bindActionCreators(mapDispatchToProps, dispatch);
    } else {
        mapdispatch = mapDispatchToProps(dispatch);
    }

    // const [count, forceUpdate] = useReducer(x => x + 1, 0);
    //强制更新
    const forceUpdate=useForceUpdata();
    //订阅state的变更
    useEffect(() => {
        const unSubscribe = subscribe(() => forceUpdate());
        //组件在卸载前取消订阅
        return () => {
            unSubscribe()
        }
    }, [store])

    return <NewComponent {...props} {...mapStates} {...mapdispatch} />

}
const bindActionCreator = (action, dispatch) => {
    return (...args) => (dispatch(action(...args)))
}

const bindActionCreators = (actions, dispatch) => {
    let bindActions = {};
    for (let key in actions) {
        bindActions[key] = bindActionCreator(actions[key], dispatch);
    }
    console.log(bindActions);
    return bindActions
}
//hooks
//get
const useSelector = (select) => {
    const store = useStore();
    const getState=store.getState();
    //需要订阅(否则不会更新视图)
    // const [count,forceUpdate]=useReducer((x)=>x+1,0);
    const forceUpdate=useForceUpdata();
    useEffect(()=>{
        const unSubScribe=store.subscribe(()=>forceUpdate());
        return ()=>{
            unSubScribe();
        }

    },[store])
    return select(getState);
}

//set
const useDispatch = () => {
    const store = useStore();
    return store.dispatch;
}
//获取store
const useStore = () => {
    //返回store
    const store = useContext(Context);
    return store;

}
const useForceUpdata=()=>{
  const [state,setState]=useState(0);
  const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
  return forceUpdate;

}
export {bindActionCreators, connect, Provider,useSelector,useDispatch};

完结

(ps:第一次写技术博客,最近在学习源码,记录一番. 自己加深下印象以及锻炼下表达能力. 里面可能有许多不足与疏忽之处,望各位大佬多多指导以及指出)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值