Meteor Mantra 介紹 (四)- 博客例子前端代碼解讀

Meteor Mantra 系列文章:

Meteor Mantra 介紹(一)- 基本概念
Meteor Mantra 介紹(二)- 前端架構(gòu)詳解
Meteor Mantra 介紹(三)- 后端架構(gòu)解釋
Meteor Mantra 介紹(四)- 博客例子前端代碼解讀
Meteor Mantra 介紹(五)- 博客例子后端代碼解讀
Meteor Mantra 介紹(六)- 使用 mantra-cli 命令行生成源碼


這篇文章由兩部分組成

  • 基本介紹云挟。對(duì)每部分源代碼作用的介紹

  • 工作流程舉例。數(shù)據(jù)如何在各部分流動(dòng)

基本介紹

這篇文章是對(duì) Meteor Mantra 的官方博客例子的詳細(xì)解讀舞萄,相當(dāng)于前面幾篇文章的一個(gè)應(yīng)用例子荠瘪。

博客例子代碼, 博客的在線 demo

前端入口

Mantra app 的前端入口是 client/main.js淘捡,這是 Meteor 框架的約定,會(huì)首先被執(zhí)行。它不應(yīng)該有任何其他邏輯锦积,只是初始化配置和加載必要模塊假哎。

例子利用 client/configs/context.js 把整個(gè)應(yīng)用的配置初始化瞬捕,還利用 mantra-core 加載了各個(gè) UI 組件并初始化。代碼很簡(jiǎn)短舵抹,見(jiàn)下面

import {createApp} from 'mantra-core';
import initContext from './configs/context';

// 引入 界面模塊
import coreModule from './modules/core';
import commentsModule from './modules/comments';

// 初始化 context
const context = initContext();

// 創(chuàng)建整個(gè) app肪虎,加載模塊并初始化
const app = createApp(context);
app.loadModule(coreModule);
app.loadModule(commentsModule);
app.init();

這里有必要解釋下初始化 context,就是 client/configs/context.js 這個(gè)文件惧蛹。Context 的意思是上下文扇救,這里是把全體環(huán)境使用到的第三方包和變量等引入刑枝,相當(dāng)于全局變量,統(tǒng)一引入可以避免再在每個(gè)文件去重復(fù) import迅腔,這樣整個(gè)應(yīng)用都能使用装畅,只需在需要時(shí)從 context 引入就行。

import * as Collections from '/lib/collections';
import {Meteor} from 'meteor/meteor';
import {FlowRouter} from 'meteor/kadira:flow-router';
import {ReactiveDict} from 'meteor/reactive-dict';
import {Tracker} from 'meteor/tracker';

// 可以看到 Meteor 環(huán)境沧烈,數(shù)據(jù)庫(kù)的集合掠兄,路由,還有本地的響應(yīng)式變量等都引入了
export default function () {
  return {
    Meteor,
    FlowRouter,
    Collections,
    LocalState: new ReactiveDict(),
    Tracker
  };
}

在 main.js 初始化 context 后锌雀,需要?jiǎng)?chuàng)建整個(gè) app蚂夕,加載各個(gè)模塊并初始化。參考 mantra-core 的源代碼 可以看到這里主要是通過(guò)依賴注入方式腋逆,把context婿牍,actions 和 UI 連接起來(lái)的地方,這樣你寫(xiě)代碼的時(shí)候可以把它們分開(kāi)寫(xiě)闲礼,達(dá)到 store牍汹、action 和 UI 解耦的目的,并讓數(shù)據(jù)單向流動(dòng)柬泽。

Modules

Mantra 使用的是模塊化結(jié)構(gòu)慎菲。這里的模塊不是 ES2015 的模塊,而是指結(jié)構(gòu)上的模塊锨并,形式上就是一組 ES2015 exports 構(gòu)成的一個(gè)文件夾露该,完成一個(gè)具體的功能。

我們這里以每個(gè) Mantra app 都必須有的 core 模塊為例第煮。

index.js

