React之五种状态解决方案

113 阅读17分钟

本文带你掌握5种状态共享解决方案,从使用到原理再到源码实现,请耐心的一步步看完。

一、redux

1.使用

1).创建store

关键词:createStore、reducer、state、action(type、data)

import { createStore } from '../myredux';

let initial = {
  supNum: 10,
  oppNum: 5
};
const reducer = function reducer (state = initial, action) {
  state = { ...state };
  switch (action.type) {
    case 'VOTE_SUP':
      state.supNum++;
      break;
    case 'VOTE_OPP':
      state.oppNum++;
      break;
    default:
  }
  return state;
};

const store = createStore(reducer);
export default store;

2).基于context传递

关键词:createContext、Provider、value={store}

context.js中

import React from "react";
const ThemeContext = React.createContext();
export default ThemeContext;

index.jsx中

import React from "react";
import ReactDOM from "react-dom/client";
import Vote from "./views/Vote";
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
import store from "./store";
import ThemeContext from "./ThemeContext";
import "./index.less";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ConfigProvider locale={zhCN}>
    <ThemeContext.Provider
      value={{
        store,
      }}
    >
      <Vote />
    </ThemeContext.Provider>
  </ConfigProvider>
);

3).类组件中使用

关键词:contextType、store.getState、store、subscribe

import React from "react";
import ThemeContext from "../ThemeContext";

class VoteMain extends React.Component {
    static contextType = ThemeContext;

    render() {
        const { store } = this.context;
        let { supNum, oppNum } = store.getState();

        return <div className="main">
            <p>支持人数:{supNum}人</p>
            <p>反对人数:{oppNum}人</p>
        </div>;
    }

    componentDidMount() {
        const { store } = this.context;
        store.subscribe(() => {
            this.forceUpdate();
        });
    }
}

export default VoteMain;

4).函数组件中使用

关键词:useContext、store.getState、store.subscribe

import React, { useContext, useState, useEffect } from "react";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import ThemeContext from "../ThemeContext";
import "./Vote.less";

const Vote = function Vote() {
  const { store } = useContext(ThemeContext);
  let { supNum, oppNum } = store.getState();

  let [_, setNum] = useState(0);
  useEffect(() => {
    store.subscribe(() => {
      setNum(+new Date());
    });
  }, []);

  return (
    <div className="vote-box">
      <div className="header">
        <h2 className="title">React是很棒的前端框架</h2>
        <span className="num">{supNum + oppNum}</span>
      </div>
      <VoteMain />
      <VoteFooter />
    </div>
  );
};
export default Vote;

2.手写实现createStore

import _ from './assets/utils';

