weex剛開源不久,作為一名前端,當然是抑制不住自己的好奇心想要嘗嘗鮮。雖然weex的最大亮點在于對于電商類應用場景能夠提供快速動态部署的功能,但是用js就能寫跑在native端的頁面更加吸引我。于是在空餘時間就開始搗騰着weex,想做一個native app看看weex有什麼“能耐”。
在開發過程中,在體會到weex周邊工具帶來的效率提升的同時,也發現了不少問題。除了weex本身剛開源肯定會存在各種問題之外,還有一些開發體驗的問題。weex相關的問題都在github上提了issue,而開發體驗的問題隻能自己來解決了。
本文主要記錄的就是在用weex開發app過程中遇到的一個最大的問題——資料流管理問題。當然這個問題從某種程度上來說也是我“自找的”,畢竟現在weex大多數的應用場景(電商活動頁面)的複雜度是不會有這個問題的。但是有想法就去試試也未嘗不是一件好事,是以,接下來都是圍繞着用weex來寫單頁app的情景來讨論的。
在寫app的過程中,一旦複雜度稍微上升一點,管理應用狀态就是個非常痛苦的事情。在沒有引入資料流工具之前,在weex裡隻能通過元件間的通信和傳props來控制資料的流動,頁面和互動少一點還好,一旦應用的狀态多起來,散落在各處的應用狀态就是一團亂麻。

