React状态管理工具介绍及redux简析-灵析社区

JACKY

react 状态管理

react 是一个用于用户界面构建的库,提供了 createContext 来进行状态管理,当复杂项目中 context 就捉襟见肘了,需要使用合理的工具来管理状态。

1. react 中常用状态管理

a. 父子组件通过 props 传递数据, 当属性跨层级传递时不是很方便。

function Parent() {
  const a = 1;
  return (
    <div>
      <Child a={a} />
    </div>
  );
}

function Child(props) {
  console.log(props); //{a: 1}
  return <div>{props.a}</div>;
}

b.createContext react提供了createContext,创建上下文context来实现属性的跨层级传递。

React.createContext()方法是React提供的一个用于创建上下文的方法。通过创建上下文,可以在组件之间共享数据,避免了通过 props 一层层地传递数据的繁琐过程。使用上下文,可以更方便地在组件树中的任何地方访问共享的数据。

import { createContext } from "react";
//使用React.createContext()方法创建上下文
const MyContext = createContext();

function Text() {
  const obj = { a: 1, b: false };
  return (
    // 需要注意的是,Consumer组件必须在Provider组件的子组件中使用,否则无法获取到上下文中的值。
    //组件中使用Provider组件来提供共享的数据
    <MyContext.Provider value={obj}>
      <Child />
    </MyContext.Provider>
  );
}
function Child() {
  console.log(MyContext);
  /**
   * $$typeof: Symbol(react.context)
   * Consumer:{$$typeof: Symbol(react.context), _context: {…}, …}
   * Provider: {$$typeof: Symbol(react.provider), _context: {…}}
   * _currentValue: {a: 1, b: false}
   */
  //在Consumer组件中,使用一个函数作为子元素,并将上下文中的数据作为参数传递给这个函数。在这个函数中,可以使用上下文中的数据
  return (
    <div>
      <p>test context</p>
      <MyContext.Consumer>
        {(value) => {
          return <div>{value.a}</div>;
        }}
      </MyContext.Consumer>
    </div>
  );
}

c. redux 中文官网

为了我们开发方便,可以使用redux-devtools来查看redux状态。 Redux中,处理异步操作需要使用中间件,如redux-thunk或redux-saga,redux-promise等中间件

redux使用

 <!-- 创建store -->
let store = createStore(combinedReducer);
//action
export const ADD1 = "ADD1";
export const MINUS1 = "MINUS1";
import { ADD1, MINUS1 } from "../action-types";
function add1() {
  //actionCreator 它是一个创建action的函数
  return { type: ADD1 };
}

//reducers
import { ADD1, MINUS1, DOUBLE } from "../action-types";
let initState = { number: 0 };
const reducer = (state = initState, action) => {
  switch (action.type) {
    case ADD1:
      return { number: state.number + 1 };
    case MINUS1:
      return { number: state.number - 1 };
    case DOUBLE:
      return { number: state.number * 2 };
    default:
      return state;
  }
};
// 使用combineReducers方法合并reducer
let combinedReducer = combineReducers({
  counter1,
  counter2,
});
// 在组件中的使用
import actionCreators from "../store/actionCreators/counter1";
import { connect } from "react-redux";
class Counter1 extends React.Component {
  render() {
    return (
      <div>
        <p>{this.props.number}</p>
        <button onClick={this.props.add1}>+</button>
      </div>
    );
  }
}
//把仓库中的状态映射为组件的属性对象 仓库到组件的输出
const mapStateToProps = (state) => state.counter1;
//const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch)
export default connect(
  mapStateToProps,
  actionCreators //组件的输出,在组件里派发动作,修改仓库
)(Counter1);

d. redux-tookit 用于简化Redux开发的工具集。它提供了一组用于处理常见Redux任务的工具和API,使得开发者可以更快、更简单地编写Redux代码。英文官网:redux-toolkit.js.org/

import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
//configureStore函数,用于创建Redux store。这个函数封装了常见的Redux配置,包括合并reducer、应用中间件和DevTools等。使用configureStore函数可以减少配置代码的编写量,并提供了一些默认的配置选项
const store = configureStore({
  reducer: rootReducer,
});
const initialState = { value: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case "increment":
      return { ...state, value: state.value + 1 };
    case "decrement":
      return { ...state, value: state.value - 1 };
    case "incrementByAmount":
      return { ...state, value: state.value + action.payload };
    default:
      return state;
  }
}
function increment(amount: number) {
  return {
    type: INCREMENT,
    payload: amount,
  };
}