/* 实现redux的部分源码 */
export const createStore = function createStore (reducer) {
  // reducer 必须是函数
  if (typeof reducer !== 'function') throw new Error("Expected the root reducer to be a function");

  let state, // 存放公共状态
    listeners = []; // 事件池

  /* 获取公共状态 */
  const getState = function getState () {
    return state; // 返回公共状态信息即可
  };

  /* 向事件池中加入让组件更新的方法 */
  const subscribe = function subscribe (listener) {
    // 规则校验 listener 必须是函数
    if (typeof listener !== "function") throw new TypeError("Expected the listener to be a function");
    // 把传入的方法(让组件更新的办法)加入到事件池中「需要做去重处理」
    if (!listeners.includes(listener)) {
      listeners.push(listener);
    }
    // 返回一个从事件池中移除方法的函数
    return function unsubscribe () {
      let index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  };

  /* 派发任务通知REDUCER执行 */
  const dispatch = function dispatch (action) {
    // 规则校验 action 必须是一个普通对象!
    if (!_.isPlainObject(action)) throw new TypeError("Actions must be plain objects");
    // axtion 必须要有type属性!
    if (typeof action.type === "undefined") throw new TypeError("Actions may not have an undefined 'type' property");

    // 把reducer执行,传递:公共状态、行为对象;接收执行的返回值,替换公共状态;
    state = reducer(state, action);

    // 当状态更改,我们还需要把事件池中的方法执行
    listeners.forEach(listener => {
      listener();
    });

    return action;
  };

  /* redux内部会默认进行一次dispatch派发,目的:给公共容器中的状态赋值初始值 */
  const randomString = () => Math.random().toString(36).substring(7).split('').join('.');
  dispatch({
    // type: Symbol()
    type: "@@redux/INIT" + randomString()
  });

  // 返回创建的STORE对象
  return {
    getState,
    subscribe,
    dispatch
  };
};

3.redux工程化

1. 目录结构

  • store
    • actions
      • index.js
      • AModule.js
      • BModule.js
    • reducers
      • index.js
      • AReducer.js
      • BReducer.js
    • action-types.js
    • index.js

2. action-types.js

/* 
统一管理需要派发的行为标识:
 + 为了保证不冲突,我们一般都是这样命名:模块名_派发的行为标识「大写」
 + 变量和存储的值是一致的
 + 所有需要派发的行为标识,都在这里定义
*/
export const VOTE_SUP = "VOTE_SUP";
export const VOTE_OPP = "VOTE_OPP";

export const PERSONAL_SUP = "PERSONAL_SUP";
export const PERSONAL_INFO = "PERSONAL_INFO";

3. actions

AModule.js

/* 
vote版块要派发的行为对象管理 
  voteAction包含好多方法,每一个方法执行,都返回要派发的行为对象
*/
import * as TYPES from '../action-types';
const voteAction = {
    support() {
        return {
            type: TYPES.VOTE_SUP
        };
    },
    oppose() {
        return {
            type: TYPES.VOTE_OPP
        };
    }
};
export default voteAction;

BModule.js

/* personal版块要派发的行为对象管理 */
import * as TYPES from '../action-types';
const personalAction = {
    // ...
};
export default personalAction;

index.js

/* 把各个版块的action合并为一个action即可 */
import voteAction from "./voteAction";
import personalAction from "./personalAction";

const action = {
    vote: voteAction,
    personal: personalAction
};
export default action;

4. reducers

ARecuder.js

/* Vote版块下的reducer */
import _ from '@/assets/utils';
import * as TYPES from '../action-types';

const initial = {
    supNum: 10,
    oppNum: 5,
    num: 0
};
export default function voteReducer(state = initial, action) {
    state = _.clone(true, state);
    switch (action.type) {
        case TYPES.VOTE_SUP:
            state.supNum++;
            break;
        case TYPES.VOTE_OPP:
            state.oppNum++;
            break;
        default:
    }
    return state;
};

BReducer.js

/* personal版块下的reducer */
import _ from '@/assets/utils';
import * as TYPES from '../action-types';

const initial = {
    num: 100,
    info: null
};
export default function personalReducer(state = initial, action) {
    state = _.clone(true, state);
    switch (action.type) {
        case TYPES.PERSONAL_INFO:
            state.info = action.payload;
            break;
        default:
    }
    return state;
};

index.js

import { combineReducers } from 'redux';
import voteReducer from './voteReducer';
import personalReducer from './personalReducer';

const reducer = combineReducers({
    vote: voteReducer,
    personal: personalReducer
});
export default reducer;

5. index.js

import { createStore } from 'redux';
import reducer from './reducers';

const store = createStore(reducer);
export default store;

6.代码中使用

上下文注入

/* 
  顶层组件 
  + 利用上下文传递store
  + 通过context.Provider的value属性传递store给所有后代组件
*/
import React from "react";
import ReactDOM from "react-dom/client";
import Vote from "./views/Vote";
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
import store from "./store";
import ThemeContext from "./ThemeContext";
import "./index.less";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ConfigProvider locale={zhCN}>
    <ThemeContext.Provider value={{ store }}>
      <Vote />
    </ThemeContext.Provider>
  </ConfigProvider>
);

页面级(大模块)组件中

/* 
  页面(大模块)级组件中
  + 通过useContext取出上下文对象中的store
  + 调用store.getState取出state中的数据
  + 调用store.subscribe订阅组件强制更新事件
  + 函数组件模拟类组件的forceUpdate,需要借助useState的能力
  + 给store.subscribe传递的回调函数中,执行一个setState即可
*/
import React, { useContext, useEffect, useState } from "react";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import ThemeContext from "../ThemeContext";
import "./Vote.less";

