【React】使用Context实现全局store

492 阅读1分钟

使用useContext实现全局store

React中实现全局store的库有redux等,不过本人更倾向于使用React自身的方法来实现

简单介绍

使用React.createContext方法定义一个Context对象,比如AppContext

App组件内使用AppContext.Provider来为AppContext提供值。

定义useXxxStorehook作为全局store,内部可以定义数据与方法,并通过返回数据或方法使外部可以查询与修改全局store

App组件中使用此hook,并将结果作为AppContext的一个属性值。

App组件的所有子组件中均可以使用AppContext并获取store

示例代码

1、定义usePersonStore,目录为src/store/persons.store.ts

import { STORE_KEYS } from '@/configs/store.config';
import { Person } from '@/types/persons';
import { useLocalStorageState } from 'ahooks';
import { useCallback, useMemo } from 'react';

export function usePersonsStore() {
  const [persons, setPersons] = useLocalStorageState<Person[]>(
    STORE_KEYS.PERSONS_STORE,
    {
      defaultValue: [],
    },
  );

  const showPersons = useMemo(
    () => persons.filter(showPerson => !showPerson.archived),
    [persons],
  );

  function addPerson(person: Person) {
    setPersons([...persons, person]);
  }

  function removePerson(person: Person) {
    person.archived = true;
    setPersons([...persons]);
  }
  
  function mutatePerson(person: Person) {
    const target = persons.find(p => p.id === person.id);
    if (target) {
      Object.assign(target, person);
      setPersons([...persons]);
    }
  }
  
  const getPersonById = useCallback(
    function (id: string | undefined) {
      if (!id) return undefined;
      return persons.find(p => p.id === id);
    },
    [persons],
  );

  const getPersonsByIds = useCallback(
    (ids: (string | undefined)[]) => ids.map(getPersonById),
    [getPersonById],
  );

  return {
    showPersons,
    addPerson,
    removePerson,
    mutatePerson,
    getPersonById,
    getPersonsByIds,
  };
}

2、定义AppContext,目录为src/store/index.context.ts

import React from 'react';
import { useGameStore } from './game.store';
import { useHistoryGamesStore } from './historyGames.store';
import { useMessageStore } from './message.store';
import { usePersonsStore } from './persons.store';

export const AppContext = React.createContext<{
  personStore: ReturnType<typeof usePersonsStore>;
  gameStore: ReturnType<typeof useGameStore>;
  messageStore: ReturnType<typeof useMessageStore>;
  historyGamesStore: ReturnType<typeof useHistoryGamesStore>;
}>(undefined as any);

export function useAppContext() {
  const personStore = usePersonsStore();
  const gameStore = useGameStore();
  const messageStore = useMessageStore();
  const historyGamesStore = useHistoryGamesStore();
  return { personStore, gameStore, messageStore, historyGamesStore };
}

3、在App.tsx中使用AppContext

import { useRoutes } from 'react-router-dom';
import MessageHint from './components/MessageHint';
import { routesConfig } from './router.config';
import { AppContext, useAppContext } from './store/index.context';

function App() {
  const routes = useRoutes(routesConfig);
  const appContextValue = useAppContext();

  return (
    <AppContext.Provider value={appContextValue}>
      {routes}
      {appContextValue.messageStore.showMessage && <MessageHint />}
    </AppContext.Provider>
  );
}

export default App;

4、在子组件中使用store,以一个消息提示组件为例

import React, { useCallback, useContext, useMemo } from 'react';
import classes from './index.module.css';
import calssnames from 'classnames';
import { AppContext } from '@/store/index.context';

export default function MessageHint() {
  const { messageStore } = useContext(AppContext);

  const classNames = useMemo(() => {
    return calssnames({
      [classes.MessageHint]: true,
      animate__animated: true,
      animate__slideInDown: messageStore.showMessage,
      animate__slideOutUp: messageStore.startHide,
    });
  }, [messageStore]);

  const hanldeEnd = useCallback(() => {
    if (messageStore.startHide) {
      messageStore.setStartHide(false);
      messageStore.setShowMessage(false);
    }
  }, [messageStore]);

  return (
    <div className={classNames} onAnimationEnd={hanldeEnd}>
      {messageStore.message}
    </div>
  );
}