現在前端比較火的架構都有配套的資料流工具,比如react的redux、vue的vuex、angular的自己……可見,現在開源社群有很多可用的資料流方案。于是我就琢磨着給weex引入一套資料流工具。由于weex和vue的淵源,在工具選型方面也沒怎麼糾結,就vue的親兒子vuex吧。
作為(不會native的)前端,我們寫的<code>.we</code>檔案經過<code>webpack</code>的<code>weex-loader</code>編譯以後,變成了native上js framework能夠識别的js bundle,然後之後的生成<code>weex instance</code>和<code>virtual dom</code>就已經脫離我們的管轄範圍了。也就是說,在前端<code>deploy</code>之前能做的就隻有搞搞字元串的“把戲”。這一點很重要,在下面分析<code>vuex</code>的時候會說到。
記得之前在github上看到<code>vuex</code>的issue裡有人問能不能把vuex用在别的架構裡,作者的答複是
vuex is not coupled with what rendering platform you use.
于是我就天真的安裝了<code>vuex</code>并且引入到了<code>weex</code>項目中,直到報錯資訊中提到沒有找到<code>vue</code>執行個體才把我打回現實。看來是要改源碼了。
在vuex源碼中,進行了一個初始化vue執行個體的操作(說好的不依賴呢):
報錯的來源就是這裡,從注釋得知,作者用了個<code>vue</code>執行個體來存儲vuex的state tree,當使用者直接通路<code>store.state.somestate</code>的時候傳回這個<code>vue</code>執行個體中儲存在<code>data</code>屬性裡的<code>state</code>,當使用者直接通路<code>store.getters.somegetter</code>的時候傳回這個<code>vue</code>執行個體中儲存在<code>computed</code>屬性裡的<code>getter</code>,僅此而已。嗎?
其實作者在這裡用到<code>vue</code>執行個體是“别有居心”的,因為這樣就能複用<code>vue</code>中的<code>watch</code>機制,當data改變,依賴該data的computed函數就會自動重新計算,是以,在store中的state改變之後,getters就會自動被計算了。
既然說要和<code>vue</code>解耦,那這個watch機制咋辦?好在weex也在元件内部複用了<code>vue</code>的那套watch,是以理論上這個問題是可以解決的。但是正如之前提到的,weex的執行個體是在native端的js framework生成的,我們在前端是通路不到的。但通路不到weex執行個體不代表我們不能把state和getters引入到weex執行個體中,因為執行個體是會根據我們寫的<code>.we</code>檔案來生成的,是以思路很清晰,我們在<code>.we</code>檔案裡引入store不就能在元件内通路state和getters了。(vuex源碼的改動不僅限于替換vue執行個體為普通對象)
簡單一句<code>vue.use(vuex)</code>然後在根元件内部引入<code>store</code>屬性就能将store引入到vue的子元件裡,通過在vue元件内部調用<code>this.$store</code>就能通路到store,而不是每個子元件引入一次store。但是由于weex沒有提供這樣的接口,是以又隻能自己想辦法了。
在前端,我們編寫的是<code>.we</code>檔案,native的js framework拿到的是js bundle,那從<code>.we</code>到js bundle的過程中發生了什麼?這就要看transformer做了什麼事情了。先看看以下<code>.we</code>檔案和它對應的js bundle是什麼樣子的:
可以看出,從<code>.we</code>檔案轉換到到js bundle的過程中,實際上就是對<code>.we</code>檔案進行了編譯,将他轉換成js framework能夠識别的格式。是以可以了解為,js bundle就是格式不同的<code>.we</code>檔案,隻要<code>.we</code>檔案的格式是符合transformer要求的,那麼transformer就可以輸出符合js framework要求的js bundle。是以,隻要保證在每次轉換前的檔案格式符合轉換器的要求,我們對轉換前的檔案做怎樣的修改都可以。
是以,為了解決需要在每個weex元件内部<code>require(xxx/store)</code>的問題,我們可以寫一個loader,在transformer之前将<code>require</code>語句引入到每個<code>.we</code>檔案中,這樣就解決了這個問題。
另外,要用weex元件的watch機制,就需要在元件的data中引入<code>store.state</code>,是以loader也需要做這個事情。
首先在項目下安裝weex-vuex-loader:
然後打開<code>webpack.config.js</code>,在weex-loader之後添加weex-vuex-loader:
後面<code>?</code>之後的<code>store</code>是使用者能夠自定義的,你可以替換成你想要的變量,這個變量就是在weex元件中通過<code>this</code>通路到的vuex的<code>store</code>。例如上面的例子中,<code>?</code>之後是<code>store</code>,那麼在weex元件中就可以通過<code>this.store</code>通路vuex的<code>store</code>。
這樣做的原因是,本來想像<code>vue</code>一樣,通過<code>this.$store</code>來通路,但是js framework的規則不允許weex元件中的data裡有<code>$</code>開頭的變量,因為這些變量是事先定義好的weex元件的vm的屬性。
是以,這裡隻能将預設的通路方式改為<code>this._store</code>,也就是在<code>weex-vuex-loader</code>後面不指定變量名的預設情況。考慮到不是人人都喜歡這樣的通路方式,幹脆就改成能夠自定義了。
需要注意的是,這裡getter的第一個參數是<code>store</code>,并不是vuex中原本的<code>state</code>。
完成了上面兩個步驟之後,就能愉快地在.we檔案中使用vuex了:
vue中的action是支援異步操作的,傳回promise即可,weex也在native端支援了promise,看起來不應該有什麼問題。但是我在使用過程中發現,在action中加入異步操作之後,在<code>then()</code>中調用了<code>commit()</code>來觸發某個mutation之後,雖然mutation會執行(例如改變某個state),并且weex元件中調用了依賴這個state的getter的computed函數也會執行,但是視圖是不會更新的,除非你再觸發一個事件(比如click事件),之後視圖才會更新。
說到stream子產品,在目前的weex-v0.6.1中,在ios端傳給回調函數的<code>response</code>的類型是<code>string</code>而不是<code>object</code>,是以在處理之前需要先<code>json.parse(response)</code>。這個bug将會在下個版本被修複。
這樣就能如絲般順滑地體驗weex+vuex了。
下面gif中就是這幾天用weex+vuex寫的模仿“口袋記賬app”的單頁應用,跑在ios playground上