【Redux系列】五分钟掌握Redux

2,856 阅读5分钟

简介

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux不耦合任何框架,在React、Vue、Angular、jQuery等都可以使用。

为什么使用Redux呢?

  • 随着项目的越来越庞大和复杂,组件之间状态难以维护。
  • Redux是为了解决state的数据管理难问题。
  • 在React开发中由于单项数据流,两个非父子组件通讯麻烦。

Redux工作原理

简述上图的一个工作:

ComponentA派发一个动作dispatch(action)(动作意思是改变state颜色为🧱色),reducer接受到这个动作后改变state颜色,然后通知所有的订阅者,ComponentBComponentC监听到state有变化,拿到最新的state,改变组件颜色为🧱色。

  • store是应用的集中管理的地方,也可以称之为仓库。
  • state里保存我们的数据。
  • reducer处理器,接受到动作之后修改state状态。
  • dispatch派发一个动作通知reducer要修改的state,然后循环调用缓存函数,subscribe回掉函数触发拿到新状态。

action是一个普通对象,描述想要进行什么操作。组件修改state,必须要通过Reducer处理器,不能直接操作state。

话不多说直接上代码:

demo2传送门

自己动手试一下吧🌹

模拟redux

一、 mini版Redux,实现下面计数器的功能,使用原生js开发,不依赖任何框架。

计数器

1.1 新建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>
  <div>
    <p id='text'>0</p>
    <button id='add_btn'>+</button>
    <button id='minus_btn'>-</button>
  </div>
  <script src="./createStore.js"></script>
  <script src="./index.js"></script>
</body>
</html>
1.2 在同级目录创建createStore.js
// createStore 创建一个仓库
function createStore(reducer, initialState) {
  let state = initialState;
  let listeners = [];
  
  function getState() { 
    return state;
  }

  function dispatch(action) {
    state = reducer(action, state);
    listeners.forEach(l=>l()); // 遍历调用listener函数
  }

  function subscribe(listener) { 
    listeners.push(listener); //缓存监听函数
    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      if (index > -1) listeners.splice(index, 1);
    };
  }

  return { dispatch, getState, subscribe }
}
1.3 在同级目录创建index.js
const text = document.getElementById('text');
const addBtn = document.getElementById('add_btn');
const minusBtn = document.getElementById('minus_btn');
const initalState = 0;
const Add = 'Add';
const MINUS = 'MINUS';

