redux ,react-redux 源码实现

570 阅读6分钟

在react中,redux 可以说是一个管理全局状态的状态机,但是状态管理的方式不只有redux,还有mobx,等待其他的状态管理的库。下面我们先了解一下我们为什么需要对项目做状态管理,以及如何实现redux的。

  • redux可以在其他框架中使用吗?

当我们,redux是一套与ui相互独立的库,也就是说他不依赖任何前端框架,在Vue中也可以直接使用,但是Vuex是不可以在其他框架中使用的,因为Vuex是基于Vue开发的。

  • 为什么要redux状态管理?

比如我们有A,B,C三个个容器,这三个容器中的状态都是相同的,其中一个容器做出改变,另外几个容器需要进行同步改变,这里最简单的实现就是在最外层包裹一层容器F,将状态保存在这个容器中,这样做可以解决当前问题,但是后面的问题来了,如果后期我们客户需要新增一个容器E这个容器需要的状态跟A一样并且不能创建在F容器下,其实拿到状态也不是不可以就是这样做我们的代码会越来越复杂,如果使用redux状态管理就很完美的解决上面的问题,首先redux在全局抛出一个state,并且在项目中的任何组件中都可以获取这个状态,通过某个组件做出改变后同步到其他依赖的组件。

  • redux下都有那些核心方法
    1. createStore, 创建状态 它返回三个函数subscribe监听状态改变,getState获取状态数据,dispatch触发action
    2. combineReducers, 合并多个reducer 类似于Vuex中 module
    3. bindActionCreators 将action进行合并调用
    4. applyMiddleware 中间件 在Vuex中可以使用 Vue.mixin 对Vuex进行拓展

实现createStore,下面源码是根据redux官方源码简化实现,所以还是存在差别的

  • 创建文件 redux/createStore.js
  • 官方地址redux
 
const createStore = (reducer,preloadedState,enhancer)=>{ 
       //  这里先写出来后面做解释  enhancer 处理中间件
        if(enhancer){
            return enhancer(createStore)(reducer,preloadedState)
        }
        let currentState = preloadedState // 初始状态
        let currentListenter= []; 
        let currentReducer = reducer;  // 储存reducer
        return {
             subscribe:(func)=>{
                 currentListenter.push(func)
             },
             getState:()=>{
                return JSON.parse(JSON.stringify(currentState))
             },
             dispatch:(action:{type,preload})=>{
                   currentState = currentReducer(currentState,action);
                   currentListenter.map(fn=>fn(currentState));
             } 
        }
}
export default createStore

根据上面的源码可以看出createStore核心实现就是一个发布订阅,现在问题来了,代码这么少能用吗?那必须能用啊,下面来个例子

  • 首先创建store/index.js文件
// state
 let user = {
        name:'user',
        age:18
    }
// reducer
let  user = (data ,action)=>{  
      
       switch(action.type){
          case 'C_NAME':
            return {...data,name:action.preload};
          case 'C_AGE':
          return {...data,age:action.preload};  
           default:
           return data;
       }
} 

// action
const changenames = (e)=>({type:'C_NAME',preload:e}) 
const stores = createStore(reducers,obj)
stores.changenames = changenames;
export default stores;

  • 在App.jsx中引入
import React, { useState,useEffect } from 'react' 
import stores from './store';  
function App() {  
    let [infos,setInfos] = useState();
    const changeName = ()=>{
      stores.dispatch(stores.changenames('摸鱼的汤姆'))
 
   }
   const changeAppname = (e)=>setName(e)
   useEffect(()=>stores.subscribe((e)=>changeAppname),[])
  return ( 
       <div>
            {infos.name}
            <button onClick={changeName}>点击</button> 
       </div>
   
  )
} 
export default App

  • 最后视图效果就是name的值改变为了摸鱼的汤姆

刚开始的时候说过redux一些核心方法,但我们只实现了一个,下面就是将我们的redux继续完善一下

实现combineReducers

  • 创建文件 redux/combineReducers.js
const combineReducers = (reducers:any)=>{ 
    if(!isPlanObject(reducers)){
        throw console.error('reducers is not a Object!'); 
    }
    // 为了第二次调用直接获取
    const reducerKeys =   Object.keys(reducers); 
    return function combination(state={},action){
        let hasChanged = false
        let nextstate = {}; 
        reducerKeys.forEach(item=>{
                const key = reducers[item];
                // 获取旧值
                const preDataKey =  state[item];
                const nextDataKey = key(preDataKey,action);
                // 赋值新值
                nextstate[item] = nextDataKey;
                // 新旧对比判断值是否有变
                hasChanged = hasChanged || preDataKey !== nextstate 
        }) 
        return hasChanged?nextstate:state
       }
}

export default combineReducers

  • 使用起来就比较简单了,看下面的例子
let state = {
    user1:{
       //...
    },
    user2:{
       //...
    }
}


let  user1 = (data ,action)=>{  
       // ....
} 
let  user2 = (data ,action)=>{  
       // ....
} 

const reducers =  combineReducers({
     user1,user2
})
const stores = createStore(reducersa,state );

export default stores

combineReducers 主要的作用就是将多个reducer进行合并,对状态进行更改

实现bindActionCreators

  • 创建文件 redux/bindActionCreators.js
function bindActionCreator(actionCreator, dispatch) {
    return function () {
        return dispatch(actionCreator.apply(this, arguments))
     }
    }
const bindActionCreators = (actionCreators,dispatch)=>{
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
   const boundActionCreators = {};
   for(let key in actionCreators){
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
   }
   return boundActionCreators;
}

  • bindActionCreators使用案例
