- 文中的藍色字體是相關內容的超鏈接,網址不另外列出,請放心點擊。
- 本文內容適合 MobX 和 React 新手,也歡迎 MobX 和 React 專家指導點評。
摘要
閱讀本文并實際上手編碼運行,你將解決如下幾個疑問:
- MobX如何進行狀態管理
- MobX如何管理異步操作對狀態的改變(fetch的使用)
- 如何創建一個簡單的MobX+React 示例應用(無路由)
工具
- JetBrains WebStorm
- Node.js
預備知識
- 熟悉 ES6 相關知識
- 了解 React 相關知識
- 會使用 create-react-app 腳手架創建一個 react 應用
MobX API
在開始搭建我們的第一個 MobX+React 應用前,首先需要大致地認識下 MobX 的 API ,了解 MobX 的核心概念,明白 MobX 的工作流程以及 常見陷阱 。有了相關知識儲備后再進行開發,往往能使我們編碼更加得心應手,少走彎路,不用再勞心勞力和 bug 斗智斗勇。故還請初次接觸 MobX 的讀者仔細閱讀 API 文檔。
請務必熟悉以下標簽的概念和作用
- @observable / observable()
- @observer / observer()
- @action / action()
- @computed
- @inject
示例應用需求以及效果展示
示例應用需求
本示例應用需求是實現通過輸入股票代碼查詢到相關股票信息并展示出來的功能。關于獲取股票相關信息,則通過新浪財經的證券股票數據接口進行獲取。由于該接口并未實現 cors 跨域資源共享標準 ,會存在跨域訪問的問題,所以我們在 自己編寫的后端項目中 獲取該接口返回的數據并實現cors跨域資源共享標準后傳遞給前端示例應用。(這只是一種跨域問題的解決方案,如果讀者有其他跨域問題的解決方案請自行修改實現。)
本示例應用為了簡單起見,并未添加相關css樣式文件,如讀者有興趣,可自行添加。
效果展示
啟動應用后界面如下(就是辣么粗獷……)
點擊查詢按鈕后如下所示(依舊辣么粗獷甚至有點不羈……)
第一步:使用 create-react-app 腳手架創建一個React應用
MobX采用的是ES7的裝飾器語法,目前還是一種實驗性的語法,使用 create-react-app 腳手架默認創建的項目是沒有開啟裝飾器語法的,故使用 custom-react-scripts 這種方式來創建項目。
命令行內輸入npx create-react-app my-app --scripts-version custom-react-scripts
創建項目。
其創建的項目根目錄路徑下有一個拓展名為 .env 的文件,這個文件中定義了 custom-react-scripts 為項目新增的特性。
打開該文件可以看到REACT_APP_DECORATORS = true;
表示啟用了裝飾器語法。
第二步:安裝相關依賴
查看項目目錄下的 package.json 文件,此時僅安裝了 react
和react-dom
依賴。
我們需要手動安裝mobx
和mobx-react
依賴,以及 MobX 開發調試工具。
在終端命令行切入到我們的項目目錄:
-
cd my-app
在終端命令行輸入以下命令進行安裝: npm install mobx
npm install mobx-react
npm install mobx-react-devtools --save-dev
安裝完相關依賴后我們就可以正式進入第一個入門實例項目的編寫了。
第三步:構造項目目錄
我們可以構造如下所示的項目目錄:
根目錄
|--src #開發文件目錄
? | |---components # react 組件目錄
? | | ??|--index.js # 組件文件
? | |--models # 領域模型目錄
? | | ??|--StockModel.js # 領域state文件
? | |--stores # 保存state的Store目錄
? | | ??|--index.js # 根Store目錄
? | | ??|--StockStore.js # 領域Store目錄
? | |--index.js
MobX 中的 state 一般會封裝在不同的 store 中,store 不僅保存了 state ,還保存了操作 state 的方法。對于與領域直接相關的 state ,一般會創建專門的 model 實體類,用于描述 state 。
第四步:設計 store 和 state
store 的職責是將組件使用的業務邏輯和狀態封裝到單獨的模塊,這樣組件就可以專注于UI渲染。
首先設計我們的model實體類StockModel,用于描述股票信息的state。
股票信息接口返回的是是一個字符串,我們決定在領域store中把它的數據解析出來并保存在數組里,所以在實體類中我們決定使用了一個fromArray方法來創建我們的StockModel實體。
//領域state
import {observable} from "mobx";
class StockModel {
store;//領域state所屬的領域store
@observable code;//股票代碼
@observable stockName;//股票名稱
@observable tPrice;//今日開盤價
@observable yPrice;//昨日收盤價
@observable nPrice;//今日當前價格
@observable hPrice;//今日最高價
@observable lPrice;//今日最低價
constructor(store,code,stockName,tPrice,yPrice,nPrice,hPrice,lPrice){
this.store = store;
this.code = code;
this.stockName = stockName;
this.tPrice = tPrice;
this.yPrice = yPrice;
this.nPrice = nPrice;
this.hPrice = hPrice;
this.lPrice = lPrice;
}
static fromArray(store,code,arr){
return new StockModel(
store,
code,
arr[0],
arr[1],
arr[2],
arr[3],
arr[4],
arr[5],
arr[6]);
}
}
export default StockModel;
設計完了具有相關領域 state 的實體類,我們需要創建一個保存state和相關操作 state 的領域 Store 。
在該領域Store內我們定義了一個 state [stocks
] 用以保存將要從服務獲取到的股票信息實體。我們還定義了一個動作 [fetchStockByCode
] 用于從后端獲取股票信息。需要特別注意的是,fetch是一個異步操作,所以需要編寫 異步action 來進行對state的操作。這里我們采用action
關鍵字來包裝promises
回調函數。即在獲取到數據后再發送一個action
操作 state [stocks
] 的變更。
//領域state
import { observable, action,} from "mobx";
import StockModel from "../models/StockModel";
class StockStore{
@observable stocks=[]; //數組元素是PostModel的實例
//從服務器獲取股票信息
@action fetchStockByCode(code){
//跨域訪問
const headers = new Headers({
"Access-Control-Allow-Origin":"*"
});
return fetch('http://127.0.0.1:8080/myapp/api/getStockInfo?code='+code,{method:"GET",headers:headers,mode:"cors"})
.then(function (response){
return response.text();
})
.then(
action(
data =>{
const info = data.match(/".+"/)[0];
const target = info.replace(/"/g,"");
const item = target.split(","); //目標信息數組
this.stocks.clear();
this.stocks.push(StockModel.fromArray(this,code,item));
return Promise.resolve();
}
)
)
}
}
export default StockStore;
每一個應用中不能初始化多個相同的領域 Store ,除非你想使得你的應用中的state變得相當混亂。
我們可以創建一個根 Store,來管理和初始化我們的各個領域 Store 或其他的 Store 比如應用狀態 Store 、UIStore 等。(為了使我們這個示例應用更加簡潔明了,故我們只有一個領域Store,即StockStore
)。
//根Store
import StockStore from "./StockStore";
const stockStore = new StockStore();
const stores = {stockStore,};
export default stores;
第五步:繪制視圖層
store 和 state 設計好了自然要開始設計我們的展示的視圖層了。
在視圖層,首先要明晰我們的交互邏輯,輸入股票代碼,觸發拉取股票信息的動作,獲取到股票信息后觸發更新StockStore
中保存的 state [ stocks
] 的狀態,從而自動觸發 Computed value 對 state 變更的響應 獲取到最新的股票信息數據,接著再自動觸發 Reactions 對 state 變更的響應 [ 即組件內render()方法 ] 使得UI重新渲染。
為了使得渲染更有效率,我們最好盡量地使用小組件。
此時也要特別注意一些使用MobX的陷阱,比如從 observable 屬性中提取數據并存儲,這樣的數據是不會被追蹤的。
所有使用到@observable
的組件都要加上@observer
。別擔心,@observer
越多,渲染效率越高。
@inject
將組件需要用到的具體store從根store中注入進來,具體理解需要結合下一步查看。
inject 是一個高階組件( 注意:高階組件不是React組件而是個函數 ),它和 Provider 結合使用,用于從 Provider 提供的 state 中選取所需數據,作為 props 傳遞給目標組件。
import React,{ Component } from 'react';
import { observable, action, computed } from "mobx";
import { inject, observer } from "mobx-react";
@inject("stockStore")
@observer
class StockPage extends Component{
render(){
if(this.props.stockStore.stocks.length ===0 ){
return (
<StockInput/>
);
}
return(
<div>
<StockInput/>
<StockInfoView />
</div>
);
}
}
@inject("stockStore")
@observer class StockInput extends Component{
@observable input="";
render(){
return(
<div>
<input value={this.input} onChange={this.onChange}/>
<button onClick={this.onSubmit}>查詢</button>
</div>
);
}
@action onChange=(e)=>{
this.input = e.target.value;
};
@action onSubmit = () =>{
this.props.stockStore.fetchStockByCode(this.input);
}
}
@inject("stockStore")
@observer class StockInfoView extends Component{
//常見陷阱——常見的錯誤的是從 observable 屬性中提取數據并存儲,這樣的數據是不會被追蹤的
//不要拷貝observables 屬性并存儲在本地
//Observer 組件只會追蹤在 render 方法中存取的數據。
@computed get stockModel(){
return this.props.stockStore.stocks[0];
}
render(){
const {code,stockName,tPrice,nPrice,yPrice,hPrice,lPrice} = this.stockModel;
return(
<ul>
<li>股票代碼:{code}</li>
<li>股票名稱:{stockName}</li>
<li>今日開盤價:{tPrice}</li>
<li>昨日收盤價:{yPrice}</li>
<li>當前價格:{nPrice}</li>
<li>今日最高價:{hPrice}</li>
<li>今日最低價:{lPrice}</li>
</ul>
);
}
}
export default StockPage;
第六步:連接 Store 和視圖層并加入 mobx-react-devtools
React開發的視圖層和 MobX開發的Store 現在都已開發完畢。
視圖層只負責 UI 的展示,Store 也會集中管理 state 。
現在我們需要將其連接起來使得視圖層中能獲取到 Store 保存的 state 值,以及視圖層能觸發 Store 中定義的操作 state 的 action 。
通過使用mobx-react
中提供的 Provider 組件來在React中使用MobX。
Provider 是一個 React 組件,利用 React 的 context 機制把應用所需要的 state 傳遞給子組件。
它的作用與 react-redux 提供的 Provider 組件是相同的。
import { Provider } from "mobx-react";
import React from "react";
import ReactDOM from 'react-dom';
import DevTools from 'mobx-react-devtools';
import StockPage from "./components";
import stores from "./stores";
const App = ()=>(
<div>
<StockPage />
<DevTools/>
</div>
);
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>,
document.getElementById("root"));
第七步:運行我們的第一個示例應用
進入my-app
的目錄:
在終端命令行輸入:cd my-app
運行我們的應用:
在終端命令行輸入:npm start
等待服務啟動完畢后,在瀏覽器地址欄輸入localhost:3000/
就可以看到我們的應用啦!
現在我們試試輸入柳鋼股份的股票代碼601003
,點擊查詢按鈕就可以看到柳鋼股份的相關股票信息啦!( 沒錯!這可能是篇軟廣…… )
寫在最后
相關前端代碼和后端代碼近期將會上傳至 github 以供大家參考運行,還請大家耐心等候。
這僅僅是一個簡單的 MobX+React 簡單示例應用,如果想了解更多 MobX 的高級用法,請參閱 MobX API 。
如有任何疑問,敬請留言或者私信。