- 文中的藍(lán)色字體是相關(guān)內(nèi)容的超鏈接绍移,網(wǎng)址不另外列出,請(qǐng)放心點(diǎn)擊讥电。
- 本文內(nèi)容適合 MobX 和 React 新手蹂窖,也歡迎 MobX 和 React 專家指導(dǎo)點(diǎn)評(píng)。
摘要
閱讀本文并實(shí)際上手編碼運(yùn)行恩敌,你將解決如下幾個(gè)疑問:
- MobX如何進(jìn)行狀態(tài)管理
- MobX如何管理異步操作對(duì)狀態(tài)的改變(fetch的使用)
- 如何創(chuàng)建一個(gè)簡單的MobX+React 示例應(yīng)用(無路由)
工具
- JetBrains WebStorm
- Node.js
預(yù)備知識(shí)
- 熟悉 ES6 相關(guān)知識(shí)
- 了解 React 相關(guān)知識(shí)
- 會(huì)使用 create-react-app 腳手架創(chuàng)建一個(gè) react 應(yīng)用
MobX API
在開始搭建我們的第一個(gè) MobX+React 應(yīng)用前瞬测,首先需要大致地認(rèn)識(shí)下 MobX 的 API ,了解 MobX 的核心概念纠炮,明白 MobX 的工作流程以及 常見陷阱 月趟。有了相關(guān)知識(shí)儲(chǔ)備后再進(jìn)行開發(fā),往往能使我們編碼更加得心應(yīng)手恢口,少走彎路孝宗,不用再勞心勞力和 bug 斗智斗勇。故還請(qǐng)初次接觸 MobX 的讀者仔細(xì)閱讀 API 文檔耕肩。
請(qǐng)務(wù)必熟悉以下標(biāo)簽的概念和作用
- @observable / observable()
- @observer / observer()
- @action / action()
- @computed
- @inject
示例應(yīng)用需求以及效果展示
示例應(yīng)用需求
本示例應(yīng)用需求是實(shí)現(xiàn)通過輸入股票代碼查詢到相關(guān)股票信息并展示出來的功能因妇。關(guān)于獲取股票相關(guān)信息,則通過新浪財(cái)經(jīng)的證券股票數(shù)據(jù)接口進(jìn)行獲取猿诸。由于該接口并未實(shí)現(xiàn) cors 跨域資源共享標(biāo)準(zhǔn) 婚被,會(huì)存在跨域訪問的問題,所以我們?cè)?自己編寫的后端項(xiàng)目中 獲取該接口返回的數(shù)據(jù)并實(shí)現(xiàn)cors跨域資源共享標(biāo)準(zhǔn)后傳遞給前端示例應(yīng)用梳虽。(這只是一種跨域問題的解決方案址芯,如果讀者有其他跨域問題的解決方案請(qǐng)自行修改實(shí)現(xiàn)。)
本示例應(yīng)用為了簡單起見怖辆,并未添加相關(guān)css樣式文件是复,如讀者有興趣删顶,可自行添加竖螃。
效果展示
啟動(dòng)應(yīng)用后界面如下(就是辣么粗獷……)
點(diǎn)擊查詢按鈕后如下所示(依舊辣么粗獷甚至有點(diǎn)不羈……)
第一步:使用 create-react-app 腳手架創(chuàng)建一個(gè)React應(yīng)用
MobX采用的是ES7的裝飾器語法,目前還是一種實(shí)驗(yàn)性的語法逗余,使用 create-react-app 腳手架默認(rèn)創(chuàng)建的項(xiàng)目是沒有開啟裝飾器語法的特咆,故使用 custom-react-scripts 這種方式來創(chuàng)建項(xiàng)目。
命令行內(nèi)輸入npx create-react-app my-app --scripts-version custom-react-scripts
創(chuàng)建項(xiàng)目录粱。
其創(chuàng)建的項(xiàng)目根目錄路徑下有一個(gè)拓展名為 .env 的文件腻格,這個(gè)文件中定義了 custom-react-scripts 為項(xiàng)目新增的特性。
打開該文件可以看到REACT_APP_DECORATORS = true;
表示啟用了裝飾器語法啥繁。
第二步:安裝相關(guān)依賴
查看項(xiàng)目目錄下的 package.json 文件菜职,此時(shí)僅安裝了 react
和react-dom
依賴。
我們需要手動(dòng)安裝mobx
和mobx-react
依賴旗闽,以及 MobX 開發(fā)調(diào)試工具酬核。
在終端命令行切入到我們的項(xiàng)目目錄:
-
cd my-app
在終端命令行輸入以下命令進(jìn)行安裝: npm install mobx
npm install mobx-react
npm install mobx-react-devtools --save-dev
安裝完相關(guān)依賴后我們就可以正式進(jìn)入第一個(gè)入門實(shí)例項(xiàng)目的編寫了蜜另。
第三步:構(gòu)造項(xiàng)目目錄
我們可以構(gòu)造如下所示的項(xiàng)目目錄:
根目錄
|--src #開發(fā)文件目錄
? | |---components # react 組件目錄
? | | ??|--index.js # 組件文件
? | |--models # 領(lǐng)域模型目錄
? | | ??|--StockModel.js # 領(lǐng)域state文件
? | |--stores # 保存state的Store目錄
? | | ??|--index.js # 根Store目錄
? | | ??|--StockStore.js # 領(lǐng)域Store目錄
? | |--index.js
MobX 中的 state 一般會(huì)封裝在不同的 store 中,store 不僅保存了 state ,還保存了操作 state 的方法嫡意。對(duì)于與領(lǐng)域直接相關(guān)的 state 举瑰,一般會(huì)創(chuàng)建專門的 model 實(shí)體類,用于描述 state 蔬螟。
第四步:設(shè)計(jì) store 和 state
store 的職責(zé)是將組件使用的業(yè)務(wù)邏輯和狀態(tài)封裝到單獨(dú)的模塊此迅,這樣組件就可以專注于UI渲染。
首先設(shè)計(jì)我們的model實(shí)體類StockModel旧巾,用于描述股票信息的state耸序。
股票信息接口返回的是是一個(gè)字符串,我們決定在領(lǐng)域store中把它的數(shù)據(jù)解析出來并保存在數(shù)組里鲁猩,所以在實(shí)體類中我們決定使用了一個(gè)fromArray方法來創(chuàng)建我們的StockModel實(shí)體佑吝。
//領(lǐng)域state
import {observable} from "mobx";
class StockModel {
store;//領(lǐng)域state所屬的領(lǐng)域store
@observable code;//股票代碼
@observable stockName;//股票名稱
@observable tPrice;//今日開盤價(jià)
@observable yPrice;//昨日收盤價(jià)
@observable nPrice;//今日當(dāng)前價(jià)格
@observable hPrice;//今日最高價(jià)
@observable lPrice;//今日最低價(jià)
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;
設(shè)計(jì)完了具有相關(guān)領(lǐng)域 state 的實(shí)體類纳猫,我們需要?jiǎng)?chuàng)建一個(gè)保存state和相關(guān)操作 state 的領(lǐng)域 Store 找颓。
在該領(lǐng)域Store內(nèi)我們定義了一個(gè) state [stocks
] 用以保存將要從服務(wù)獲取到的股票信息實(shí)體。我們還定義了一個(gè)動(dòng)作 [fetchStockByCode
] 用于從后端獲取股票信息蒋荚。需要特別注意的是疾棵,fetch是一個(gè)異步操作戈钢,所以需要編寫 異步action 來進(jìn)行對(duì)state的操作。這里我們采用action
關(guān)鍵字來包裝promises
回調(diào)函數(shù)是尔。即在獲取到數(shù)據(jù)后再發(fā)送一個(gè)action
操作 state [stocks
] 的變更殉了。
//領(lǐng)域state
import { observable, action,} from "mobx";
import StockModel from "../models/StockModel";
class StockStore{
@observable stocks=[]; //數(shù)組元素是PostModel的實(shí)例
//從服務(wù)器獲取股票信息
@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(","); //目標(biāo)信息數(shù)組
this.stocks.clear();
this.stocks.push(StockModel.fromArray(this,code,item));
return Promise.resolve();
}
)
)
}
}
export default StockStore;
每一個(gè)應(yīng)用中不能初始化多個(gè)相同的領(lǐng)域 Store ,除非你想使得你的應(yīng)用中的state變得相當(dāng)混亂拟枚。
我們可以創(chuàng)建一個(gè)根 Store薪铜,來管理和初始化我們的各個(gè)領(lǐng)域 Store 或其他的 Store 比如應(yīng)用狀態(tài) Store 、UIStore 等恩溅。(為了使我們這個(gè)示例應(yīng)用更加簡潔明了隔箍,故我們只有一個(gè)領(lǐng)域Store,即StockStore
)脚乡。
//根Store
import StockStore from "./StockStore";
const stockStore = new StockStore();
const stores = {stockStore,};
export default stores;
第五步:繪制視圖層
store 和 state 設(shè)計(jì)好了自然要開始設(shè)計(jì)我們的展示的視圖層了蜒滩。
在視圖層,首先要明晰我們的交互邏輯奶稠,輸入股票代碼俯艰,觸發(fā)拉取股票信息的動(dòng)作,獲取到股票信息后觸發(fā)更新StockStore
中保存的 state [ stocks
] 的狀態(tài)锌订,從而自動(dòng)觸發(fā) Computed value 對(duì) state 變更的響應(yīng) 獲取到最新的股票信息數(shù)據(jù)竹握,接著再自動(dòng)觸發(fā) Reactions 對(duì) state 變更的響應(yīng) [ 即組件內(nèi)render()方法 ] 使得UI重新渲染。
為了使得渲染更有效率辆飘,我們最好盡量地使用小組件啦辐。
此時(shí)也要特別注意一些使用MobX的陷阱污秆,比如從 observable 屬性中提取數(shù)據(jù)并存儲(chǔ),這樣的數(shù)據(jù)是不會(huì)被追蹤的昧甘。
所有使用到@observable
的組件都要加上@observer
良拼。別擔(dān)心,@observer
越多充边,渲染效率越高庸推。
@inject
將組件需要用到的具體store從根store中注入進(jìn)來,具體理解需要結(jié)合下一步查看浇冰。
inject 是一個(gè)高階組件( 注意:高階組件不是React組件而是個(gè)函數(shù) )贬媒,它和 Provider 結(jié)合使用,用于從 Provider 提供的 state 中選取所需數(shù)據(jù)肘习,作為 props 傳遞給目標(biāo)組件际乘。
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{
//常見陷阱——常見的錯(cuò)誤的是從 observable 屬性中提取數(shù)據(jù)并存儲(chǔ),這樣的數(shù)據(jù)是不會(huì)被追蹤的
//不要拷貝observables 屬性并存儲(chǔ)在本地
//Observer 組件只會(huì)追蹤在 render 方法中存取的數(shù)據(jù)漂佩。
@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>今日開盤價(jià):{tPrice}</li>
<li>昨日收盤價(jià):{yPrice}</li>
<li>當(dāng)前價(jià)格:{nPrice}</li>
<li>今日最高價(jià):{hPrice}</li>
<li>今日最低價(jià):{lPrice}</li>
</ul>
);
}
}
export default StockPage;
第六步:連接 Store 和視圖層并加入 mobx-react-devtools
React開發(fā)的視圖層和 MobX開發(fā)的Store 現(xiàn)在都已開發(fā)完畢脖含。
視圖層只負(fù)責(zé) UI 的展示,Store 也會(huì)集中管理 state 投蝉。
現(xiàn)在我們需要將其連接起來使得視圖層中能獲取到 Store 保存的 state 值养葵,以及視圖層能觸發(fā) Store 中定義的操作 state 的 action 。
通過使用mobx-react
中提供的 Provider 組件來在React中使用MobX瘩缆。
Provider 是一個(gè) React 組件关拒,利用 React 的 context 機(jī)制把應(yīng)用所需要的 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"));
第七步:運(yùn)行我們的第一個(gè)示例應(yīng)用
進(jìn)入my-app
的目錄:
在終端命令行輸入:cd my-app
運(yùn)行我們的應(yīng)用:
在終端命令行輸入:npm start
等待服務(wù)啟動(dòng)完畢后着绊,在瀏覽器地址欄輸入localhost:3000/
就可以看到我們的應(yīng)用啦!
現(xiàn)在我們?cè)囋囕斎肓摴煞莸墓善贝a601003
,點(diǎn)擊查詢按鈕就可以看到柳鋼股份的相關(guān)股票信息啦J煳尽( 沒錯(cuò)归露!這可能是篇軟廣…… )
寫在最后
相關(guān)前端代碼和后端代碼近期將會(huì)上傳至 github 以供大家參考運(yùn)行,還請(qǐng)大家耐心等候臣樱。
這僅僅是一個(gè)簡單的 MobX+React 簡單示例應(yīng)用靶擦,如果想了解更多 MobX 的高級(jí)用法,請(qǐng)參閱 MobX API 雇毫。
如有任何疑問,敬請(qǐng)留言或者私信踩蔚。