状态管理 - useStore

49 阅读3分钟

为什么需要状态管理?

  • 数据中心化管理。数据流向可预测,暴露出特定函数来操作数据。
  • 数据持久化管理。在各个模块中都能共享数据,减少请求或计算。
  • 数据同步化管理。实时更新数据,数据发生变更可实时更新状态。

flux 架构

flux 是一种架构思想,用来解决数据结构问题,类似于 MVC 架构,flux 核心理念是单向数据流。

flux.png

  • View:视图层 UI State。
  • Action:动作行为描述。
  • Dispatcher:事件派发器。
  • Store:数据中心。

用户在 View 层发起一个 Action 对象给 Dispatcher,Dispatcher 接收到 Action 并要求 Store 做相应的更改,Store 做出相对应更新,然后发出一个 changeEvent,View 接收到 changeEvent 事件后,更新页面。

简单实现

发布订阅模式

用于实时更新数据。

  • on:订阅数据
  • emit:发布数据
  • off:取消订阅
const emitter = () => {
  const hub = {};
  const on = (name, cb) => {
    if (!hub[name]) {
      hub[name] = [];
    }
    if (hub[name].indexOf(cb) === -1) {
      hub[name].push(cb);
    }
  };
  const emit = (name, data) => {
    if (hub[name]) {
      hub[name].map(cb => cb(data));
    }
  };
  const off = (name, cb) => {
    if (hub[name]) {
      const index = hub[name].indexOf(cb);
      if (index > -1) {
        hub[name].splice(index, 1);
      }
    }
  };
  return {on, emit, off};
};

createStore

提供数据操作函数。

  • getState:获取数据
  • setState:更新数据
  • subscribe:订阅数据
  • unsubscribe:取消订阅
const createStore = () => {
  const {on, emit, off} = emitter();
  const store = {};
  const getState = name => clone(store[name]);
  const setState = state => {
    if (typeof state === 'function') {
      state = state(clone(store));
    }
    const newState = clone(state);
    Object.keys(newState).map(key => {
      const oldItem = store[key];
      const newItem = newState[key];
      const item = isObject(newItem) && isObject(oldItem) ? mergeOwnProp(oldItem, newItem) : newItem;
      store[key] = item;
    });
  };
  const subscribe = (name, cb) => {
    on(name, cb);
    return () => off(name, cb);
  };
  return {getState, setState, subscribe, unsubscribe: off};
};

useStore

import {createStore} from '@huxy/utils';

const store = createStore();

const createContainer = store => (name, initState) => {
  const [state, setState] = useState(initState);
  const update = useCallback(result => store.setState({[name]: typeof result === 'function' ? result(store.getState(name)) : result}), []);
  const subscribe = useCallback(callback => store.subscribe(name, callback), []);
  const clean = useCallback((name = name) => store.clean(name), []);
  useEffect(() => {
    store.subscribe(name, result => setState(result));
  }, []);
  return [state, update, subscribe, clean];
};

const useStore = createContainer(store);

使用

直接引用:

import {store} from '@huxy/utils';
import {useStore} from '@huxy/use';

自定义 store :

import {createStore, createContainer} from '@huxy/utils';
import {createContainer as createUseContainer} from '@huxy/use';

export const container = createStore();

export const store = createContainer(container);
export const useStore = createUseContainer(container);

store 配置

定义:

import {useStore} from '@huxy/use';
// names
export const langName = 'lang-store';
export const themeName = 'theme-store';
export const menuTypeName = 'menuType-store';
export const i18nsName = 'i18ns-store';
export const userInfoName = 'userInfo-store';
export const permissionName = 'permission-store';
export const routersName = 'routers-store';
// stores
export const useLangStore = initState => useStore(langName, initState);
export const useMenuTypeStore = initState => useStore(menuTypeName, initState);
export const useThemeStore = (initState = {}) => useStore(themeName, initState);
export const useI18nsStore = (initState = {}) => useStore(i18nsName, initState);
export const useUserInfoStore = (initState = {}) => useStore(userInfoName, initState);
export const usePermissionStore = (initState = []) => useStore(permissionName, initState);
export const useRoutersStore = (initState = []) => useStore(routersName, initState);

使用示例:

import {useI18nsStore} from '@app/store/stores';

const Intls = ({keys, children}) => {
  const [i18ns] = useI18nsStore();
  return (keys && i18ns.getValue(keys)) ?? children ?? '';
};

export default Intls;

使用示例

例如:使用 useStore 来管理已读未读消息,未读消息数量在头部 nav 栏展示,当进入消息列表页面并查看消息时,实时更新 nav 栏未读信息条数。

message.png

nav 栏未读消息数量:

const Notify = props => {
  const {router} = useRoute();
  const [, , subscribe] = useStore('huxy-notify', []);
  const [count, setCount] = useState(0);
  useEffect(() => {
    const getMes = async () => {
      const {result} = await apiList.listMessage({current: 1, size: 1000});
      const mes = (result?.list ?? []).filter(item => item.active == 0);
      setCount(mes.length);
    };
    const cancelSub = subscribe(result => {
      const mes = (result ?? []).filter(item => item.active == 0);
      setCount(mes.length);
    });
    getMes();
    return cancelSub;
  }, []);
  const handleClick = e => {
    router.push({
      path: '/playground/messages',
      state: {tab: 0},
    });
  };
  return (
    <a className="notify-item" title="notify" onClick={handleClick}>
      <span className="node-icon">
        <Badge count={count} size="small">
          <BellOutlined />
        </Badge>
      </span>
    </a>
  );
};

消息列表:

const MessageList = props => {
  const historyState = props.history.getState?.();
  const [, setNotify] = useStore('huxy-notify');
  const [active, setActive] = useState(historyState?.tab ?? 1);

  const handleCheck = async item => {
    const items = item ? [item] : selectedRows;
    const ids = items.map(v => v._id);
    const {code, result, message: msg} = await apiList.markActived({ids});
    if (code === 200) {
      setNotify(result);
      message.success(msg);
      setSelectedRows([]);
      update({current: 1});
    }
  };
  ...
  return (
    <div className="messages-list">
      <TabHeader tabs={tabs} switchTab={switchTab} activekey={active} />
      <List {...tableProps} />
    </div>
  );
};