手把手從零開始搭建第一個(gè)MobX+React入門示例應(yīng)用

  • 文中的藍(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í)

  1. 熟悉 ES6 相關(guān)知識(shí)
  2. 了解 React 相關(guān)知識(shí)
  3. 會(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)用后界面如下(就是辣么粗獷……)


啟動(dòng)應(yīng)用界面

點(diǎn)擊查詢按鈕后如下所示(依舊辣么粗獷甚至有點(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í)僅安裝了 reactreact-dom依賴。
我們需要手動(dòng)安裝mobxmobx-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)留言或者私信踩蔚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棚放,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馅闽,更是在濱河造成了極大的恐慌飘蚯,老刑警劉巖馍迄,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異局骤,居然都是意外死亡攀圈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門峦甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘来,“玉大人,你說我怎么就攤上這事凯傲∪剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵冰单,是天一觀的道長幌缝。 經(jīng)常有香客問我,道長诫欠,這世上最難降的妖魔是什么涵卵? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮荒叼,結(jié)果婚禮上缘厢,老公的妹妹穿的比我還像新娘。我一直安慰自己甩挫,他們只是感情好贴硫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伊者,像睡著了一般英遭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亦渗,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天挖诸,我揣著相機(jī)與錄音,去河邊找鬼法精。 笑死多律,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搂蜓。 我是一名探鬼主播狼荞,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼帮碰!你這毒婦竟也來了相味?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤殉挽,失蹤者是張志新(化名)和其女友劉穎丰涉,沒想到半個(gè)月后拓巧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡一死,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年肛度,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片投慈。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡承耿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逛裤,到底是詐尸還是另有隱情瘩绒,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布带族,位于F島的核電站锁荔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝙砌。R本人自食惡果不足惜阳堕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望择克。 院中可真熱鬧恬总,春花似錦、人聲如沸肚邢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骡湖。三九已至贱纠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間响蕴,已是汗流浹背谆焊。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浦夷,地道東北人辖试。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像劈狐,于是被迫代替她去往敵國和親罐孝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容