const Vote = function Vote() {
  const { store } = useContext(ThemeContext);
  let { supNum, oppNum } = store.getState().vote;

  let [, forceUpdate] = useState(0);
  useEffect(() => {
    store.subscribe(() => {
      forceUpdate(+new Date());
    });
  }, []);

  return (
    <div className="vote-box">
      <div className="header">
        <h2 className="title">React是很棒的前端框架</h2>
        <span className="num">{supNum + oppNum}</span>
      </div>
      <VoteMain />
      <VoteFooter />
    </div>
  );
};
export default Vote;

函数组件中使用

/* 
  函数组件中使用
  + 通过useContext获取到上下文对象中的store
  + 调用dispatch派发行为事件,改变store中的状态数据
  + 由于此处没有用到store中的数据,所以不需要subscribe组件更新函数
*/
import React, { useContext } from "react";
import { Button } from "antd";
import ThemeContext from "../ThemeContext";
import action from "../store/actions";

const VoteFooter = function VoteFooter() {
  const { store } = useContext(ThemeContext);

  return (
    <div className="footer">
      <Button
        type="primary"
        onClick={() => {
          store.dispatch(action.vote.support());
        }}
      >
        支持
      </Button>

      <Button
        type="primary"
        danger
        onClick={() => {
          store.dispatch(action.vote.oppose());
        }}
      >
        反对
      </Button>
    </div>
  );
};
export default VoteFooter;

类组件中使用

/* 
  类组件中使用
  + 通过给react规定的静态属性contextType赋值,得到上下文对象
  + 随后可从this.context中获取到上下文对象中的store
  + 调用store.getState获取store中的数据
  + 只要有使用到store数据的地方,都需要手动派发组件的强制更新函数
  + 在componentDidMount周期函数中,调用store.subscribe,传递的linstener回调函数中,执行forceUpdate
*/
import React from "react";
import ThemeContext from "../ThemeContext";

class VoteMain extends React.Component {
  static contextType = ThemeContext;

  render() {
    const { store } = this.context;
    let { supNum, oppNum } = store.getState().vote;

    return (
      <div className="main">
        <p>支持人数:{supNum}人</p>
        <p>反对人数:{oppNum}人</p>
      </div>
    );
  }

  componentDidMount() {
    const { store } = this.context;
    store.subscribe(() => {
      this.forceUpdate();
    });
  }
}
export default VoteMain;

4.实现combineReducers

combineReducers的作用:将多个recuder模块(reducers对象),合并到一起,最终返回一个合并后的reducer函数,每次dispatch时都会执行所有的recuder(state,action)

const combineReducers = function combineReducers(reducers) {
    // reducers是一个对象,以键值对存储了:模块名 & 每个模块的reducer
    let reducerskeys = Reflect.ownKeys(reducers);
    /* 
    返回一个合并的reducer 
      + 每一次dispatch派发,都是把这个reducer执行
      + state就是redux容器中的公共状态
      + action就是派发时候传递进来的行为对象
    */
    return function reducer(state = {}, action) {
        // 把reducers中的每一个小的reducer(每个模块的reducer)执行;
        //把对应模块的state/action传递进来;返回的值替换当前模块下的状态!!
        let nextState = {};
        reducerskeys.forEach(key => {
            // reducer:每个模块的reducer
            let reducer = reducers[key];
            nextState[key] = reducer(state[key], action);
        });
        return nextState;
    };
};
export default combineReducers;

5.总结redux

原理:

  • reducer:每个reducer就是一个独立的数据共享模块,需要有自己的状态和改变状态的方法!接收两个参数:state和action
    • state:接收的state由createStore函数创建,产生一个闭包的共享数据
    • action:派发行为事件的标识,是一个对象,必需要有type属性!通过type属性分发不同的逻辑!
  • combineReducers:为方便扩展,对reducer做模块化管理!作用是将多个reducer(对象集合)合并成一个reducer函数,在合并的reducer函数中,遍历reducers全部执行一遍!
  • actionCreator:将action和reducer一样进行模块化管理,最后统一由一个文件导出,导出的对象属性名需要和reducer一样,以便于事件派发!
  • createStore:接收reducer参数,返回getState、dispatch、subscribe等函数!
    • 内部创建了两个核心属性:state、listeners
      • state:利用闭包原理,产生的缓存数据
      • listeners:通过subscribe函数收集的订阅事件(组件更新事件)
    • getState:直接返回currentState,也就是上面的state
    • dispatch:干了两件事:更新数据、调用subscribe订阅的组件更新事件
      • 更新数据:将state和接收到的action,传递给reducer,在reducer中更新state
      • 发布事件:遍历调用listeners中收集的 能让组件刷新的事件函数!
    • subscribe:接收订阅事件,将事件存入listeners事件池中!
      • 有类型判断和去重等操作
      • 返回一个unsubscribe函数,该函数中找到并执行传入的订阅事件
    • 为了给state赋初始值,创建时会自动执行一次dispatch,传递的action-type是一个随机字符串