const action = increment(3);
//createSlice:Redux Toolkit提供了一个createSlice函数,用于创建Redux的slice。slice是一个包含了reducer和action的对象,用于定义和处理Redux的一部分状态。使用createSlice函数可以更简洁地定义slice,并自动生成对应的reducer和action。
const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment(state) {
      state.value++;
    },
    decrement(state) {
      state.value--;
    },
    incrementByAmount(state, action: PayloadAction<number>) {
      state.value += action.payload;
    },
  },
});

e. dva dva

// 创建应用
const app = dva();

// 注册 Model
app.model({
  namespace: "count",
  state: 0,
  reducers: {
    add(state) {
      return state + 1;
    },
  },
  effects: {
    //Effect是一个Generator函数,用于处理异步的副作用操作。在Dva中,每个Model可以定义一个或多个effect函数,用于处理异步的操作,如发送网络请求、访问浏览器缓存等。Effect函数通过使用Dva提供的一些内置的effect函数,如call、put、select等,来处理异步操作。
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: "add" });
    },
  },
});

// 注册视图
app.router(() => <ConnectedApp />);

// 启动应用
app.start("#root");

f. rematch

基于Redux的轻量级框架,用于简化Redux应用程序的开发。它提供了一种简单的方式来定义和管理Redux的模型(Model),并提供了一些内置的功能和约定,使得开发者可以更快、更简单地编写Redux代码。

Rematch使用了redux-saga来处理异步操作,使得处理异步操作更简单和直观。 Rematch提供了一个插件系统,可以通过插件来扩展和定制Rematch的功能。插件可以用于添加中间件、增强Model、添加额外的功能等。这使得开发者可以根据实际需求来选择和使用插件,从而更灵活地定制Rematch的功能。

// 在Model中,可以定义state属性来表示状态的初始值。reducers属性用于定义同步的状态更新,effects属性用于定义异步的副作用操作。
// 过使用connect函数,可以将组件与Rematch store连接起来。mapState函数用于将状态映射到组件的props中,mapDispatch函数用于将action函数映射到组件的props中
export const countModel = {
  state: { counter: 0 }, // initial state
  reducers: {
    add: (state, payload) => {
      return {
        ...state,
        counter: state.counter + payload,
      };
    },
  },
  effects: {
    async loadData(payload, rootState) {
      const response = await fetch(`http://xxx.com/${payload}`);
      const data = await response.json();
      this.add(data); // dispatch action to a local reducer
    },
  },
};
// 创建一个Rematch store:
// 在init函数中,可以通过models属性来定义一个或多个Model。每个Model包含了reducers、effects和selectors等属性,用于定义和管理状态和副作用。
import { init } from "@rematch/core";

const store = init({
  models: {
    // 定义Model
  },
});

还有其他状态管理工具如 MobX,Zustand,React Query,感兴趣的可以去了解下。

2. redux解析

redux 核心思想

  1. 数据向下流动
  2. Redux 是将整个应用状态存储到到一个地方,称为 store,里面保存一棵状态树 state tree
  3. 组件可以派发 dispatch 行为 action 给 store,而不是直接通知其它组件
  4. 其它组件可以通过订阅 store 中的状态(state)来刷新自己的视图

redux 三大原则

  1. 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
  2. State 是只读的,唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象 使用纯函数来执行修改,为了描述 action 如何改变 state tree ,你需要编写 reducers
  3. 单一数据源的设计让 React 的组件之间的通信更加方便,同时也便于状态的统一管理