如果是 import 一個(gè)文件夾的話解幼,Node.js 的約定是從 index.js 開(kāi)始,Manta 里大量使用到了這個(gè)約定包警,所以基本每個(gè)文件里都會(huì)發(fā)現(xiàn)一個(gè) index.js 文件撵摆。

下面就是 index.js 的源碼,基本上就是一個(gè)集成害晦,就是把該文件夾里的除了 UI 組件的其他部分集中輸出給 main.js 的 app 去加載特铝。要注意的一點(diǎn)就是,這里的 configs 通常是 Meteor 的 method stubs 代碼壹瘟,目的是獲得 optimistic updates 特性鲫剿。和頂層的 configs 不太一樣。

import methodStubs from './configs/method_stubs';
import actions from './actions';
import routes from './routes';

export default {
  routes,
  actions,
  load(context) {
    methodStubs(context);
  }
};

routes.js

前面提到 index.js 沒(méi)有引入 UI 組件稻轨,那 UI 是怎么加載進(jìn)入應(yīng)用的呢灵莲?
因?yàn)?UI 組件會(huì)根據(jù)用戶的交互和 URL 變化,所以很自然的就是根據(jù) client/core/routes.js 和 url 決定 mount 那些組件殴俱。

這里注意兩點(diǎn)政冻,第一是 Mantra 在 routes.js 使用了 injectDeps 對(duì) Layout 注入了依賴枚抵。從 mantra-core 的源代碼 可以看到注入了 context 和 actions。第二是 mount 的 content 是一個(gè)函數(shù)赠幕,而不是通常的 React 組件俄精。因?yàn)槭褂昧?React context,需要在 layout 里 render榕堰,必須是函數(shù)竖慧。

    ...
export default function (injectDeps, {FlowRouter}) {
  const MainLayoutCtx = injectDeps(MainLayout);

  FlowRouter.route('/', {
    name: 'posts.list',
    action() {
      mount(MainLayoutCtx, {
        content: () => (<PostList />)
      });
    }
  });
    ...

configs

這里是模塊級(jí)的配置。入口 index.js 文件輸出一個(gè)缺省函數(shù)逆屡,這個(gè)函數(shù)的第一個(gè)參數(shù)通常就是 Application Context圾旨。這里通常是 Meteor 的 method stubs 代碼,目的是獲得 optimistic UI 特性魏蔗。如果有的 method 有自己特別的邏輯不想公開(kāi)砍的,可以在這里實(shí)現(xiàn)和服務(wù)端不一樣的代碼,只要能預(yù)測(cè)用戶交互的結(jié)果就行莺治。

actions

和前面的文件夾一樣廓鞠,也是通過(guò) index.jx export。下面的代碼就是一個(gè)完整的 action谣旁〈布眩可以看到這個(gè) action 修改了 LocalState 這個(gè)客戶端的全局變量,還有通過(guò) Meteor.call 更新了數(shù)據(jù)庫(kù)榄审,最后跳轉(zhuǎn)到新的博客頁(yè)面砌们。

export default {
  create({Meteor, LocalState, FlowRouter}, title, content) {
    if (!title || !content) {
      return LocalState.set('SAVING_ERROR', 'Title & Content are required!');
    }

    LocalState.set('SAVING_ERROR', null);

    const id = Meteor.uuid();
    // 通過(guò) method 更新數(shù)據(jù)庫(kù)
    Meteor.call('posts.create', id, title, content, (err) => {
      if (err) {
        return LocalState.set('SAVING_ERROR', err.message);
      }
    });
    FlowRouter.go(`/post/${id}`);
  },

  clearErrors({LocalState}) {
    return LocalState.set('SAVING_ERROR', null);
  }
};

Action 是在 container 里通過(guò) mapper 函數(shù)完成的依賴注入,然后在 UI 里通過(guò) props 調(diào)用搁进。

containers

Containers 文件夾里沒(méi)有 index.js 文件浪感,因?yàn)?container 都是通過(guò) import 在 routes.js 單獨(dú)引入。和普通的非 Mantra Meteor app 一樣饼问,在這里 subscribe 后端數(shù)據(jù)影兽,并將數(shù)據(jù)通過(guò) props 傳遞到 view 的 UI 組件。Mantra 用的 react-komposer 這個(gè) npm 包來(lái)創(chuàng)建 container莱革。和非 Mantra Meteor app 不一樣的是峻堰,actions 是作為依賴注入到 container 的。這樣 UI 部分的顯示就和應(yīng)用的狀態(tài)改變分開(kāi)了驮吱。以 client/modules/core/contianers/newpost.js 為例