function reducer(action, state) {
  switch (action.type) {
    case Add:
      return state + 1;
    case MINUS:
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(reducer, initalState); //创建仓库

function reder() {
  text.innerHTML = store.getState();
}

reder();
store.subscribe(()=>{ //观察state,如果有改动就会执行
  reder();
});

addBtn.addEventListener('click', function(){
  store.dispatch({ type: Add }) //派发递增的动作
})
minusBtn.addEventListener('click', function() {
  store.dispatch({ type: MINUS }) // 派发递减动作
})

这里只是简单的模拟了redux,还有很多功能没做,如果有兴趣的话不妨自己去完善,运用到react项目中,代码我会贴在文章最后。

二、 实现ReduxReact-Redux核心APi,实现上述计数器功能。

2.1 Redux

接下来主要实现APi有createStore, combineReducers, bindActionCreators

createStore:

createStore函数接受两个参数reducerinitialState,返回dispatch, getState, subscribe

reducer是一个函数,当我们调用dispatch方法会触发reducer,返回新状态赋值给state

getState获取最新的状态

subscribe接受一个函数,当调用dispatch方法时,传入subscribe的函数会被调用,在回掉函数内通过getState拿到最新的state

export default function createStore(reducer, initialState) {
  // 这个state是唯一的,所有的获取修改都是这个state
  let state = initialState; 
  let listeners = [];
  function getState() {
    return state;
  }

  function dispatch(action) {
    // console.log('-----',reducer);
    // reducer函数的目的就是返回更新后状态   然后赋值给state, 这样再去执行getState()拿到的就是新的状态
    state = reducer(state, action);
    listeners.forEach(l=>l());
  }

  function subscribe(listener) {
    listeners.push(listener);
    return function () {
      const index = listeners.indexOf(listener);
      if (index > -1) listeners.splice(index, 1);
    };
  }
  
  dispatch({type: '@xx/init'});
  return { dispatch, getState, subscribe }
}
combineReducers 合并reducer:

为了便于维护reducer我们要拆分,比如useReducer,sysReducer,countReducer等,由于只允许一个reducer,所以我们需要进行合并。

// 无论几个reducer其最终目的是返回一个新状态,更替老状态
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  return function(state={}, action){
    const newState = {};
    for (const key of reducerKeys) { //遍历所有的reducerKeys, 拿到所有reducer方法
      // 获取reducer方法
      const reduer = reducers[key]; 
      // 获取老状态
      const previousState = state[key];
      // 调用reducer, 得到最新的状态,并且赋值给 合并后的大状态(newState),key对应的 reducers对象的key
      newState[key] = reduer(previousState , action);
    }
    return newState;
  }
}

参数: 传入的是reducers对象,通过遍历调用每个reducer方法,获取的新状态,然后合并到newState对象上。

const reducers = {
  counter,
  counter1
}

const rootReducers = combineReducers(reducers);
bindActionCreators 创建actions封装:
export default function bindActionCreators(actions, dispatch) {
  if (typeof actions === 'function') {
    return function(){
      dispatch(actions(...arguments))
    }
  }

  const boundActionCreators = {};
  for (const key in actions) {
    const action = actions[key];
    if (actions.hasOwnProperty(key) && typeof action === 'function') {
      boundActionCreators[key] = function(){
        dispatch(action(...arguments))
      }
    }
  }
  return boundActionCreators;  
}
2.2 React-Redux

React-Redux目的是为了关联ReactRedux,核心api是 Providerconnect

Provider:

通过使用方式可以看出,Provider其实就是一个组件,在最高层使用通过绑定store prop,子组件通过context可以获取到store。

<Provider store={store}><Provider>

实现:

context.js

createContext使用

import React, { createContext } from 'react';

const ReactReduxContext = createContext(null); //创建ReactRedux上下文
ReactReduxContext.displayName = 'ReactRedux';
export default ReactReduxContext;

Provider.js

export default function Provider(props){
  return(
    // 这里的就是高层组件传入的store   有{ dispatch, getState, subscribe }等方法
    <ReactReduxContext.Provider value={props.store}>
      {props.children}
    </ReactReduxContext.Provider>
  )
}

connect.js :返回一个有prop的新组件,新组件通过props,拿到state也可以dispatch派发动作

参数:

mapStateToProps: 类型是一个函数,把state映射要props上,通过props可以得到值。 mapDispatchToProps:类型是一个函数或则action对象, 把dispatch(action)动作映射要props上,通过props可以直接拿到方法,进行派发动作。

WrappedComponent:类型是一个老的组件,把所有的值通过props挂载到新的组件上。

export default function (mapStateToProps, mapDispatchToProps) {

  return function (WrappedComponent) { //高阶函数为了方便扩展
    return function (props) {
        // 获取上下文,拿到value对象
      const { dispatch, getState, subscribe } = useContext(ReactReduxContext);
      // 获取状态
      const [state, setstate] = useState(mapStateToProps(getState()));
   
      const boundActionCreator = useMemo(()=>{
        return mapDispatchToPropsIsType(mapDispatchToProps , dispatch);
      },[]);

      useEffect(() => {
        // 监听数据改动
        const unsubscribe = subscribe(()=>{
          setstate(mapStateToProps(getState()));
        })
        return ()=>{
          unsubscribe();
        }
      }, []);

      return (
        <WrappedComponent {...state} {...boundActionCreator} />
      )
    }
  }
}

// 根据mapDispatchToProps类型,做相应处理
function mapDispatchToPropsIsType(mapDispatchToProps, dispatch) {
  if(typeof mapDispatchToProps === 'function'){
    return mapDispatchToProps(dispatch);
  }

  if(typeof mapDispatchToProps === 'object') {
    return bindActionCreators(mapDispatchToProps , dispatch);
  }
}

使用:

const mapStateToProps = state => {
  return {
    counter1: state.counter1
  }
}
export default connect(
  mapStateToProps,
  actions
)(Counter1)

周边

总结

所有的代码都在这,记得给个star哦!

THK!!!