版本redux@4.2.0
export {
  createStore,
  legacy_createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes,
};
//  __DO_NOT_USE__ActionTypes
// 这是redux内部的默认action,我们在设置自己的action的时候,不要和他们重复
const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`,
};

2.1 createStore

export function createStore(reducer, preloadedState, enhancer) {
  if (
    (typeof preloadedState === "function" && typeof enhancer === "function") ||
    (typeof enhancer === "function" && typeof arguments[3] === "function")
  ) {
    //    这里进行参数校验
  }

  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      );
    }
    // 如果有enhancer则在enhancer内部初始化store
    return enhancer(createStore)(reducer, preloadedState);
  }
  // reducer是纯函数,在函数内部修改state。
  if (typeof reducer !== "function") {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    );
  }

  let currentReducer = reducer;
  let currentState = preloadedState;
  let currentListeners = [];
  let nextListeners = currentListeners;
  let isDispatching = false;

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }

  function getState() {
    return currentState;
  }
  // subcribe是我们订阅事件的地方,subcribe返回一个卸载函数。
  // 订阅状态变化事件,当状态发生改变后执行所有的监听函数、
  // 创建Redux store时,可以通过调用store.subscribe()方法来订阅状态的变化。该方法接受一个回调函数作为参数,这个回调函数会在状态发生变化时被调用
  function subscribe(listener) {
    let isSubscribed = true;

    ensureCanMutateNextListeners();
    nextListeners.push(listener);

    // 如果不再需要监听状态的变化,应该及时取消订阅,以避免不必要的性能消耗。可以通过调用返回的取消订阅函数来取消订阅,例如:const unsubscribe = store.subscribe(callback),然后在不需要监听时调用unsubscribe()即可。
    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }
      isSubscribed = false;

      ensureCanMutateNextListeners();
      const index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
      currentListeners = null;
    };
  }
  // dispatch是一个用于触发状态变化的方法。它是Redux中唯一能够改变状态的方式。
  // 通过调用store.dispatch()方法来触发状态的变化。该方法接受一个action对象作为参数,这个action对象描述了状态变化的类型和相关的数据。
  function dispatch(action) {
    if (!isPlainObject(action)) {
      //   判断action的类型
    }
    if (typeof action.type === "undefined") {
      //   判断action的类型
    }
    // 调用dispatch方法时,Redux会根据action对象的类型来执行相应的reducer函数。reducer函数是一个纯函数,它接受当前的状态和action对象作为参数,并返回一个新的状态。
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }
    // 在这里执行subscribe订阅的方法,响应式更新
    const listeners = (currentListeners = nextListeners);
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }

    return action;
  }
  // replaceReducer是一个用于替换当前reducer的方法。它允许我们在运行时动态地替换Redux store中的reducer函数。
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;

    dispatch({ type: ActionTypes.REPLACE });
  }

  //当创建一个存储时,会调度一个“INIT”操作,以便reducer返回其初始状态。这有效地填充初始状态树。
  dispatch({ type: ActionTypes.INIT });

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
  };
}
export const legacy_createStore = createStore;

2.2 bindActionCreators

bindActionCreators 用于绑定action creators到dispatch的方法,简化在组件中使用action creators的过程. 返回一个与原始action creators具有相同键值对的对象,但是每个action creator都会被自动调用dispatch函数进行包装。

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
// 返回调用dispatch函数的回调
function bindActionCreator(actionCreator, dispatch) {
  return function () {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

2.3 applyMiddleware

applyMiddleware是一个用于redux使用中间件的方法。允许我们在Redux的数据流中插入自定义的逻辑,以处理异步操作、日志记录、错误处理等。 创建store时,可以通过调用applyMiddleware()方法来应用中间件。该方法接受一个或多个中间件函数作为参数,并返回一个增强后的store创建函数

中间件函数是一个接受store的dispatch函数作为参数的函数。在dispatch函数被调用之前或之后执行一些额外的逻辑。中间件函数可以访问store的getState方法来获取当前的状态,也可以调用dispatch方法来触发下一个中间件或reducer函数。 我们常用的redux中间件有:redux-thunk中间件来处理异步操作,redux-logger中间件来记录日志,redux-promise中间件来处理Promise对象,redux-saga处理异步操作等。

function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }
}
// 中间件函数的顺序非常重要。它们会按照顺序依次执行,因此前一个中间件的输出会成为下一个中间件的输入。在编写中间件时,我们需要确保它们的顺序是正确的,以便实现预期的逻辑。
// compose是一个用于组合函数的方法。允许我们将多个函数按照从右到左的顺序依次执行,并将每个函数的输出作为下一个函数的输入。

// 调用compose(f, g, h)时,它会返回一个新的函数,这个新的函数等价于f(g(h(...args)))。也就是说,它会将参数args依次传递给h、g和f,并将每个函数的输出作为下一个函数的输入。
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)))
}

2.4 redux中间件

//日志中间件 中间件的格式是固定的
function logger({ getState, dispatch }) {
    return function (next) {
        return function (action) {//此方法就是我们改造后的dispatch方法
            console.log('老状态', getState());
            next(action);//调用原始的dispatch方法,传入动作action,发给仓库,仓库里会调用reducer计算新的状态
            console.log('新状态', getState());
            return action;
        }
    }
}

function promise({ getState, dispatch }) {
    return function (next) {
        return function (action) {//此方法就是我们改造后的dispatch方法
            if (action.then && typeof action.then === 'function') {
                action.then(dispatch)
            } else {
                next(action);
            }
        }
    }
}
function thunk({ getState, dispatch }) {
    return function (next) {
        return function (action) {//此方法就是我们改造后的dispatch方法
            if (typeof action === 'function') {
                //把新的dispatch传递给了函数,这样就可以在函数里派发动作
                return action(getState, dispatch);
            }
            return next(action);
        }
    }
}

3. 总结

主要分为两部分:第一部分简单介绍了react常用的状态管理工具,第二部分分析了redux的源码和中间件。后面有时间介绍下react和redux连接的react-redux

阅读量:2036

点赞量:0

收藏量:0