...
export const depsMapper = (context, actions) => ({
  create: actions.posts.create,  // 修改數(shù)據(jù)庫(kù)的 action 作為 props.create 被傳遞進(jìn)了 UI 的 NewPost 組件茧妒。
  clearErrors: actions.posts.clearErrors,
  context: () => context
});

export default composeAll(
  composeWithTracker(composer),
  useDeps(depsMapper)
)(NewPost);

components

這里就是 UI 組件了萧吠。也沒(méi)有 index.js, 因?yàn)?container 也是通過(guò) import 引入用到的每個(gè) UI 組件左冬。UI 組件就沒(méi)有什么特別之處了。Layout 和 css 文件也位于這個(gè)文件夾纸型。

其他模塊

這個(gè)博客例子還有一個(gè) comments 模塊拇砰,就是博客的評(píng)論部分梅忌。這個(gè)模塊相當(dāng)于 core 這個(gè)核心模塊就是一個(gè)副模塊了,所以它沒(méi)有 routes.js, 也沒(méi)有 layout 和 css除破,都是通過(guò) core 模塊來(lái)實(shí)現(xiàn)的牧氮。


工作流程舉例

mantra_flow.png

上圖是 Mantra 的數(shù)據(jù)流動(dòng)示意圖,我們下面以它來(lái)說(shuō)明 Mantra 的工作流程瑰枫。假設(shè)你點(diǎn)擊了 http://mantra-sample-blog-app.herokuapp.com 這個(gè)博客的在線例子踱葛,然后接下來(lái)會(huì)發(fā)生

1 client/main.js

首先運(yùn)行的代碼是 client/main.js,在這個(gè)文件里光坝,各個(gè)模塊 module 的 route 和 action 被引入(詳見(jiàn) client/modules/core/index.js 的 export)尸诽,同樣 context 里的 FlowRouter,Collection 和 LocalState 等也被引入盯另。

這里就是圖中紅色虛線框的左邊兩個(gè)框 context 和 states 就緒性含。States 就是 context 里的 Collection 和 LocalState。

2 client/modules/core/route.js

在 client/main.js 里由 mantra-core 包創(chuàng)建的 app.init() 初始化會(huì)調(diào)用各個(gè) module 的 routes.js鸳惯。在 routes.js 里先把前面提到的 context 注入到 layout商蕴,然后根據(jù)用戶輸入或點(diǎn)擊正則匹配到前面列出的 routes.js 的根 url,接著掛載(mount)PostList 這個(gè) container 到注入了依賴的 MainLayoutCtx芝发。

這里就是圖中紅色虛線框的最右邊的框 container 就緒绪商。他們之所以在紅色的虛線里,就是表面他們都是基于 Meteor 的 reactive tracker 機(jī)制工作的后德,就是 Meteor 會(huì)自動(dòng)保證你的 states 的更新部宿。

3 container & UI component

Container 和使用 React-komposer 的非 Mantra app 的沒(méi)有太大區(qū)別,不一樣的是如果包含的 UI 有用戶交互的話瓢湃,那么需要注入 context 和 action理张。可以參看上面的 container 欄列出的 mapper 函數(shù)绵患。actions 就是這樣通過(guò) props 傳遞到 UI 組件的雾叭。因?yàn)槔永锏氖醉?yè)沒(méi)有用戶輸入的交互,所以我們以 newpost.js 這個(gè) container 為例落蝙,它通過(guò)前述方式注入了 action 的 create 和 clearErrors 函數(shù)织狐,然后在 UI 組件里的 newpost.js 通過(guò) props 調(diào)用 create 函數(shù),這就是淺藍(lán)色的 User Action 框筏勒,它執(zhí)行后會(huì)更改應(yīng)用的數(shù)據(jù) states移迫,而這種更改行為是通過(guò)注入 context 里的 Meteor, LocalState 等實(shí)現(xiàn)的。