调用链解读:

创建时:createStore(reducer) > 初始化state、listeners > 创建并返回getState、dispatch、subscribe等函数 > 主动执行一次dispatch给state赋初始值 > 调用reducer传入初始state和action(随机type) > 执行reducer > 赋初始值 > 未匹配到type直接return > 初始化完毕

订阅时:调用subscribe传递组件更新函数 > 将组件更新函数存入到lisnteners中 > 返回一个unsubscribe函数

调用时:调用dispatch传递action > 将state和action传递给reducer > reducer匹配到action的type > 修改state > 遍历执行listener中收集的组件更新函数 > 组件更新(此时拿到的state也更新了)

设计缺陷及决绝方案:

  • getState直接返回currentState,导致能直接修改state,
    • 解决方案:克隆一份或者添加get set拦截。
  • istener事件池没有收集依赖,每次都是将事件池全部执行,
    • 解决方案:增加依赖收集,每次只执行dispatch对应的subscribe
  • 合并的reducer每次也会全部执行,即便触发的reducer已经执行完,也要执行其他的……
    • 解决方案:根据派发行为标志,进行触发判断,没匹配上就跳过,匹配到才执行。

二、react-redux

1.关系和概念

Redux: Redux 是一个用于管理 JavaScript 应用中状态的状态容器。它通过单一的全局状态存储来管理应用的状态,并通过纯粹的函数来描述状态的变化。Redux 的核心概念包括 Store(存储状态)、Action(描述状态变化的操作)、Reducer(纯粹的状态处理函数)以及中间件等。

React Redux: React Redux 是一个与 Redux 协同工作的库,它提供了一些组件和 API,使得在 React 应用中更容易使用 Redux。React Redux 提供了 Provider 组件,它可以将 Redux 的状态注入到整个应用中,使得所有组件都可以访问 Redux 中的状态。此外,React Redux 还提供了 connect 函数,该函数可以将 React 组件与 Redux 连接起来,使得组件可以访问 Redux 的状态,并在状态发生变化时自动重新渲染。

React Redux 的主要目标是在 React 组件中提供与 Redux 状态管理的集成,它负责将 Redux 的状态传递给组件并处理状态变化时的更新

上面的废话总结:redux是管理状态的!react-redux是让redux使用起来更方便的!

2.使用

关键词:Provider、connect、mapStateToProps、mapDispatchToProps、bindActionCreators

1).创建store

创建和定义 和redux一样的,只是有个react-redux的加持,使用时更加方便

2).上下文注入

/* 
  使用react-redux传递store
  + 使用react-redux提供的Provider方法,对store进行传递
  + 其原理是帮我们创建了一个上下文,不用我们再手动创建
*/
import React from "react";
import ReactDOM from "react-dom/client";
import Vote from "./views/Vote";
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
import store from "./store";
import { Provider } from "react-redux";
import "./index.less";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ConfigProvider locale={zhCN}>
    <Provider store={store}>
      <Vote />
    </Provider>
  </ConfigProvider>
);

3).类组件中使用state (mapStateToProps)

/* 
  类组件中使用state
  + 通过context方法,取出需要用的state
  + 再柯里化函数式的将类组件传进去
*/
import React from "react";
import { connect } from "react-redux";

class VoteMain extends React.Component {
  render() {
    let { supNum, oppNum } = this.props;
    return (
      <div className="main">
        <p>支持人数:{supNum}人</p>
        <p>反对人数:{oppNum}人</p>
      </div>
    );
  }
}
export default connect((state) => state.vote)(VoteMain);

4).函数组件中使用state (mapStateToProps)