// store/index.js
const changenames = (e)=>({type:'C_NAME',preload:e}) 
const bindAction = bindActionCreators({
    names:(e)=>changenames(e), 
},stores.dispatch); 
// App.jsx 使用 
bindAction.names('users')  

实现applyMiddleware

  • 创建文件 redux/applyMiddleware.js
// 管道函数
function compose(...funcs) {
    if (funcs.length === 0) {
      return (arg) => arg
    }
  
    if (funcs.length === 1) {
      return funcs[0]
    } 
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
  }
// 中间件就是一个洋葱模型,中间件会根据顺序一层一层的向后调用
const applyMiddleware = (...middlewares)=>{
         // 这里的柯里化就是开始的时候enhancer(createStore)(reducer,preloadedState)的enhancer调用
         return (createStores)=>(...args)=>{ 
            const store = createStores(...args)
            let dispatch = (e)=>{};
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (...arg) => dispatch(...arg),
            }
            const chain = middlewares.map((middleware) => middleware(middlewareAPI))
            // 将所有中间件放入到管道中调用
            dispatch = compose(...chain)(store.dispatch);
            return {
                ...store,
                dispatch,
              } 
         }
}
  • 使用redux-thunk中间件

redux-thunk主要用于异步调用action,关于redux-thunk的源码我们就不分析了,感兴趣的可以去看看源码,下面我们就安装一下

  • 执行x-thunk npm install redux-thunk
  • 将上面combineReducers的案例改造一下
// 引入thunk
import thunk from "redux-thunk";

const stores = createStore(reducersa,state,applyMiddleware(thunk));

实现react-redux

  • 上面关于redux的核心方法都已经实现了,下面开始实现一下react-redux

首先我们分析一下我们在使用react-redux都是引入react-redux中的Provider包裹在最外层,然后里面需要使用store的使用connect对子组件进行包裹。其实其中的原理也不是很复杂主要是用到了createContext去实现的,那下面就开始上代码!

  • 创建react-redux/context.js文件,内容如下
import { createContext } from "react";
const _context = createContext({});
export default _context;
  • 创建react-redux/connect.js文件,内容如下
import React, {useContext, useState, useEffect } from 'react';
// 核心用的就是 createContext
import ReduxContext from './context';

let key = false;
export const connect = (mapStateToProps,mapDispatchToProps)=>Component=>{
      function Connect(props){
           const store = useContext(ReduxContext);
           const [count,setCount] = useState(false); 
           const forceUpdata = ()=>{ 
            setCount(val=>!val);   
           };
           //这里是核心,因为redux更改是单项数据流,不会触发页面更新,所以这里需要监听一下更新count,触发视图更新
           useEffect(()=>store.subscribe((e)=>{
            forceUpdata()
           }),[]);
           return (
             <ReduxContext.Consumer>
                  {
                     store => <>
                       <Component
                           {...props}
                           {...mapStateToProps(store.getState())}
                           {...mapDispatchToProps(store.dispatch)}
                       >    
                       </Component>
                     </>
                  }
             </ReduxContext.Consumer>
           )
       }
       return Connect;
}

redux,react-redux都实现了,下面就来一个完整案例把

  • 完整store案例
import { createStore,combineReducers,bindActionCreators,applyMiddleware } from "./redux/createStore";
import thunk from "redux-thunk";
// state
let state = {
    user:{
        name:'user',
        age:18
    }
}
// reducer
let  user = (data ,action)=>{  
      
       switch(action.type){
          case 'C_NAME':
            return {...data,name:action.preload};
          case 'C_AGE':
          return {...data,age:action.preload};  
           default:
           return data;
       }
}  
// action
const changenames = (e)=>({type:'C_NAME',preload:e})  
// reducers
const reducersa =  combineReducers({
     user
})
const stores = createStore(reducersa,state,applyMiddleware(thunk)); 
// 合并action
const bindAction = bindActionCreators({
    names:(e)=>changenames(e), 
},stores.dispatch);


// thunk用法 -- thunk 的原理比较简单就不实现了

const logins = (name)=>(dispatch)=>{
      setTimeout(()=>{
        dispatch(changenames(name))
      },1000)
} 
stores.dispatch(logins('摸鱼的汤姆')) 
export default stores;  
  • 更改App.jsx
import React, { useState,useEffect } from 'react' 
import stores from './store';
import ReduxContext from './redux/context';
import Child from './Child'; 

function App() {    
  return ( 
      <ReduxContext.Provider value={stores}>
           <Child/>
     </ReduxContext.Provider>
   
  )
} 
export default App

  • 创建Child.jsx文件

import React,{useEffect} from 'react';
import { connect } from './redux/connectValue';

const Child = (props)=>{ 
    const changeusername = ()=>{
        const nums = Math.random()*10
        props.changeName(nums)
        console.log(props.user.name)
    }
    return (
       <> 
         {props.user.name}
         <div onClick={()=>changeusername()}>
           asdasd
         </div>
       </>
    )
 } 
 const mapStateToProps = (state)=>({...state});
 const mapDispatchToProps = (dispatch)=>{
       return {
           changeName:(e)=>dispatch({type:'C_NAME',preload:e})
       }
 }
 export default connect(mapStateToProps,mapDispatchToProps)(Child);

最后效果

image.png

点击改变之后

image.png

最后

以上就是关于本片文章全部内容了, 如果文章中存在什么问题并且有什么意见,还请大佬指出,非常感谢!!!