4 Web Pub Action

上圖中最左邊的 action 是 Meteor 的數(shù)據(jù)訂閱 publication管行,當(dāng)有數(shù)據(jù)更新時(shí)厨埋,Meteor 的 tracker 會(huì)自動(dòng)接收到更新的 action 事件,然后啟動(dòng)相應(yīng) Meteor.subscribe 所在的 container 盡行組件的 re-render捐顷。而這一切也都是通過(guò) context 和瀏覽器里的 minimongo 來(lái)實(shí)現(xiàn)的荡陷。

    ...
export const composer = ({context}, onData) => {
  const {Meteor, Collections} = context();
  if (Meteor.subscribe('posts.list').ready()) {
    const posts = Collections.Posts.find().fetch();
    onData(null, {posts});
  }
};
    ...

以上就是 Mantra 的數(shù)據(jù)流動(dòng)方式雨效。

小結(jié)

這就是 Mantra 博客例子的前端代碼解釋。建議多結(jié)合例子代碼還有使用到包的源碼來(lái)理解废赞。和 Redux 類似徽龟,剛開(kāi)始時(shí)可能不太容易理解,因?yàn)椴恢庇^唉地,也不知道為什么非要繞一個(gè)很大的圈子來(lái)完成一件任務(wù)据悔,最好是多和實(shí)際例子聯(lián)系、應(yīng)用耘沼,理解 Mantra 的目的是寫(xiě)出更易于理解和維護(hù)的代碼屠尊,特別是對(duì)復(fù)雜的 app 有幫助。

注意:

  1. Mantra 官方文檔里有的 JSX 文件例子還是使用 .jsx 后綴「剑現(xiàn)在因?yàn)榻忉屍鬟M(jìn)步讼昆,Meteor 1.3+ 可以使用 .js 支持 JSX 語(yǔ)法,所以建議使用 .js 后綴骚烧。 Atwood's Law 再次發(fā)生作用 - Any application that can be written in JavaScript, will eventually be written in JavaScript
  2. Mantra 的這個(gè)博客例子里搭建好了 storybook, 大家也可以試試浸赫,它可以分離前后端開(kāi)發(fā),而且讓你對(duì)前端界面的更新立即可見(jiàn)赃绊。所以如果你發(fā)現(xiàn)在開(kāi)發(fā)前端時(shí)等待每次修改結(jié)果顯示時(shí)間太長(zhǎng)既峡,要不換臺(tái)更快的電腦,要不使用 storybook 立即看到你的修改碧查。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末运敢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子忠售,更是在濱河造成了極大的恐慌传惠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稻扬,死亡現(xiàn)場(chǎng)離奇詭異卦方,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泰佳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)盼砍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人逝她,你說(shuō)我怎么就攤上這事浇坐。” “怎么了黔宛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵近刘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)跌宛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任积仗,我火速辦了婚禮疆拘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寂曹。我一直安慰自己哎迄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布隆圆。 她就那樣靜靜地躺著漱挚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渺氧。 梳的紋絲不亂的頭發(fā)上旨涝,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音侣背,去河邊找鬼白华。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贩耐,可吹牛的內(nèi)容都是我干的弧腥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼潮太,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼管搪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起铡买,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤更鲁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后奇钞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體岁经,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蛇券,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缀壤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纠亚,死狀恐怖塘慕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒂胞,我是刑警寧澤图呢,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響蛤织,放射性物質(zhì)發(fā)生泄漏赴叹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一指蚜、第九天 我趴在偏房一處隱蔽的房頂上張望乞巧。 院中可真熱鬧,春花似錦摊鸡、人聲如沸绽媒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)是辕。三九已至,卻和暖如春猎提,著一層夾襖步出監(jiān)牢的瞬間获三,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工锨苏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留石窑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓蚓炬,卻偏偏與公主長(zhǎng)得像松逊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肯夏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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