/* 
  函数组件中调用state
  + 和类组件使用方式一样
  + 柯里化调用context再传递函数组件
*/
import React from "react";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import { connect } from "react-redux";
import "./Vote.less";

const Vote = function Vote(props) {
  let { supNum, oppNum } = props;
  return (
    <div className="vote-box">
      <div className="header">
        <h2 className="title">React是很棒的前端框架</h2>
        <span className="num">{supNum + oppNum}</span>
      </div>
      <VoteMain />
      <VoteFooter />
    </div>
  );
};
export default connect((state) => state.vote)(Vote);

5).使用dispatch (mapDispatchToProps)

/* 
  使用dispatch
  + connext的第二个函数的作用就是你要使用哪些dispatch,调用方式和state一样
  + 该函数中接收一个dispatch作为参数,返回一个对象,对象中调用dispatch做你想做的是
  + 可直接写action,内部会帮我们调用bindActionCreators做转发!
*/
import React from "react";
import { Button } from "antd";
import action from "../store/actions";
import { connect } from "react-redux";

const VoteFooter = function VoteFooter(props) {
  let { support, oppose } = props;
  return (
    <div className="footer">
      <Button type="primary" onClick={support}>
        支持
      </Button>
      <Button type="primary" danger onClick={oppose}>
        反对
      </Button>
    </div>
  );
};
export default connect(null, action.vote)(VoteFooter);

// 上面的等同于下面的
/* export default connect(null, (dispatch) => {
  return {
    support() {
      dispatch(action.vote.support());
    },
    oppose() {
      dispatch(action.vote.oppose());
    },
  };
})(VoteFooter); */

3.手写react-redux

import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  useMemo
} from "react";
const ContextZYR = createContext();


/* 
  Provider
  + 本质是一个react高阶组件
  + 帮你创建一个上下文对象,并将属性上的store分发到下面的子组件
*/
export function Provider (props) {
  let { store, children } = props;
  return <ContextZYR.Provider value={{ store }}>
    {children}
  </ContextZYR.Provider>;
};

/* 
  contect:对state和dispatch进行预处理,将指定的state或dispatch作为props传递给组件
  + 接收两个参数:mapStateToProps(函数),mapDispatchToProps(函数)
    + mapStateToprops:接收state,对state进行预处理,函数返回的数据作为props传递给组件
    + mapDispatchToProps:接收dispatch,预处理dispatch,返回的方法集合作为props传递给组件
  + 本质是一个柯里化函数,返回的函数中再返回一个高阶函数
    + 返回的高阶函数中,取出上下文中的store,帮我们做了一些处理
    + 调用subscribe订阅组件更新事件(基于函数组件的useState + useEffect)
    + 调用传入的mapStateToProps函数对state进行处理,得到nextState,并作为参数传递给子组件
    + 对dispatchProps进行处理:
      + 如果传入的mapDispatchToProps是一个函数,就调用该函数并传入dispatch,由该函数手动分发action
      + 如果传入的是对象,则可能是action,调用bindActionCreators对actions进行自动分发!
  + 高阶函数组件返回值就是调用传入的组件,并将stateProps和dispatchProps作为props传给该组件
*/
export function connect (mapStateToProps, mapDispatchToProps) {
  if (!mapStateToProps) {
    mapStateToProps = () => ({})
  }
  if (!mapDispatchToProps) {
    mapDispatchToProps = (dispatch) => ({ dispatch })
  };
  return function currying (Component) {
    return function HOC (props) {
      const { store } = useContext(ContextZYR)
      const { getState, dispatch, subscribe } = store;

      let [, forceUpdate] = useState(0);
      useEffect(() => {
        let unsubscribe = subscribe(() => {
          forceUpdate(+new Date());
        });
        return () => unsubscribe();;
      }, []);

      const state = getState()
      const nextState = useMemo(() => {
        return mapStateToProps(state);
      }, [state]);

      let dispatchProps = {};
      if (typeof mapDispatchToProps === "function") {
        dispatchProps = mapDispatchToProps(dispatch);
      } else {
        dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
      }

      return <Component
        {...props}
        {...nextState}
        {...dispatchProps}
      />;
    };
  };
}

/* 
  绑定action
  + 调用dispatch派发指定的action
*/
function bindActionCreator (actionCreator, dispatch) {
  return function (this, ...args) {
    return dispatch(actionCreator.apply(this, args))
  }
}

