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

Meteor Mantra 系列文章:

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


這篇文章由兩部分組成

  • 基本介紹。對每部分源代碼作用的介紹

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

基本介紹

這篇文章是對 Meteor Mantra 的官方博客例子的詳細解讀,相當于前面幾篇文章的一個應用例子。

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

前端入口

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

例子利用 client/configs/context.js 把整個應用的配置初始化,還利用 mantra-core 加載了各個 UI 組件并初始化。代碼很簡短,見下面

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)建整個 app,加載模塊并初始化
const app = createApp(context);
app.loadModule(coreModule);
app.loadModule(commentsModule);
app.init();

這里有必要解釋下初始化 context,就是 client/configs/context.js 這個文件。Context 的意思是上下文,這里是把全體環(huán)境使用到的第三方包和變量等引入,相當于全局變量,統(tǒng)一引入可以避免再在每個文件去重復 import,這樣整個應用都能使用,只需在需要時從 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ù)庫的集合,路由,還有本地的響應式變量等都引入了
export default function () {
  return {
    Meteor,
    FlowRouter,
    Collections,
    LocalState: new ReactiveDict(),
    Tracker
  };
}

在 main.js 初始化 context 后,需要創(chuàng)建整個 app,加載各個模塊并初始化。參考 mantra-core 的源代碼 可以看到這里主要是通過依賴注入方式,把context,actions 和 UI 連接起來的地方,這樣你寫代碼的時候可以把它們分開寫,達到 store、action 和 UI 解耦的目的,并讓數(shù)據(jù)單向流動。

Modules

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

我們這里以每個 Mantra app 都必須有的 core 模塊為例。

index.js

如果是 import 一個文件夾的話,Node.js 的約定是從 index.js 開始,Manta 里大量使用到了這個約定,所以基本每個文件里都會發(fā)現(xiàn)一個 index.js 文件。

下面就是 index.js 的源碼,基本上就是一個集成,就是把該文件夾里的除了 UI 組件的其他部分集中輸出給 main.js 的 app 去加載。要注意的一點就是,這里的 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 沒有引入 UI 組件,那 UI 是怎么加載進入應用的呢?
因為 UI 組件會根據(jù)用戶的交互和 URL 變化,所以很自然的就是根據(jù) client/core/routes.js 和 url 決定 mount 那些組件。

這里注意兩點,第一是 Mantra 在 routes.js 使用了 injectDeps 對 Layout 注入了依賴。從 mantra-core 的源代碼 可以看到注入了 context 和 actions。第二是 mount 的 content 是一個函數(shù),而不是通常的 React 組件。因為使用了 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

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

actions

和前面的文件夾一樣,也是通過 index.jx export。下面的代碼就是一個完整的 action。可以看到這個 action 修改了 LocalState 這個客戶端的全局變量,還有通過 Meteor.call 更新了數(shù)據(jù)庫,最后跳轉(zhuǎn)到新的博客頁面。

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();
    // 通過 method 更新數(shù)據(jù)庫
    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 里通過 mapper 函數(shù)完成的依賴注入,然后在 UI 里通過 props 調(diào)用。

containers

Containers 文件夾里沒有 index.js 文件,因為 container 都是通過 import 在 routes.js 單獨引入。和普通的非 Mantra Meteor app 一樣,在這里 subscribe 后端數(shù)據(jù),并將數(shù)據(jù)通過 props 傳遞到 view 的 UI 組件。Mantra 用的 react-komposer 這個 npm 包來創(chuàng)建 container。和非 Mantra Meteor app 不一樣的是,actions 是作為依賴注入到 container 的。這樣 UI 部分的顯示就和應用的狀態(tài)改變分開了。以 client/modules/core/contianers/newpost.js 為例

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

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

components

這里就是 UI 組件了。也沒有 index.js, 因為 container 也是通過 import 引入用到的每個 UI 組件。UI 組件就沒有什么特別之處了。Layout 和 css 文件也位于這個文件夾。

其他模塊

這個博客例子還有一個 comments 模塊,就是博客的評論部分。這個模塊相當于 core 這個核心模塊就是一個副模塊了,所以它沒有 routes.js, 也沒有 layout 和 css,都是通過 core 模塊來實現(xiàn)的。


工作流程舉例

mantra_flow.png

上圖是 Mantra 的數(shù)據(jù)流動示意圖,我們下面以它來說明 Mantra 的工作流程。假設(shè)你點擊了 http://mantra-sample-blog-app.herokuapp.com 這個博客的在線例子,然后接下來會發(fā)生

1 client/main.js

首先運行的代碼是 client/main.js,在這個文件里,各個模塊 module 的 route 和 action 被引入(詳見 client/modules/core/index.js 的 export),同樣 context 里的 FlowRouter,Collection 和 LocalState 等也被引入。

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

2 client/modules/core/route.js

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

這里就是圖中紅色虛線框的最右邊的框 container 就緒。他們之所以在紅色的虛線里,就是表面他們都是基于 Meteor 的 reactive tracker 機制工作的,就是 Meteor 會自動保證你的 states 的更新。

3 container & UI component

Container 和使用 React-komposer 的非 Mantra app 的沒有太大區(qū)別,不一樣的是如果包含的 UI 有用戶交互的話,那么需要注入 context 和 action。可以參看上面的 container 欄列出的 mapper 函數(shù)。actions 就是這樣通過 props 傳遞到 UI 組件的。因為例子里的首頁沒有用戶輸入的交互,所以我們以 newpost.js 這個 container 為例,它通過前述方式注入了 action 的 create 和 clearErrors 函數(shù),然后在 UI 組件里的 newpost.js 通過 props 調(diào)用 create 函數(shù),這就是淺藍色的 User Action 框,它執(zhí)行后會更改應用的數(shù)據(jù) states,而這種更改行為是通過注入 context 里的 Meteor, LocalState 等實現(xiàn)的。

4 Web Pub Action

上圖中最左邊的 action 是 Meteor 的數(shù)據(jù)訂閱 publication,當有數(shù)據(jù)更新時,Meteor 的 tracker 會自動接收到更新的 action 事件,然后啟動相應 Meteor.subscribe 所在的 container 盡行組件的 re-render。而這一切也都是通過 context 和瀏覽器里的 minimongo 來實現(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ù)流動方式。

小結(jié)

這就是 Mantra 博客例子的前端代碼解釋。建議多結(jié)合例子代碼還有使用到包的源碼來理解。和 Redux 類似,剛開始時可能不太容易理解,因為不直觀,也不知道為什么非要繞一個很大的圈子來完成一件任務,最好是多和實際例子聯(lián)系、應用,理解 Mantra 的目的是寫出更易于理解和維護的代碼,特別是對復雜的 app 有幫助。

注意:

  1. Mantra 官方文檔里有的 JSX 文件例子還是使用 .jsx 后綴。現(xiàn)在因為解釋器進步,Meteor 1.3+ 可以使用 .js 支持 JSX 語法,所以建議使用 .js 后綴。 Atwood's Law 再次發(fā)生作用 - Any application that can be written in JavaScript, will eventually be written in JavaScript
  2. Mantra 的這個博客例子里搭建好了 storybook, 大家也可以試試,它可以分離前后端開發(fā),而且讓你對前端界面的更新立即可見。所以如果你發(fā)現(xiàn)在開發(fā)前端時等待每次修改結(jié)果顯示時間太長,要不換臺更快的電腦,要不使用 storybook 立即看到你的修改。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

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