一个适用于项目中的 mobx 解决方案:react-mobxer

在项目中简单的使用 mobx 解决问题的方案
更新于: 2023-07-13 21:19:01
项目主页: https://github.com/afeiship/react-mobxer

快用

最简单的在项目中使用此工具套件。

npx @jswork/mobxer-init

Why

  • mobx 里面的方法很多,我不知道什么时候应该使用哪一个
  • 另外:很多方法有新的,或者其它方案替换,但我需要熟悉
  • mobx/react-mobx/react-mobx-lite,新手会不清楚应该安装哪种组合

安装

yarn add @jswork/react-mobxer
yarn add --dev @types/webpack-env

使用

  • 在 webpack 的环境下,因为依赖于 require.context
  • harmony 会自动生成 nx.$root 方便操作 mobx 对象

使用

  • ConfigProvider: 配置 mobx
  • useGlobal: 共享 store
  • useLocal: 局部 store
  • obs: observer 的别名,来源 react-mobx

index.tsx

harmony 这个是为 @jswork/next 特别留的接口。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import App from '@/app';
import reportWebVitals from './reportWebVitals';
import { HashRouter } from 'react-router-dom';
import { ConfigProvider } from '@jswork/react-mobxer';
import "@jswork/next"

const ctx = require.context('./stores/', true, /\.(ts|js|tsx|jsx)$/);
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
  <HashRouter>
    <ConfigProvider context={ctx} harmony>
      <App />
    </ConfigProvider>
  </HashRouter>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

类型 global.d.ts

由于 store 里的具体内容是在项目中生成的,所以这里需要动态维护一个 GlobalStore 的类型 interface

interface IGlobalStore {
  user: import('../stores/user');
  auth: import('../stores/auth');
}

interface NxStatic {
  $root: IGlobalStore;
}

定义 store

import { makeAutoObservable } from 'mobx';

export default class AuthStore {
  static storeKey = 'special';
  special = 'special var';

  constructor() {
    makeAutoObservable(this);
  }
}

useGlobal

import { obs, useGlobal } from '@jswork/react-mobxer';

export const GlobalCtxComp = obs(() => {
  const { user, auth } = useGlobal<IGlobalStore>();
  return (
    <div>
      {user.username} + token: {auth.token}
    </div>
  );
});

export const AricComp = obs(() => {
  const { user, auth } = nx.$root;
  return <div>{user.username + ':' + auth.token} + aric.style</div>;
});

useLocal

  • 适用于管理多个 state
  • 也是 useReducer 的场景
import { useEffect } from 'react';
import { obs, useLocal } from '@jswork/react-mobxer';

export const ProfileMobx = obs(() => {
  const store = useLocal({
    loading: false,
    data: { login: '' },
  });

  useEffect(() => {
    store.loading = true;
    fetch('https://api.github.com/users/afeiship')
      .finally(() => (store.loading = false))
      .then((r) => r.json())
      .then((res) => {
        store.data = res;
      });
  }, []);

  if (store.loading) return <div>loading...</div>;
  return <div>username: {store.data.login}</div>;
});

watch

  • 对于一些复杂的对象,我们期望检测到每一个key的变化,然后处理一些逻辑
  • 用法: watch(store, fn, options);
  • 这个是我们默认的 options: const defaults: IAutorunOptions = { delay: 10 };

以下是实现这个 watch 的实现原理: https://playcode.io/1430672

import React, { useEffect } from 'react';
import { observable, reaction, autorun, toJS } from 'mobx';

const store = observable({
  filters: {
    keywords: 'aaa',
    status: 'init',
  },
});

function watch(obj, callback) {
  return autorun(() => {
    callback(toJS(obj));
  });
}

// 也可以使用
reaction(
  () => {
    return JSON.stringify(store);
  },
  () => {
    console.log("obj:", store);
  }
);


export const App = () => {
  
  useEffect(() => {
    return watch(store.filters, filters => {
      console.log('filters changed:', filters);
    });
  }, []);

  return (
    <div>
      <h1>it works</h1>
      <button onClick={()=>{ store.filters.status = Math.random()}}>Change filters1 </button>
      <p>
        Filters: {store.filters.status1}, {store.filters.status2}
      </p>
    </div>
  );
};

实际项目中的使用: 普通风格

import React, { useEffect } from 'react';
import { watch, obsb } from "@jswork/react-mobxer";

const store = obsb({
  filters: {
    keywords: 'aaa',
    status: 'init',
  },
});

export const App = () => {
  useEffect(() => {
    return watch(store.filters, (plain) => {
      console.log("filters changed:", plain);
    });
  }, []);

  return (
    <div>
      <h1>it works</h1>
      <button onClick={()=>{ store.filters.status = Math.random()}}>Change filters1 </button>
      <p>
        Filters: {store.filters.status1}, {store.filters.status2}
      </p>
    </div>
  );
};

项目使用,useWatch 风格(react hook 风格),适合开发一个小功能,需要用到的 deep-change 的观测。

import React, { useEffect } from 'react';
import { useWatch, obsb } from "@jswork/react-mobxer";

const store = obsb({
  filters: {
    keywords: 'aaa',
    status: 'init',
  },
});

export const App = () => {
  useWatch(store.filters, (plain) => {
    console.log("filters changed:", plain);
  });

  return (
    <div>
      <h1>it works</h1>
      <button onClick={()=>{ store.filters.status = Math.random()}}>Change filters1 </button>
      <p>
        Filters: {store.filters.status1}, {store.filters.status2}
      </p>
    </div>
  );
};

脚手架

yo @jswork/react-app:mobxer

参考