mobx-vue 目前已進入 mobxjs 官方組織,歡迎試用求 star!
原文連接前端
幾周前我寫了一篇文章描述了 mobx 與 angularjs 結合使用的方式及目的 (老樹發新芽—使用 mobx 加速你的 AngularJS 應用),此次介紹一下如何將 MobX 跟 Vue 結合起來。vue
npm i mobx-vue -S
mobx-vue 的使用很是簡單,只須要使用 connect 將你用 mobx 定義的 store 跟 vue component 鏈接起來便可:react
<template> <section> <p v-text="amount"></p> <p v-for="user in users" :key="user.name">{{user.name}}</p> </section> </template> <script lang="ts"> import { Connect } from "mobx-vue"; import Vue from "vue"; import Component from "vue-class-component"; class ViewModel { @observable users = []; @computed get amount() { return this.users.length } @action fetchUsers() {} } @Connect(new ViewModel()) @Component() export default class App extends Vue { mounted() { this.fetchUsers(); } } </script>
咱們知道,mobx 跟 vue 都是基於 數據劫持&依賴收集 的方式來實現響應式機制的。mobx 官方也屢次提到 inspired by vue
,那麼咱們爲何還要將兩個幾乎同樣的東西結合起來呢?git
Yes, it's weird.
2016年我在構建公司級組件庫的時候開始思考一個問題,咱們如何在代碼庫基於某一框架的狀況下,能以儘量小的代價在將來將組件庫遷移到其餘 框架/庫 下?總不能基於新的技術所有重寫一遍吧,這也太浪費生命了。且不說對於基礎控件而言,交互/行爲 邏輯基本上是可肯定的,最多也就是 UI 上的一些調整,並且單純爲了嘗試新技術耗費公司人力物力將基礎庫推導重寫也是很是不職業的作法。那麼咱們只能接受被框架綁架而只能深陷某一技術棧今後泥潭深陷嗎?對於前端這種框架半衰期尤爲短的領域而言顯然是不可接受的,結果無非就是要麼本身跑路坑後來人,要麼招不到人來一塊兒填坑... 簡單來講咱們沒法享受新技術帶來的種種紅利。angularjs
在 MVVM 架構視角下,越是重型的應用其複雜度越是集中在 M(Model) 跟 VM(ViewModel) 這兩層,尤爲是 Model 層,理論上應該是能脫離上層視圖獨立運行獨立發佈獨立測試的存在。而相應的不一樣視圖框架只是使用了不一樣綁定語法的動態模板引擎而已,這個觀點我在前面的幾篇文章裏都講述過。因此只要咱們將視圖層作的很薄,咱們遷移的成本天然會降到一個可接受的範疇,甚至有可能經過工具在編譯期自動生成不一樣框架的視圖層代碼。github
要作到 Model 甚至 ViewModel 獨立可複用,咱們須要的是一種能夠幫助咱們描述各數據模型間依賴關係圖且框架中立的通用狀態管理方案。這期間我嘗試過 ES6 accessor、redux、rxjs 等方案,但都不盡如人意。accessor 過於底層且異步不友好、redux 開發體驗太差(參考Redux數據流管理架構有什麼致命缺陷,將來會如何改進?)、rxjs 太重等等。直到後來看到 MobX:MobX 語法足夠簡單、弱主張(unopinioned)、oop 向、框架中立等特性正好符合個人需求。web
在過去的一年多裏,我分別在 react、angularjs、angular 上嘗試過基於 MobX 構建 VM/M 層,其中有兩個上線項目,一個我的項目,實踐效果基本上也達到了個人預期。在架構上,咱們只須要使用對應的 connector,就能基於同一數據層,在不一樣框架下自如的切換。這樣看來,這套思路如今就剩 Vue 沒有被驗證了。express
在 mobx-vue 以前,社區已經有一些優秀的 connector 實現,如 movue vue-modex 等,但基本都是基於 vue 的插件機制且 inspired by vue-rx,除了使用起來相對繁瑣的問題外,最大的問題是其實現基本都是藉助 Vue.util.defineReactive 來作的,也就是說仍是基於 Vue 自有的響應式機制,這在必定程度不只浪費了 MobX 的reactive 能力,並且會爲遷移到其餘視圖框架下埋下了不肯定的種子(畢竟你沒法確保是 Vue 仍是 MobX 在響應狀態變化)。npm
參考:why mobx-vue編程
理想狀態下應該是由 mobx 管理數據的依賴關係,vue 針對 mobx 的響應作出 re render 動做便可,vue 只是一個單純的動態模板渲染引擎,就像 react 同樣。
在這樣的一個背景下,mobx-vue 誕生了。
既然咱們的目的是將 vue 變成一個單純的模板渲染引擎(vdom),而且使用 mobx 響應式機制取代 vue 的響應式,那麼只要咱們劫持到 Vue 的組件裝載及更新方法,而後在組件裝載的時候收集依賴,在依賴發生變動時更新組件便可。
如下內容與其叫作 mobx-vue 是如何運做的,不如叫 Vue 源碼解析😂:
咱們知道 Vue 一般是這樣初始化的:
new Vue({ el: '#app', render: h => h(App)});
那麼找到 Vue 的構造函數,
function Vue (options) { ...... this._init(options) }
跟進到_init
方法,除了一系列組件初始化行爲外,最關鍵是最後一部分的 $mount
邏輯:
if (vm.$options.el) { vm.$mount(vm.$options.el) }
跟進 $mount
方法,以 web runtime 爲例:
if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { ... } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } vm._watcher = new Watcher(vm, updateComponent, noop)
從這裏能夠看到,updateComponent
方法將是組件更新的關鍵入口,跟進 Watcher
構造函數,看 Vue 怎麼調用到這個方法的:
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { ... this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) ... } this.value = this.lazy ? undefined : this.get()
get () { ... try { value = this.getter.call(vm, vm) } catch (e) { ... }
看到這裏,咱們能發現,組件 裝載/更新 的發起者是: value = this.getter.call(vm, vm)
,而咱們只要經過 vm._watcher.getter
的方式就能獲取相應的方法引用, 即 updateComponent := vm._watcher.getter
。因此咱們只要在 $mount
前將 MobX 管理下的數據植入組件上下文供組件直接使用,在$mount
時讓 MobX 收集相應的依賴,在 MobX 檢測到依賴更新時調用 updateComponent
便可。這樣的話既能讓 MobX 的響應式機制經過一種簡單的方式 hack 進 Vue 體系,同時也能保證組件的原生行爲不受到影響(生命週期鉤子等)。
中心思想就是用 MobX 的響應式機制接管 Vue 的 Watcher,將 Vue 降級成一個純粹的裝載 vdom 的組件渲染引擎。
核心實現很簡單:
const { $mount } = Component.prototype; Component.prototype.$mount = function (this: any, ...args: any[]) { let mounted = false; const reactiveRender = () => { reaction.track(() => { if (!mounted) { $mount.apply(this, args); mounted = true; } else { this._watcher.getter.call(this, this); } }); return this; }; const reaction = new Reaction(`${name}.render()`, reactiveRender); dispose = reaction.getDisposer(); return reactiveRender(); };
完整代碼在這裏:https://github.com/mobxjs/mob...
尤大大以前說過:mobx + react 是更繁瑣的 Vue
,本質上來看確實是這樣的,mobx + react 組合提供的能力剛好是 Vue 與生俱來的。而 mobx-vue 作的事情則恰好相反:將 Vue 降級成 react 而後再配合 MobX 升級成 Vue 😂。這確實很怪異。但我想說的是,咱們的初衷並非說 Vue 的響應式機制實現的很差從而要用 MobX 替換掉,而是但願藉助 MobX 這個相對中立的狀態管理平臺,面向不一樣視圖層技術提供一種相對通用的數據層編程範式,從而儘可能抹平不一樣框架間的語法及技術棧差別,以便爲開發者提供更多的視圖技術的決策權及可能性,而不至於被某一框架綁架裹挾。
PS: 這篇是系列文章的第一篇,後面將有更多關於 如何基於 MobX 構建視圖框架無關的數據層
的架構範式及實踐的內容,敬請期待!