使用 mobx-react-stores 開發 react 應用

原文地址:https://acme.top/mobx-react-stores-docreact

前言

mobx-react-stores 是爲了方便將 antd pro 改成使用 mobx 而開發的,裏面的思路和代碼大量的參考了 dva 和 umi 的實現ios

安裝

npm i mobx-react-stores

使用

獲取 stores 實例對象shell

import {stores} from 'mobx-react-stores';

狀態管理

mobx-react-stores 能夠用做狀態管理,也是其最核心的目的,接口設計的比較簡單,主要就兩個:npm

  1. stores.add(model: object | Promise, namespaceAlias: null | string) - 用來向 stores 注入待管理的 model 對象
  • 參數 model 若是爲對象則必須包含一個 string 類型的 namespace 屬性,用做其在 stores 中的惟一標識,也就是 stores 中的屬性名稱
  • 參數 model 也能夠爲一個 Promise 對象,Promise 必須返回一個包含 string 類型的 namespace 屬性
  • 參數 namespaceAlias 可選,用來爲 model 起別名,若是存在則 model 能夠不包含 namespace 屬性
  1. dispatch({type, payload}) - 相似 dvadispatch,用來經過 namespace/action 這種形式快速訪問 model 對象中的函數
  • 參數 type 爲字符串類型,格式爲 namespace/action
  • 參數 payload 爲任意類型,可選,用於向所調用的函數中傳遞參數,namespace/action 函數接收到的參數就是 payload
  • dispatch 通常直接被注入到了上下文中,方便組件調用

監聽加載狀態

mobx-loading 能實現相似 dva-loading 的效果,方便監聽 model 及其 action 的執行狀態,減小開發人員對組件 show hide 的操做axios

  • 獲取 loadingStore 對象的方法
import {loadingStore} from 'mobx-loading'

mobx-react-stores 默認集成了 mobx-loading,做爲 stores 的內置屬性,namespace 爲 loading,因此能夠直接從 stores 中獲取 loadingStore 對象數組

const {loading} = stores;
  • 使用方式
// 經過 actions 獲取指定 namespace/action 的當前加載狀態
loading.actions['goods/fetchList']

// 經過 models 獲取指定 namespace 的當前加載狀態
// namespace 下任何 action 加載狀態爲 true,則此 namespace 的加載狀態即爲 true
loading.models.goods

// 經過 global 獲取全局的加載狀態
// 只要任何一個 model 的狀態爲 true,則 global 的狀態即爲 true
loading.global

// 從 mobx-react-stores 和 mobx-loading 中均可以獲取 namespace 裝飾器
// 用做 class,方便向 class 中注入 namespace 屬性
import {namespace} from 'mobx-loading';
import {namespace} from 'mobx-react-stores';
  • 使用示例
// 獲取 namespace 和 loading 裝飾器
import {namespace, loading} from 'mobx-react-stores';
import {fetchRandomUser} from "@/services/demo";

// 默認會爲 class 添加 namespace 屬性,值爲將 class 類名轉換爲首字母小寫的字符串
// 此處 namespace 值爲 randomUser
@namespace
class RandomUser {

    user;

    message;

    // 經過 loading 裝飾器,向 loadingStore 中注入當前 action 的加載狀態
    @loading
    fetchUser = async () => {

        this.user = null;
        this.message = null;

        const response = await fetchRandomUser().catch(this.onRejected);

        if (response.results) {
            const user = response.results[0];

            this.change({
                user: {
                    name: `${user.name.first} ${user.name.last}`,
                    email: user.email,
                    picture: user.picture.large
                },
                message: null
            });
        }
    }

    change = ({user, message}) => {
        this.user = user;
        this.message = message;
    }

    onRejected = (e) => {
        return {
            status: 500,
            message: e.message
        }
    }
}

export default new RandomUser();

// -----------------------RandomUser.js--------------------------------

import React from 'react';
import {inject} from 'mobx-react'

@inject(({stores: {dispatch, loading, randomUser}}) => {
    return {
        dispatch,
        randomUser,
        loading: loading.models.randomUser
    }
})
class RandomUser extends React.PureComponent {

    onFetchUser = () => {
        const {dispatch} = this.props;
        dispatch({
            type: 'randomUser/fetchUser'
        })
    }

    render() {
        const {loading, randomUser} = this.props;

        if (loading) {
            // do someting
        }else{
            const {user, message} = randomUser;
            // do someting
        }
    }
}

export default RandomUser;
  • @loading(names: string | array | null) - loading 裝飾器做用於 model 內的函數,能夠接收字符串和數組的參數antd

  • 例如: @loading('randomUser/fetchList')、@namespace(['randomUser', 'fetchList']) 與上面的示例代碼結果同樣app

  • loading 裝飾器優先使用當前 class 的 namespace 屬性和 函數的名稱(首字母小寫)生成此 action 的惟一標識,無 namespace 屬性則使用 class 的名稱(首字母小寫)。上門示例中 loading 無參則其函數的加載狀態可由 loadingStore.actions['randomUser/fetchList'] 取得less

