loading
,通常是在組件中用一個變量( 如isLoading
)來保存請求數據時的 loading
狀態,請求 api
前將 isLoading
值設置爲 true
,請求 api
後再將 isLoading
值設置爲 false
,從而對實現 loading
狀態的控制,如如下代碼:import { Spin, message } from 'antd';
import { Bind } from 'lodash-decorators';
import * as React from 'react';
import * as api from '../../services/api';
class HomePage extends React.Component {
state = {
isLoading: false,
homePageData: {},
};
async componentDidMount () {
try {
this.setState({ isLoading: true }, async () => {
await this.loadDate();
});
} catch (e) {
message.error(`獲取數據失敗`);
}
}
@Bind()
async loadDate () {
const homePageData = await api.getHomeData();
this.setState({
homePageData,
isLoading: false,
});
}
render () {
const { isLoading } = this.state;
return (
<Spin spinning={isLoading}>
<div>hello world</div>
</Spin>
);
}
}
export default HomePage;
複製代碼
api
都要寫以上相似的代碼,顯然會使得項目中重複代碼過多,不利於項目的維護。所以,下文將介紹全局存儲 loading
狀態的解決方案。fetch
請求(傳送門👉:react + typescript 項目的定製化過程)及相關數據請求相關的 api
mobx
作狀態管理@initLoading
來實現 loading
狀態的變動和存儲readonly
用來裝飾類的 name
方法。class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
複製代碼
readonly
一共能夠接受三個參數:
target
是類的原型對象,在這個例子中是 Person.prototype
,裝飾器的本意是要「裝飾」類的實例,可是這個時候實例還沒生成,因此只能去裝飾原型(這不一樣於類的裝飾,那種狀況時 target
參數指的是類自己)name
是所要裝飾的屬性名descriptor
是該屬性的描述對象function readonly(target, name, descriptor){
// descriptor對象原來的值以下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 相似於
Object.defineProperty(Person.prototype, 'name', descriptor);
複製代碼
readonly
會修改屬性的描述對象(descriptor
),而後被修改的描述對象再用來定義屬性。@log
裝飾器,能夠起到輸出日誌的做用:class Math {
@log
add(a, b) {
return a + b;
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
複製代碼
@log
的做用就是在執行原始的操做以前,執行一次 console.log
,從而達到輸出日誌的目的。項目中的狀態管理不是使用 redux
而是使用 mobx
,緣由是 redux
寫起來十分繁瑣:html
side-effects
,要用 redux-saga
或者 redux-thunk
來作異步業務邏輯的處理immutable
相關的庫保證 store
的性能,用 reselect
來作緩存機制redux
的替代品是 mobx
,官方文檔給出了最佳實踐,即用一個 RootStore
關聯全部的 Store
,解決了跨 Store
調用的問題,同時能對多個模塊的數據源進行緩存。react
在項目的stores
目錄下存放的 index.ts
代碼以下:es6
import MemberStore from './member';
import ProjectStore from './project';
import RouterStore from './router';
import UserStore from './user';
class RootStore {
Router: RouterStore;
User: UserStore;
Project: ProjectStore;
Member: MemberStore;
constructor () {
this.Router = new RouterStore(this);
this.User = new UserStore(this);
this.Project = new ProjectStore(this, 'project_cache');
this.Member = new MemberStore(this);
}
}
export default RootStore;
複製代碼
mobx
的用法可具體查看文檔 👉mobx 中文文檔,這裏不展開介紹。loading
狀態控制的相關代碼與組件自己的交互邏輯並沒有關係,若是還有更多相似的操做須要添加劇復的代碼,這樣顯然是低效的,維護成本過高。initLoading
裝飾器,用於包裝須要對 loading
狀態進行保存和變動的類方法。store
控制和存儲 loading
狀態,具體地:
BasicStore
類,在裏面寫 initLoading
裝飾器loading
狀態的不一樣模塊的 Store
須要繼承 BasicStore
類,實現不一樣 Store
間 loading
狀態的「隔離」處理@initLoading
裝飾器包裝須要對 loading
狀態進行保存和變動的不一樣模塊 Store
中的方法Store
存儲的全局 loading
狀態stores
目錄下新建 basic.ts
文件,內容以下:import { action, observable } from 'mobx';
export interface IInitLoadingPropertyDescriptor extends PropertyDescriptor {
changeLoadingStatus: (loadingType: string, type: boolean) => void;
}
export default class BasicStore {
@observable storeLoading: any = observable.map({});
@action
changeLoadingStatus (loadingType: string, type: boolean): void {
this.storeLoading.set(loadingType, type);
}
}
// 暴露 initLoading 方法
export function initLoading (): any {
return function (
target: any,
propertyKey: string,
descriptor: IInitLoadingPropertyDescriptor,
): any {
const oldValue = descriptor.value;
descriptor.value = async function (...args: any[]): Promise<any> {
let res: any;
this.changeLoadingStatus(propertyKey, true); // 請求前設置loading爲true
try {
res = await oldValue.apply(this, args);
} catch (error) {
// 作一些錯誤上報之類的處理
throw error;
} finally {
this.changeLoadingStatus(propertyKey, false); // 請求完成後設置loading爲false
}
return res;
};
return descriptor;
};
}
複製代碼
@initLoading
裝飾器的做用是將包裝方法的屬性名 propertyKey
存放在被監測數據 storeLoading
中,請求前設置被包裝方法的包裝方法 loading
爲 true
,請求成功/錯誤時設置被包裝方法的包裝方法 loading
爲 false
。ProjectStore
爲例,若是該模塊中有一個 loadProjectList
方法用於拉取項目列表數據,而且該方法須要使用 loading
,則項目的stores
目錄下的 project.ts
文件的內容以下:import { action, observable } from 'mobx';
import * as api from '../services/api';
import BasicStore, { initLoading } from './basic';
export default class ProjectStore extends BasicStore {
@observable projectList: string[] = [];
@initLoading()
@action
async loadProjectList () {
const res = await api.searchProjectList(); // 拉取 projectList 的 api
runInAction(() => {
this.projectList = res.data;
});
}
}
複製代碼
HomePage
組件增長數據加載時的 loading
狀態顯示:import { Spin } from 'antd';
import { inject, observer } from 'mobx-react';
import * as React from 'react';
import * as api from '../../services/api';
@inject('store')
@observer
class HomePage extends React.Component {
render () {
const { projectList, storeLoading } = this.props.store.ProjectStore;
return (
<Spin spinning={storeLoading.get('loadProjectList')}>
{projectList.length &&
projectList.map((item: string) => {
<div key={item}>
{item}
</div>;
})}
</Spin>
);
}
}
export default HomePage;
複製代碼
mobx-react
的 @inject
和 @observer
裝飾器來包裝 HomePage
組件,它們的做用是將 HomePage
轉變成響應式組件,並注入 Provider
(入口文件中)提供的 store
到該組件的 props
中,所以可經過 this.props.store
獲取到不一樣 Store
模塊的數據。
@observer
函數/裝飾器能夠用來將 React
組件轉變成響應式組件@inject
裝飾器至關於 Provider
的高階組件,能夠用來從 React
的context
中挑選 store
做爲 props
傳遞給目標組件this.props.store.ProjectStore.storeLoading.get('loadProjectList')
來獲取到 ProjectStore
模塊中存放的全局 loading
狀態。loading
狀態的展現;當有錯誤時,全局可對錯誤進行處理(錯誤上報等)。