/* 
  绑定actions集合
  + connect的第二个参数可以是action!免去一系列麻烦的dispatch操作!
  + 自动分发action,如果是单条action,就调用bindActionCreator自动绑定dispatch
  + 如果是多条action(对象),就遍历并调用bindActionCreator自动绑定dispatch
*/
export function bindActionCreators (actions, dispatch) {
  if (typeof actions === 'function') {
    return bindActionCreator()
  }
  if (typeof actions !== 'object' || actions === null) {
    throw new Error('参数传递错误')
  }
  for (let key in actions) {
    const actionCreator = actions[key]
    if (typeof actionCreator === 'function') {
      return bindActionCreator(actionCreator, dispatch)
    }
  }
}

三、@reduxjs/toolkit

1.关系和概念

react-redux:react官方推出的redux绑定库,react-redux将所有组件分为两大类:UI组件和容器组件,其中所有容器组件包裹着UI组件,构成父子关系。容器组件负责和redux交互,里面使用redux API函数,UI组件负责页面渲染,不使用任何redux API。容器组件会给UI组件传递redux中保存对的状态和操作状态的方法

@reduxjs/toolkit:Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集。默认集成了reduxThunk

上面的废话总结:react-redux是react官方推荐的 基于redux团队增强的插件;@reduxjs/toolkit是redux团队推荐的

经验之谈:react-redux需要一定的js功底;@reduxjs/toolkit有点像vue的组合式api,适合小白

2.使用

关键词:

xxxSlice = createSlice({ name、initialState、reducers })

configureStore({ xxxSlice.reducer、middleware })

useSelect、useDispatch、dispatch.xxxSlice .actions.xxx

1.创建切片

关键词:createSlice、name、initialState、reducers、actions

/* 创建一个切片数据 */
const userSlice = createSlice({
  name: "user",
  initialState: { code:'', name:''},
  reducers: {
    setUserName(state, { payload }) {
      state.name = payload?name;
    }
  },
});

/* 从创建好的切片数据中,导出dispatch需要用到的action,不用我们再手动写 */
export const { setUserName } = userSlice.actions;

/* 从切片数据中导出reducer,这个reducer就是当前模块的reducer */
export default userSlice.reducer;

2.配置store

关键词:configureStore、reducer、middlewaer

import { configureStore } from '@reduxjs/toolkit';
import reduxThunk from 'redux-thunk';
import user from './user';

const store = configureStore({
    reducer: { user },
    /* 
      使用中间件:
      + 如果我们不指定任何中间件,则默认集成了reduxThunk;
      + 但是一但设置,会整体替换默认值,需要手动指定thunk中间件!
    */
    middleware: [reduxThunk]
});
export default store;

3.上下文注入

关键词:Provider、value、store

/* 同样是通过Provider + value注入 */
import React from 'react';
import ReactDOM from 'react-dom/client';
import Task from './views/Task';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import store from './store';
import { Provider } from 'react-redux';
import './index.less';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <ConfigProvider locale={zhCN}>
        <Provider store={store}>
            <Task />
        </Provider>
    </ConfigProvider>
);

4.组件中使用

关键词:useSelect、useDispatch

import { useSelector, useDispatch } from "react-redux";
import { setUserName } from '@/store/user'

const Demo = () => {
  const dispatch = useDispatch();
  const { name } = useSelector((s: Store) => s.user);
  
  const handler = (name) => {
      dispatch(setUserName(name))
  }
  
  return (
    <>
        <span>{ name }</span>
        <button onClick={()=>handler('王麻子')}>点我</button>
    </>
  )
}

四、mobx

ai生成的废话描述,别浪费时间 继续往下看。

MobX 通过透明的函数响应式编程,使得状态管理变得简单和可扩展。 MobX 背后的哲学是 React 和 MobX 是一对强力组合, React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染, 而 MobX 提供机制来存储和更新应用状态供 React 使用

就是一种状态管理方案,v5版本基于decorator,由于decorator目前还在第二阶段,未来到底要怎么搞还不确定,所以不用了;v6版本改为了proxy

1.装饰器版本

创建store

关键词:observable - 装饰属性, action - 装饰修改属性的方法

