前端?後端?全端?

小閃光問了個問題:非本科轉職資訊走前端是不是比較容易?身為全端老司機,依多年經驗與觀察給了分析:

  • 前端技術工具常砍掉重練,大家一起被拉回起跑線,老鳥優勢不明顯
  • 前端知識較收斂,打好 HTML、CSS、JavaScript 基礎即能上場
  • 前端設計成果回饋直覺,驗收規格明確,也易靠作品集證明實力,新人較有機會被委以重任
  • 前端常有修改、調整、修新需求,創造較多工作機會
  • 初階前端工作對 Domain Knowhow 需求較低,無經驗新人進場容易

這串不專業分析在臉書意外引發迴響,大家對前端框架與工具放煙火式的生命週期都很有感慨(也可能是讀者圈形成的同溫層),不少人偏愛後端,雖然也得花時間深耕架構、效能議題,熟悉異質系統及了解產業知識,但知識技能較易累積,不會動不動砍掉重練。也有人提醒,前端入門容易,但初階工作常陷入無止境的畫面調整修改,技能(及薪水)成長受限,需另訂長期規劃轉進或突圍。

我把自己定位成全端工程師,多年下來對系統各段都有所涉獵,能從 DB、WebAPI、MVC 一路寫到 HTML/CSS/JavaScript,必要時來點排程、Windows Service 串場,期許自己能成為一條龍服務中的那條龍。(笑)

既已決定走全端路線,沒有一目十行過目不忘的天分、又是需要睡覺的凡人,能撥給前端的時間有限,很難把前端當成終生志業,不管技術怎麼翻新都緊緊跟隨,無怨無悔。因此,在技術選型時,安裝方便、容易上手是首選。

輕前端

早期的 JavaScript 程式庫如 jQuery、Knockout.js、AngularJS (註:我都用過,後二者已乘黃鶴去,唯有 jQuery 仍有一席之地,但隨著 IE 消失及瀏覽器規格漸趨統一,jQuery 的必要性亦大不如昔),用起來沒那麼麻煩,在既有 HTML/WebForm/MVC 網頁加個 <script src="..."> 載入程式庫,便能享受新技術。而當代前端框架主流已不是這種玩法,你必須另起專案,按照各框架要求的專案架構將畫面拆解成大小元件、模組,編譯打包後才會生出 HTML、JS、CSS 檔。這意味著若你想在 WebForm/ASP.NET MVC 專案應用這些前端框架,必須捨棄原本的 .aspx、.cshtml 概念,另起一個前端專案,後端改為只留 WebAPI/SSE/WebSocket 接口與其整合。這條路不是不能走,前端框架的複雜能換來架構分明、易維護擴充的好處,但也有代價,你必須付出較高的學習成本高並重構既有專案將前後端分離,再者,若有一天要換用其他前端框架,有極大部分得丟棄重寫。

與傳統 <script src="..."> 即插即用的做法相比,當代前端框架像「嵌入式冷氣機」,美觀大方,但安裝費錢費工,重新裝潢時一併規劃進去還好,要裝在既有住家,難逃拆掉天花板重做的命運。而前端牌嵌入式冷氣機又常因某些莫名其妙原因被迫淘汰更換,工程之大不難想像。以我的觀點,若沒有要追求極致的美觀舒適,傳統 <script src="..."> 比較像挖個洞走明管連室外機的分離冷氣,好裝好拆,適合不想破壞既有裝潢的屋主,如果你跟我抱持相似想法,應該也會選擇「輕前端」這條路。

thumbnail
照片來源:吊隱式/內嵌式冷氣安裝實例

Vue.js!

我現在寫網頁,簡單需求會用 jQuery 甚至香草 JS 解決,但複雜網頁沒有 MVVM 不如一刀給我個痛快,但之前慣用的 Knockout.js/AngularJS 已走入歷史。當代前端的三大主流框架:React.js、Angular、Vue.js 中,只有 Vue 自許為漸進式框架,保留了 <script src="..."> 即插即用的引用方式,可加裝在 WebForm/MVC 網頁就讓你享受 MVVM 的便利,而不用修改專案架構,學習 npm、webpack 等不知哪天會通通換掉的工具。讓專案維持 WebForm/MVC 為主,JavaScript 輔助的精神,對全端工程師負擔較小,也夠應付內部網站或簡單應用。等哪天需要寫出 Gmail、FB 概念的 SPA,再乖乖去學 CLI/npm/webpack 搞定。 (或許這時該「閃開,叫專業的來」才是上策)

