在維護一陣子的 Rails 專案上,難免會看到各處擺放的 JavaScript function 或是 jQuery 各種直接操作 DOM 之類的作法。
在這個前端技術大時代,為了因應更複雜的 UI 需求,有更多易於擴充、提升效能的作法能夠選擇,例如 React / Vue 之類的前端框架。
要解決什麼問題
- 避免直接操作 DOM
- 更易維護及套件管理
- UI 易於管理 LifeCycle
在 Rails 5.1 之後,已經對 webpakcer 有相當好的支持性,在後面的 Rails 6 版本也已經變為預設的前端工具了
那麼在 Rails 專案中陳年已久的 jQuery + Asset pipeline,要怎麼樣才能夠跟這些新技術接軌呢
在有限的資源下要整個重寫,或許共存也是一種選擇。
在這篇文章中,我們的目標是將前端框架,以輕量導入的方式和 Rails 共存
這裡使用 Vue 做為示範,你也可以引入其他框架例如 React 來整合前端技術
怎麼不直接做前後端分離
前後端分離要考慮的事情很多,除非產品有強烈的需求,或是在資源狀況足夠的情況下是可以規劃的,不然很容易變成「前後端分裂」
對於 Rails 來說,你的前後端分離可能會遇到這些事情
- 原本好用的 session 要另外處理
- Server side rendering(SEO?)
以現有專案開始
加入 gem "webpacker"
直接執行指令來安裝 vue 的相依 JS 套件
rails webpacker:install:vue
這時候你會看到新的目錄
/app/javascript
├── app.vue
└── packs
├── application.js
└── hello_vue.js
packs 下面放的檔案可以當作一個「入口點」,也就是和 asset pipeline 中的 Manifest 一樣。
差別在於你在 layout 中引入時語法有些不同
# Webpacker
<%= javascript_pack_tag 'application' %>
# Asset pipeline
<%= javascript_include_tag 'application' %>
我們可以在 app/javascript/packs/application.js
中修改
import Vue from 'vue/dist/vue.esm'
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
el: '[data-behavior="vue-app"]'
})
})
然後在 app/views/layouts/application.html.erb
中加入 <%= javascript_pack_tag 'application' %>
並且新增 data-behavior="app"
將 Vue 掛載在 DOM element 上(官方不建議掛在 body 上,別這麼懶 XD)
<body>
<div class="container" data-behavior="vue-app">
<%= yield %>
</div>
</body>
打開有用到該 layout 的頁面,應該可以看到瀏覽器 console 顯示 You are running Vue in development mode.
那就可以開始來用 Vue 開發我們的 JS 元件了
Vue component
新增 app/javascript/components/Message.vue
<template>
<div class="message">
{{message}}
</div>
</template>
<script>
export default {
data: function () {
return {
message: "Vue card"
}
}
}
</script>
並修改 app/javascript/packs/application.js
import Vue from 'vue/dist/vue.esm'
+ import Message from "../components/Message"
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
+ el: '[data-behavior="vue-app"]',
+ components: { Message }
})
})
這時候你可以在 .erb
裡面插入 Vue 的 component,例如在 app/views/home/index.html.erb
就可以這樣寫
<message></message>
所以如果元件是 MessageCard
,在 .erb
裡面就寫 message-card
也因為這樣,你在 app/javascript/
下的 vue component 是可以和 rails 共享 CSS 的
所以你可以在 asset pipeline 寫 SCSS,在 webpakcer 裡面寫 JS 是可以共存的
直接從 Rails 給 props
有時候一些 JS 元件在初次渲染時,會沒有預設資料,這時候因為是這種 hybird 的寫法,所以你可以透過 controller 先拿好資料,在 view 的部分直接將 props 給 vue component
Ruby 部分
# Message controller
@message = Message.find(params[:id])
# Message View
<message :message="<%= @message.to_json %>"></message>
因為你拿出來的 @message
是 ruby object,這邊可以直接轉 JSON 餵給 component,要更方便的作法就是直接在 controller 內先 serialize 也是不錯的方向。
JS 部分
<template>
<div class="message">
<ul>
<li>ID: {{ message.id }}</li>
<li>email: {{ message.user.email }}</li>
<li>content: {{ message.content }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ["message"],
}
</script>
基本上到這裡,你可以在 Rails 的基礎上,使用前端框架來開發更多的 component,以現在這種 MVVM 雙向綁定加上不用直接操作 DOM 做渲染的方式,在未來協作或管理更複雜的資料流及 UI 都是有一定程度的幫助。
正常來說能寫 ES6 就不會想寫原本的 JS,能有 MVVM 就不想回去自己到處綁 element 了
Hybird 做法的優缺點
優點
這樣一來,可以慢慢抽換原本舊有的作法,不用再擔心因為到處寫 jQuery 來做 UI flow 而導致難以維護的問題
- 可以直接餵 props 而不用 API 請求
- 可以用 Rails cache
- 可以用 Rails session
缺點
1. I18n 處理
大致上兩種作法
- 和 JS 放在一起(比較推薦的作法)
- 全部寫在 Rails 裡面,然後倒出來用轉 JSON 餵過去,優點是只要維護一套,缺點是如果語系檔很大會非常慢,後期要單獨將前端拆出去時還要擔心語系耦合問題
2. 前後端混在一起寫
你沒辦法直接招前端工程師變成及戰力,除非他也會 Rails,不然就還要花時間帶了 XD
小結
用這種方式將 UI 邏輯丟給更適合解決問題的框架來做更方便,不管是效能或是撰寫方式都更好,不然常常看到 Rails 裡面各種綁 element 然後到處操作 DOM 真的很頭痛。