import React from "react";
import { observable, action } from 'mobx';

/* 创建一个容器 */
class Store {
    // 公共状态
    @observable num = 10;
    // 修改公共状态的方法
    @action change() {
        this.num++;
    }
}
let store = new Store;

函数组件使用

关键词:observer

import React from "react";
import { observer } from 'mobx-react';

const Demo = observer(function Demo() {
    return <div>
        <span>{store.num}</span>
        <br />
        <button onClick={() => {
            store.change();
        }}>按钮</button>
    </div>;
});

export default Demo;

类组件使用

关键词:observer

@observer
class Demo extends React.Component {
    render() {
        return <div>
            <span>{store.num}</span>
            <br />
            <button onClick={() => {
                store.change();
            }}>按钮</button>
        </div>;
    }
}

特性解读

class Store {
    @observable x = 10;
    
    // action:修饰函数的装饰器,它让函数中的状态更改变为“异步批处理”「真实项目中,状态值的更改,我们建议都使用这种方式!」
    // action.bound:保证函数无论如何执行,函数中的this都是Store的实例
    
    @action change() {
       this.x = 100
    }
    @action.bound change1() {
       this.x = 100
    }
    
    // computed:装饰器,创建一个具备计算缓存的计算属性
    @computed get total() {
        console.log('total run');
        return this.count * this.price;
    }
}
let store = new Store;

setTimeout(() => {
    /!* // 修改多个状态,会让autorun监听器执行多次!!
    store.x = 1000;
    store.y = 2000; *!/

    // store.change(); //this->store
    let func = store.change;
    func(); //没有设置bound this->undefined ;设置bound后 this->store
    
    let func1 = store.change1;
    func1(); // store

    // 基于runInAction可以实现出和@action一模一样的效果!!
    runInAction(() => {
        store.x = 1000;
        store.y = 2000;
    });
}, 1000);

// 监听器 类似watch
autorun(() => {
    console.log('autorun:', store.x);
});

// reaction:和autorun一样,都是监听器,提供更细粒化的状态监测「默认是不会执行的」
reaction(
    () => [store.x, store.total],
    () => {
        console.log('reaction:', store.x, store.total);
    }
);

全局注入

和redux一样,也需要在根组件上加Provider

import React from 'react';
import ReactDOM from 'react-dom/client';
import Task from './views/Task';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import './index.less';
import { Provider } from 'mobx-react';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <ConfigProvider locale={zhCN}>
        <Provider {...store}>
            <Task />
        </Provider>
    </ConfigProvider>
);

2.proxy版本

api名字都一样,只是改为函数调用,不是之前的装饰器了

观测数据

经过observable处理后的数据,是基于ES6Proxy做过数据劫持的,这样我们后期修改状态值,就可以在SETTER函数中去做一些特殊处理,例如:把依赖其值的监听器触发执行...

let obj = observable({
    x: 10,
    y: 20
});

创建监听器

创建监听器,对对象进行监听,当对象中的某个成员发生改变,触发回调函数执行「前提是:对象是基于observable修饰的,把其变为可监听的了」

observe(obj, change => {
    console.log(change);
});

观测原始值

observable无法直接装饰原始值,需要使用observable.box处理

let x = observable.box(10);
console.log(x); //=>ObservableValue
console.log(x.get()); //10
observe(x, change => {
    console.log(change);
});
x.set(1000); 

class处理

export default class TaskStore {
    constructor(root) {
        this.root = root;
        /* // 基于makeObservable给状态和方法设置装饰效果!!
        makeObservable(this, {
            taskList: observable,
            queryAllTaskAction: action.bound,
        }); */
        // makeAutoObservable:对makeObservable的加强,可以自己给状态和方法设置装饰,等同于上述操作
        makeAutoObservable(this);
    }
    taskList = null;
    // 异步获取全局任务
    async queryAllTaskAction() {
        let list = [];
        try {
            let result = await getTaskList(0);
            if (+result.code === 0) list = result.list;
        } catch (_) { }
        runInAction(() => {
            this.taskList = list;
        });
    }
}

五、veract

上面这些 都很繁琐!

如果你会vue3,那我给你推荐一个插件:veract 在react中用vue的响应数据!

插件地址:www.npmjs.com/package/ver… API文档也在这里