上面示例中的 loading 是裝飾器,與 stores.loading 不一樣。stores.loading 是 loadingStore 注入 stores 後的屬性dom

路由管理

爲了方便管理路由以及向 stores 中注入 model,mobx-react-stores 提供了相似 umi 管理路由的方法,不過沒有像 umi 那樣去生成臨時文件,而是更直接的方式

建議在 src 目錄下新建 default.router.js 文件來存放路由,若是模塊多而複雜,能夠將各個模塊的路由分散開來而後聚合到這個文件中

路由示例

export default [
    // user
    {
        path: '/user',
        // 佈局文件
        component: () => import('./layouts/UserLayout'),
        LoadingComponent = require('./components/PageLoading').default,
        Routes: [require('./pages/Authorized').default],
        authority: ['admin', 'user'],
        routes: [
            // 從 /user 重定向到 /user/login
            {path: '/user', redirect: '/user/login'},
            {path: '/user/login', name: 'login', component: () => import('./pages/User/Login')},
            {
                name: 'register',
                path: '/user/register',
                models: () => [import('./pages/User/models/register')],
                component: () => import('./pages/User/Register')
            },
            {
                name: 'register.result',
                path: '/user/register-result',
                component: () => import('./pages/User/RegisterResult'),
            },
        ],
    }
]
  • name - 路由名稱,用於生成菜單,name 和 path 都存在的纔會生成菜單 ~ Antd Pro 這麼定義的
  • path - 路由的路徑
  • models - model 對象集合,支持按需加載,會與 component 一同加載,支持:() => [import('xxx/model1'), import('xxx/model2')]() => import('xxx/model1')[() => import('xxx/model1'), () => import('xxx/model2')] 等形式。主要方便於子路由的 models 和上級路由 models 進行數組合並,一些公共的 models 能夠放在上級路由中,方便管理。
  • component - 組件,若是有下級路由,則是下級路由的佈局文件,支持異步按需加載(需提供一個函數,相似:()=>import('xxxx'))
  • LoadingComponent - 異步按需加載組件時的加載效果組件
  • Routes - 方便自定義權限校驗路由,可參考 Antd Pro 去實現
  • authority - 訪問路由所需的權限,用於權限校驗,可參考 Antd Pro 去實現
  • routes - 子路由集合

使用路由

import {formatterRoutes, renderRoutes} from 'mobx-react-stores';
import router from './default.router.js';

// ... do someting

// 格式化
const routes = formatterRoutes(this.router, this.stores);
// 渲染路由
let children = renderRoutes(routes);

return (
    <Router history={history}>
        {children}
    </Router>
);

// ... do someting

國際化

mobx-react-stores 推薦使用 react-intl 來管理國際化

注入 Locale

import {stores, Locale} from 'mobx-react-stores';

// ... do someting

// 可參考 Antd Pro
const translations = {
    'en-US': {
        messages: {
            ...require('../locales/en-US.js').default,
        },
        locale: 'en-US',
        antd: require('antd/lib/locale-provider/en_US'),
        data: require('react-intl/locale-data/en'),
        momentLocale: '',
    },
    'zh-CN': {
        messages: {
            ...require('../locales/zh-CN.js').default,
        },
        locale: 'zh-CN',
        antd: require('antd/lib/locale-provider/zh_CN'),
        data: require('react-intl/locale-data/zh'),
        momentLocale: 'zh-cn',
    },
    // ... 其餘語言包
};

// 注入
stores.add(new Locale('zh-CN', translations));

// ... do someting

切換語言

stores.locale.change('zh-CN');

// 或者

dispatch({
    type: 'locale/change',
    payload: 'zh-CN'
});

集成

爲了簡化開發,方便使用 國際化、history 等,mobx-react-stores 提供了集成好的接口

使用示例

src 目錄下新建 index.js 文件做爲入口文件

// 兼容 IE九、IE10 ~ 若是須要的話請解開封印
// import 'react-app-polyfill/ie9';
// import 'react-app-polyfill/stable';
import React from 'react';
import {LocaleProvider} from 'antd';
import {app} from 'mobx-react-stores';
import get from 'lodash/get';
import './utils/axios'; // 初始化攔截器
import './global.less';
import * as serviceWorker from './serviceWorker';

app.stores = require('./models').default;
app.router = require('./default.router').default;

app.defaultLoadingComponent = require('./components/PageLoading').default;

// 渲染時回調,方便一些額外的操做,好比注入 antd 的國際化
app.renderCallback = (children) => {

    const {locale} = app.stores;

    const antd = get(app.stores, 'locale.translation.antd', undefined);

    if (antd) {
        return (
            <LocaleProvider locale={antd.default || antd}>
                {children}
            </LocaleProvider>
        );
    }

    return children;
};

app.render(document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

集成後

  • stores 中將得到 intlrouting 兩個對象,可用於國際化和操做路由
  • intl 是 react-intl 注入後的實例對象,可以使用 intl.formatMessage() 等函數
  • routing 能夠實現 routing.push()routing.replace() 等路由操做
相關文章
相關標籤/搜索