下圖是 Vue.js 的 Progressive JavaScript Framework 漸進式 JavaScript 框架概念圖,若走輕前端路線,只會使用 到 Declarative Rendering (即在元件加上 v-bind、v-model 引用 Vue 功能) 及 Component System (自定元件封裝邏輯重複使用),要學的東西不多,不難上手。

圖片來源:Vue.js 與前端生態圈 / 重新認識 Vue.js by Kuro Hsu

前陣子到圖書館借書,順便預約了 Kuro 大大的 重新認識 Vue.js:008 天絕對看不完的 Vue.js 3 指南,小排了一下前陣子拿到書。全書共六章,而寫輕前端只需讀前兩章(基礎入門及元件系統),嘿,我成功在 8 天內看完。而最酷的是書裡的第一二章範例都有放上 Github,猜猜怎麼著?每個範例都是一個獨立 HTML 檔,直接用瀏覽器開啟就能完整執行,符合輕前端精神。

筆記(自用)

讀完照例留下筆記,方便日後拾回記憶。(筆記以瑣碎關鍵字為主,細節請參閱原書或線上版)

  • Vue 0.8 2014 HackerNews / Reddit 公開
  • Vue 1.0 2015 發佈,輕量級 AngularJS,vue-router, vuex, vue-cli
  • Vue 2.0 2016 Oct,React Virtual DOM,效能大增
  • Vue 3.0 2019-09-18 核心以 TypeScript 重寫,效能提升,體積下䧏
  • 與 AngularJS,React 最大不同 - Progressive JS Framework
    宣告式渲染 + 元件系統
  • 傳統為指令式程式設計 Imperative Programming,自掛 onchange, onclick 事件
  • Vue 3 特色
    • Fragment 元件不限單一根元素
    • object.defineProperty 換成 Proxy API,效能好,支援深度偵測物件及陣列更新
    • 模板編譯:靜態節點優化,VDOM 更新只遍歷動態節點,減少浪費
    • Teleport, Suspense 功能性元件
    • Composition API, 取代 mixins (createApp() 寫法叫 Options-Based)
    • setup, ref 語法糖
  • 用 _ 或 $ 開頭的屬性不會被 Proxy 代理
  • Clone 做法:ES6 解構 clone = 為淺 Copy,深 Copy 用 JSON clone = JSON.parse(JSON.stringify(srcObject))
  • vm.$data 可取得原始資料物件
  • methods 方法不能用 Arrow Function () => ,因為 this 會指向 window
  • computed 只會在涉及屬性有異動時觸發重算
  • computed 可自訂讀寫邏輯 computed: { jpy { get() set() }
  • v-bind, v-on, v-model... 這些叫指令 Directive
  • v-model 雙向綁定,限表單元素,故不能用在 DIV,設 contenteditable 也不行
  • v-model 綁 SELECT 若未對映到相符值,在 iOS 會有選不到第一個選項的狀況(沒觸發change),解法為第一個 option 設空值並 disabled
  • 修飾子 v-model.lazy 由按揵觸發改 change 觸發、v-model.number 將值視為數字、v-model.trim 取字串時先 Trim()
  • v-text 或 {{ text }} 設文字內容
  • v-html 注入 HTML 內容
  • v-once 只更新一次
  • <div v-pre>{{ text }}</div> 不當模板直接輸出
  • <div v-bind:class="{active:isActive,'text-danger':hasError}">
  • <div :style="msgStyle">,而 computed msgStyle() { return { 'border': ...., 'color': ... } }
  • v-on 事件,<button v-on:click="plus">, plus(event)
    <button @click="plus(amount, $event)"> + plus(amount, event)
    event.target <= 觸發元素
  • 修飾子
    v-on.stop 加 event.stopPropagation()
    v-on.prevent 加 event.preventDefault()
    v-on.capture 會觸發外層元素事件再內層
    v-on.self 只觸發本身,不含子元素浮上來的
    v-on.once 只觸發一次
    v-on.passive 等同 addEventListener passive 常用於 scroll 事件
  • 鍵盤修飾子 .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right, .ctrl, .alt, .shift, .meta (Windows 鍵,mac Command)
  • @keydown.enter.exact=".." Enter 可,Ctrl-Enter 不可
  • 滑鼠修飾子 .left, .right, .middle
  • v-model="msg" 可拆解成 :value="msg" @input="msg = $event.target.value"
  • v-if 元素會動態刪增,也會重複使用 DOM 元素,Vue 2 要加 key 防殘留,Vue 3 己修正
  • v-for="item in items", v-for="(item,index) in items", v-for="propValue in someObject", v-for="(val, key) in someObject", v-for="pageNo in 10" (1 ~ 10)
  • <template v-for="i in links"> 隱形容器
  • v-for 要篩選排序用 computed 自訂計算屬性配 JS filter(), sort()
  • v-for="i in list" :key="i.id" 確保識別 DOM 重複利用時不殘留
  • Vue 2 使用 Object.defineProperty() 注入 setter,getter 偵測異動,對陣列操作加裝偵測,但有可能感應不到。Vue 3 用 Proxy API,無此問題。
  • 元件生命週期(Vue3):(beforeCreate, created, beforeMount, mounted 建立階段), (beforeUpdate, updated 更新階段), (beforeUnmount, unmounted 銷毁階段), errorCaptured (CompositionAPI 的 Hook 不同)
  • data, prop, computed 在 created 後才能存取。取得 template 進入 beforeMount。將 template 內容替換後進入 mounted,可以操作 DOM(querySelector, addEventListener),不然操作結果可能被 Vue 蓋掉
  • watch 偵測狀況更新多次,也只會在 Queue 出現一筆,在下個事件迴圈(Event Loop 或 Tick)更新 DOM。所以你如果在 watch 中透過 this.$el 存取綁定的DOM,其狀況仍是更新前。
    解法1 setTimout 大絕、解法2 this.$nextTick(() => { this.$el... })
  • 元件種類:展示型元件 (Presentation) UI 呈現、容器型元件(Container)與Server溝通、互動型元件(Interactive)elementUI/bootstrap UI library、功能性元件(Functions)不渲染內容,功能或機制的封裝
  • Vue3 app.component('my-component', )
  • app.use(...), app.mixin(...), app.component(...), app.directive(...), app.mount('#app') 不像 Vue2 掛在 Vue 全域物件
  • 區域性元件 Vue.createApp({ components: { 'my-component': ... }})
  • ES Module 寫法
    import myComponent from './com/my-com.js'
    const app = Vue.createApp({ components: { myComponent } ... })
    
  • 元件名稱建議用連字線與標準元素區隔 <todo-item>,註冊時用 Pascal-Case 'TodoItem' 或 'todo-item',瀏覽器解析標籤無大小寫區別,模板一律用 <todo-item>
  • x-template 封裝
    <script id="media-block" type="text/x-template">
    app.component('media-block', template: '#media-block')
  • Vue3 支援多個根元素,此時 this.$el 不能用,改用 <div ref="title"> + this.$refs.title 存取
  • data 記得 data() { return } 傳回新物件,避免多元件共用 data 產生污染
  • 恢復初始狀態 resetData() { Object.assign(this.$data, this.$options.data()); }
  • 元件與外部溝通使用 props + v-bind,自身的則可用 data() { return }
    props 屬性採 Camel 命名,引用時要改連字線,例 props: ['someProp'] -> v-bine:some-prop。
    v-bind:some-prop 或 :some-prop 可綁定外部狀態,若寫成 some-prop="string" 則傳入字串
  • props 可指定型別:String, Number, Boolean, Array, Object, Date, Function, Symbol、指定預設值、設定驗證規則(函式內不能用 this)
    props: { 'prop1': { type: Number, validator: value => value > 0 }, 'prop2': { type: [String, Number], default: 'Hello' } }
  • 注意:用 props 傳物件,若在元件修改其屬性,物件內容會因此異動,為不好的設計。可善用自動解構:book: { name:..,author:.. }、props: ['name', 'author'] 則寫成 v-bind="book",name, author 會自動對映上
  • 未定義的 props 或事件會傳到子元件上
    <my-com :class="parent-css"></my-com> 而 my-com props 沒有 props: ['class'],template: '<div class="child-css"></div>' 渲染結果 class 會包含 child-css 及 parent-css 屬性值
    若有多個根節點,元件 template 可用 <main v-bind="$attrs">... 指定接收
  • 元件 props 屬性是不能修改的,解法為用 data 承接
    props: ['name','author'], data() { return { bookName: this.name, bookAuthor: this.author }}
  • 元件也可以遞迴,例如:ViewTree
  • Vue 父子元件溝通口訣:Props In,Event Out
  • 事件更新範例:
    props: ['name','author'], data 宣告 bookInfo: { bookName:this.name, bookAuthor:this.author }  
    watch: { bookInfo: {   
    deep: true, //記得要加  
    handler(val) {  
      this.$emit('update', val)  
    }  
    } }  
    
    呼叫端:<my-com @update="updateBook"> methods: { updateBook(val) }
  • 父子元件直接溝通巧門 this.$parent, <my-com ref="child1"> this.$refs.child1
  • 子元件的 v-model:props: ['modelValue'], $emit('update:modelValue', value)
  • 父孫溝通:createApp 時 provide() { return { provideMsg: this.msg }} 子孫元件用 inject: ['provideMsg'] 取得。但這種做法異動時不會更新,要用 provide() { return { provideMsg: Vue.computed(() => this.msg) } } + inject: ['provideMsg'] + {{ provideMsg.value }}
  • EventBus 在 Vue3 己不建議使用,請用 vuex,一個受控的 store;或是 Vue Composition API
  • 動態元件:例如 註冊三個元件 tab-home, tab-posts, tab-archive,<component :is="currTabCom"></component>,而 computed: { currTabCom() { return `tab-$`; } }
  • 與 v-show 相比,:is 無法保留元件狀態(切換走再切回來,輸入值會不見),解法是用 <keep-alive><component :is=...></component></keep-alive> (註:keep-alive 也可搭配 v-if 使用)
  • keep-alive 可用 include, exclude 指定哪些元件狀態要保留,元件要加 name: '...' 供識別,並支援 RegExp 及陣列 <keep-alive :include="/tab-(home|posts)/"> <keep-alive :include="['tab-home', 'tab-posts']"> 另有 max="n" 指定保存暫存子元件數量
  • keep-alive 專用生命周期 Hook: activated, deactivated
  • 非同步元件 Async Component
    const AsyncComp = Vue.defineAsyncComponent(() => 
    	new Promise((resolve, reject) => {
    		resolve({
    			template: '<div>I am async!</div>'
    		})
    	})
    );
    app.component('async-example', AsyncComp);
    
  • Slot 插槽 / 編輯作用域(元件的scope)
  • 在元件模板加入 <slot></slot> 會代入父元素用 <my-com><blah></blah></my-com> 塞入的 blah。<slot name="header"></slot> 具名插槽,父層用 <template v-slot:header></template> 分別插入指定插槽
  • 動態切換具名插槽:<template v-slot:[dynamic_slot_name]>
  • teleport (Vue 3 +)
    <teleport to="body"></teleport> 將內容元素放在 body 層 (不會刪除後重建,會有 keep-alive 保留效果
  • transition 漸變
    定義 CSS .v-enter-from/.v-enter-active/.v-enter-to 進場過程的三個樣式
    .v-leave-from/.v-leave-active/.v-leave-to 消失的三個樣式
    <transition><div v-show="..."></transition>
    <transition name="slide"> --> .slide-enter-active
    transition 若有多個元素用 if 切換,要用 v-if, v-else-if,不然就是改用 transition-group
    Hooks 函式:before-enter, enter, after-enter, enter-cancelled, before-leave, leave, after-leave, appear, after-appear, appear-cancelled
    BUT!! 其實用 CSS 動畫就好了
    要反覆播放
    <div ref="block" :class="{shake: noActivated}">Block</div>
    <button @click="noActivated = true">Shake It!</button>
    
    methods: {
        reactivated() {
      	  this.noActivated = false;
        }
    },
    mounted() { 
        this.$refs.block.addEventListener('animationend', this.reactivated); 
    },
    unmounted() {
        this.$ref.block.removeEventListener('animationend', this.reactivated);
    }
    
  • 現成動畫特效 Animate.css (npm install animate.css --save, 也有 CDN 版)
  • 非輕前端部分:(未來有用到再學)
    • Vue CLI
    • Vue SFC
    • Vue Router
    • Vuex 狀態管理
    • Composition API
  • ES6 必備知識
    • var / let / const 的區別
    • ES Module
    • Arrow Function Expression 與 this
    • Template Literals `...$...`
    • Destructuring Assignment const { propA, propB } = object_with_propA_and_propB; const [x, y]= [1,2,3,4];
    • Spread Operator const arr = ['to-insert', ...another_array ] Rest Operator const {x, y, ...z} = { x:1, y:2, a:3, b:4 }z = { a:3, b:4 }
    • Promise, await, async

Notes of reading book: Vue.js 3 guide.


Comments

# by jc

黑大好 vue 組件內資料傳遞推薦可以用 provide inject 來替代 vuex 另外若是要用 vuex,但又希望 typescript 強型別,推薦可以用 pinia

Post a comment