顯示具有 Vue 標籤的文章。 顯示所有文章
顯示具有 Vue 標籤的文章。 顯示所有文章

2024年12月10日 星期二

vue開發上的小坑,vFor的item不能當vModel

簡而言之我在程式碼裡面這樣寫

UserCard(v-for="data in datas", v-model="data")
UseCard是我自製的component,因為裡面會牽涉到改動,所以不太適合用props的方式傳進去,所以用model的方式來傳,開發上還沒啥問題,不過打包卻會爆出以下錯誤
v-model cannot be used on v-for or v-slot scope variables because they are not writable

上網查了一下,用v-for拆出來的物件,該物件不能放到v-model裡面進行修改,那怎麼辦呢?
很簡單,跟著v-for傳出來的還有index,用它來指向原物件就可以了
UserCard(v-for="(_data, index) in datas", v-model="datas[index]")

沒想到用vue這麼多年還有這麼一個小坑沒採過,記錄一下@@收工下班!

2024年4月17日 星期三

vue要產生一個component在body要怎麼做

前言

相信很多小夥伴跟我有相同的疑問,或是使用過modal這個套件,會在body增加一個可控制的component,我一直以為是createElement相關的方法去做到的,一直到今天.......

今天在線上抗到一個component,來人我們直接上範例

<teleport to="body">
<p> 移動我到 body </p>
</teleport>

最終結果就會是

<body>
<div id="app">......</div>
<p> 移動我到 body </p>
</body>

關於teleport有兩個屬性to跟disabled,to就是要移到哪裡去,支援css選擇器,disabled就是有沒有生效,亦或是留在原地

以上簡短分享

2023年12月3日 星期日

我受夠了react的hook,我想寫vue啊~

前言

你是否有跟我一樣的困擾呢,覺得react class component的setState寫法相當繁瑣,覺得react hook的的queue也不夠直覺,為什麼不能直接update state然後畫面就自動render需要透過一個setter,然後資料異動還不是同步的,也不是異步,竟然是透過queue......這是我一直以來的心聲。我今天沒有要戰,純粹是就職以來的一些小心得,作為一套老牌框架,我承認他的市佔率,但我覺得如果以便利性讓我挑選,我一定不會選react,但礙於現有的社群要他大改幾乎是不太可能,就算不論三大框架現在市場上也充斥著太多套創新的框架(qwik, svelte, solid, astro....),所以要用一個形容詞來形容這套框架只能說它“過時”了,but!最可怕的but來了,公司就愛用嘛~所以有沒有什麼辦法?

老實說我很驚訝竟然查不到相關的資料,還是大家都超滿意react,沒遇到什麼奇怪的坑,我是異類,總之~辦法還是有的,首先我在腦海中搜索想到了奶綠茶有介紹過preact/signal,奶綠大大事我很喜歡的一位工程師,我大學時還買過他的書,不過那是另外一個故事了

內文

preact跟react是不一樣的框架,但其中的api友87%像,signal使用起來就像是vue裡面的ref(太詳細的內容自己去看原始碼),然後看網誌說明signal無法直接使用在react,特別是底層是vite打包的狀況下(該死)

所以不得已我又開始尋找其他法,後來發現preact/signal有出preact/react-signal特別優化的版本,這樣應該是妥了吧,官方都直接出優化了,直接使用在公司專案結果爆出getSnapshop is undefined......我去查了一下原始碼也一無所獲,歸功於react亂七八糟......抱歉我不該戰,是莫名其妙...抱歉,是我孤陋寡聞不得而知的底層運作機制,所以我放棄了這麼一條路

難道我就該止步於此繼續使用這該死又難寫......抱歉我不該戰,我要繼續使用這我用了很痛苦的框架嗎?當然不是,既然連官方都靠不住,那就只能自己來寫了,一開始我嘗試使用Object.defineProperty去寫(沒錯就是vue2那套,想看原始碼也附註解在裡面),反正所有東西都丟到value下面就好了吧,但也發生了跟vue2一樣的問題,沒有註冊過的屬性就不會reactive,但如果使用Proxy怕太新UC那些奇怪的瀏覽器不支援

沒錯公司要求產品要支援UC,講到這裡我想花一點篇幅抱怨一下,建議看到這裡的工程師,珍惜生命,遠離UC,下次面試記得要問主管公司有沒有要求要支援UC,有人說UC就像手幾板的ie我覺得他說錯了,他比ie還不如!不僅沒辦法debug,之前發生一次白屏,在我一行一行除錯,最後問題竟然是html排序它不滿意,不是什麼沒有加上</>這種低級錯誤喔,就是單純排序它不滿意,當時的狀況就類似以下:
<div>
    hello
    <div>hello2</div>
<div>
然後我把它改成
<div>hello<div>
<div>hello2</div>
就正常了.....我還以為我又用什麼新的寫法或物件戳到它的G點,Fuck!這我真的沒辦法誒!!

好拉,工作還是要繼續下去,但這還在研發階段之不支援我就先不考慮,之後不支援大不了不用!所以你以為我就乖乖刻輪子嗎?當然沒有,我想到既然底層都是Proxy那我用signal就好拉,那畫面不會reactive的問題怎麼處理?奶綠茶大大的網誌有提到react18有出一個新的hook是useSyncExternalStore 這個hook可以控制畫面要不要渲染,網誌裡面有提到大大如何包裝這個function,我試著去改寫但我一直嘗試不成功,因為我一直把變數放在function內,所以在rerender時function會重跑一遍,所以變數每次都會被重置(Fuck我就說寫法很不直覺吧),當我想到這問題時已經好幾天過去了,如果不想變數被重置,那就使用原生的hook來解決吧,最後產出來的code就如下

沒錯,前面說這麼多我只是想要抱怨,最終結果在下面,大家如果為了自己身體著想,可以直接拿去使用

我模擬出一些比較常用的hook,其他我也很少用就不做了,畢竟這就像那些砲轟我的網友說的,就是vue皮react骨,我也沒有不承認這件事,但更新資料畫面就會自動更新就是爽,也不用在那邊setState後還要在useEffect等狀態更新才能在function裡面執行我想執行的內容,這寫法真的很蠢......抱歉我不該戰,這寫法真的很不直覺

好吧廢話不多說直接上程式碼

import { useReactive } from "@reactivedata/react"
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react"
import { signal, effect, computed as preactComputed } from "@preact/signals-core"

export const onMounted = (fn) => {
useEffect(() => {
fn()
}, [])
}
export const onUnmounted = (fn) => {
useEffect(() => {
return () => {
fn()
}
}, [])
}

export const ref = (initValue) => {
const valueRef = useRef(initValue)
const versionRef = useRef(0)
function createStore(signal) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
valueRef.current = signal.value
versionRef.current++
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return versionRef.current
},
}
}
const res = signal(valueRef.current)
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res
// const _value = useRef(initValue)
// const [ref, setRef] = useState({ value: initValue })
// Object.defineProperty(ref, "value", {
// get() {
// return _value.current
// },
// set(newValue) {
// _value.current = newValue
// setRef({ value: newValue })
// },
// })
// return ref
}
export const reactive = useReactive
export const computed = (computeFn) => {
const res = preactComputed(computeFn)
function createStore(res) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
res.value
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return res.value
},
}
}
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res.value
}

export const watch = (sSatcher, fn) => {
const watcher = typeof sSatcher === "object" ? sSatcher : [sSatcher]
const value = useMemo(
() =>
watcher.map((x) => {
return typeof x == "function" ? x() : x
}),
[watcher]
)
const ref = useRef(value)
if (value.toString() !== ref.current.toString()) {
ref.current = value
}
useEffect(fn, ref.current)
}

export { effect }

這樣onMounted, onUnmounted也解決了useEffect裡面不能放async的function,那警告看了真的很礙眼,支援就不要吵,不支援就直接抱錯,在那邊吵什麼吵,然後為什麼不多包一層就好,我也不知道,可能礙於什麼東方神秘力量

reactive就直接用別人寫好的吧

watch就是watch

computed這麼好用的功能怎麼能不實現(但我沒實現getter、setter,沒錯因為我不常用)

ref就用useRef去解決變數被重置問題,我們來比較一下差別
// normal way
const [value, setValue] = useState(0)
setValue(1)
console.log(value) // 0 wtf!
useEffect(() => {
    console.log(value) // 1
}, [value])

// better way
const [value, setValue] = useState(0)
const newValue = 1
setValue(newValue)
console.log(newValue) // 1 .....why so troublesome?

// new way
const state = ref(0)
state.value++
console.log(state.value) // 1
// 484很直覺!484很爽!484很好寫


後記

最重要UC, QQ這些中國大牌瀏覽器到底有沒有支援Proxy呢~~答案是有的,至少上線到現在沒有傳出什麼災情
謎之聲:連Promise.race跟any都可以拔掉,竟然支援Proxy,真是可喜可賀~
不過想想也是拉,現在vue3都用Proxy寫了,如此泱泱大國總不會想跟自國工程師對著幹吧!

最後,我沒有實際測過使用起來的效能,我相信應該不會太好,畢竟有內容更新畫面就要重新渲染一次,原本react18 queue的優化都沒用了,但我才不管,開發體驗最重要!!事實上,你如果注重效能就不會選擇react框架了,那這篇就先到這裡拉,希望大家都可以準時下班,see u later~

12/18更新

computed並非是及時的,useSyncExternalStore只用來更新畫面,所以如果要用在邏輯演算中,用useMemo效果可能比較好,之後再來思考看有沒有機會debug,下面有個使用範例,這真令人有點難過

const users = [{ birthday: 2020-01-01}, {birthday:"2022-02-02"}]

let index = 0

const user = computed(() => users[index].birthday) 

console.log(user.birthday) // 2020-01-01

index = 1

console.log(user.birthday) // 2020-01-01 來不及更新


2023年11月20日 星期一

vue2 event hook destroyed

在官方建議寫法一般是這樣

mounted() {
this.$root.$on("getSelf", this.getSelf)
},
destroyed() {
this.$root.$off("getSelf")
},

但你也可以這樣寫

mounted() {
this.$root.$on("getSelf", this.getSelf)
this.$on("hook:destroyed", () => this.$root.$off("getSelf"))
},

這樣寫有什麼好處呢,主要防止邏輯四散,每個人寫作習慣不同,mounted跟destroyed不一定就在旁邊,有人mounted會寫一大串,最造成閱讀上沒這麼直覺。你可以在宣告偵聽時就確定他會在component destroyed時被取消偵聽減少資源浪費。這寫法就跟golang的defer類似。

其實早在很久前就有這樣寫了,只是沒記下來真的會忘記,到最近又碰到vue想要寫才把這招翻出來,所以就做個紀錄

2021年1月13日 星期三

vue-router也可以下錨點

小編之前在想對於單頁式的形象頁面,難道現在強大的響應式前端框架就無用武之力了嗎?那到不一定,今天來介紹一下vue-router裡面一個不常用,但小編覺得很可以的功能

簡單說就是透過vue-router的selector來指定頁面上的tag,透過behavior來形容行徑的過程,預設是直接跳過去,小編這邊用smooth平滑過去

// js
export default new Router({
  scrollBehavior: function (tofromsavedPosition) {
    if (to.hash) {
      return {
        selector: to.hash,
        behavior: "smooth"
      };
    } else {
      return { x: 0y: 0 };
    }
  },
  mode: "history",
  routes: []
});

也可以透過RouterLink直接跳(滑)到指定錨點

// template
<router-link :to="{ name: 'Homepage', hash: '#enter' }">Enter</router-link>


vue component的強制重新render

不知道大家有沒有遇過想要重置component的時候呢,不論是在validate後想要消除紅匡,或是在某些特定場合取到資料後,需要重新繪製畫面,最直覺的作法應該是透過if消除畫面後再重新顯示吧,程式碼如下

<template>
<div v-if="show">
hello
<button type="button" @click="rerender()"></button>
</div>
</template>

<script>
export default {
data: () => ({
show: true
}),
methods: {
rerender() {
this.show = false
this.$nextTick(() => {
this.show = true
}))
}
}
}
</script>

但這樣小編總覺得不是這麼直覺跟簡潔!

那有沒有更簡單的方式呢?有的!只要利用key這個屬性讓這個component屬於一個全新的物件就可以了

<template>
<div :key="index">
hello
<button type="button" @click="rerender()"></button>
</div>
</template>

<script>
export default {
data: () => ({
index: 0
}),
methods: {
rerender() {
this.index++
}
}
}
</script>


2021年1月7日 星期四

改變Vue sub component樣式

相信元件帶給大家的好處不言而喻,但使用上總是有一些不方便之處,例如三方的元件樣式我能否調成自己喜歡的?我自己的元件在不同情境下樣式能否不一樣?難道最終都難逃考比爬斯特,重新寫一個的命運嗎?

小編的任務就是盡可能幫各位提早下班,第一個問題,我們可以寫個全域css去改變他

先假設我們的子元件長這樣

// hello.vue
<div class="component-hello">
  hello
</div>
<style scoped>
  .component-hello {
    colorred
  }
</style>

父元件長這樣

// App.vue
<div class="app">
  <hello />
</div>

<style>
  .app .component-hello {
    colorblue
  }
</style>

在先不考慮css分數的情況下,這樣寫原則上就可以覆蓋component裡面的樣式。

雖然一般三方class寫都得不錯,不太會有重複的情況,但css汙染在我心目中跟watch一樣危險,有很多不可控的因素,所謂的不可控,所謂的黑魔法,就是當下可能會覺得很爽,一個禮拜之後就會變成導致你加班的原因,且一個不小心就會演變成泥沼戰(就是比誰宣告比較兇),所以能不要互相汙染、互相傷害就不要去做這種事,能寫sooped就給他用力寫,為這社會帶來一絲和諧吧!

然而你會發現當你加上了scoped,你原本吃的到css一夕之間的突然都吃不到了,原因是因為scoped會為該component的所有tag注入一個唯一的屬性(亂數產生),但卻不會為子component注入該屬性,所以導致子component吃不到上層scoped的css。那這問題怎麼要解呢?

這就是今天寫這篇主題的目的:那就是使用v-deep,用法很簡單如下。

// App.vue
<div class="app">
  <hello />
</div>

<style scoped>
  .app /deep/ .component-hello {
    colorblue
  }
</style>

這樣就可以控制在App.vue下的component-hello顏色才會是藍色,這樣也同時解決了我們上述的第二個問題,同一個元件在不同class下可以有不同樣式就是這樣來的。

大部分語言也可以簡寫成如下,我以stylus為例

<style lang="stylus" scoped>
  .app >>> .component-hello 
    color blue
</style>

scss或sass語言也可以簡寫成如下

<style lang="stylus" scoped>
  .app ::deep .component-hello 
    color blue
</style>

打完收工下班!

Vue component的v-model

承繼上一篇,我們在撰寫component時,除了要接value,如果data改變,同時也要emit出去,所以程式碼就會如下

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    data() {
      this.$emit("input"this.data);
    },
    value: {
      immediate: true,
      handler() {
        this.data = this.value;
      }
    }
  }
};

乍看之下也沒什麼問題,小編也這樣寫了好幾年了,但小編一直對於宣告一個data:""感到不是很舒服,雖然只是個過水變數,但沒有更乾淨的寫法嗎?還有小編其實一直對於watch很感冒,watch用得好可以帶你上天堂,用不好你就好幾天不用下班,甚至忙到你小孩覺得你只是個很有趣的叔叔!所以能不用就不要用,因此這幾天在賺國產車時想到一個妙招,可以使用computed的set阿!!直接超級簡化,我們直接來看程式碼

export default {
  props: ["value"],
  computed: {
    data: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("input"val);
      }
    }
  }
};

是不是簡單很多呢,打完收工下班!

Vue的watch

這篇來介紹vue的watch的進階使用immediate,

使用情境:以前在子元件要接父層的值,並要隨時監聽變化覆寫data時,都會在mounted時覆寫第一次,之後就依watcher即時覆寫,我們來比較一下兩個寫法

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    value() {
      this.data = this.value;
    }
  },
  mounted() {
    this.data = this.value;
  }
};

雖然功能正常,但上面這寫法會造成邏輯分散,閱讀不易,我們來試一下immediate

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    value: {
      immediate: true,
      deep: true// 深層監聽
      handler() {
        this.data = this.value;
      }
    }
  }
};

這樣就可以在渲染之初就把父層丟進來的值寫入啦,是不是又更簡潔了呢

Vue的ngInit

AngularJS是小編踏入前端的第一個語言,雖然目前已經幾乎是時代的眼淚了,所以小編後來轉戰了vue,在渲染效能及編寫習慣上各方面小編都比較喜歡,但有個功能是我踏入vue至今難忘的功能就是ngInit,不知道各位有沒有遇過:一個變數只出現在某個tag中,但我又不想宣告再data裡面,且變數常出現,這時ngInit就可以幫助你少寫很多code。

小編以前都用這方法達成類似功能,但相較之下顯得攏長

<div v-for="a in ['ABC']" :key="a">
  {{ a }}
</div>

我也曾經查過directive:"v-init"、"vue ngInit",等相關資料,但始終找不到有相關功能,但就在今天,在幫一個同事解決效能問題時,無意中看到他寫了:set屬性,經同事一番解釋,沒想到這就是我夢寐以求的功能,廢話不多說我們直接來看怎麼使用

<div :set="a = 'ABC'">
  {{ a }}
</div>

就是這麽簡單,變數a無須註冊在data裡面,就可以在set的tag裡面重複使用,這功能在很多情境都非常好用。


2021/01/08補充

今天再查詢找到了一個挺有趣的解法,因為還牽扯到大小寫,所以我認為不夠直觀,對我來說這並不是最佳解法,但也有紀錄的價值

// js
Vue.directive('init', {
  bind: function(elbindingvnode) {
    // convert kebab-case to camelCase
    let arg = binding.arg.split('-').map((argindex=> {
      return (index > 0) ? arg[0].toUpperCase() + arg.substring(1) : arg;
    }).join('');
    vnode.context[arg] = binding.value;
  }
});

// template 
<div v-init:my-var="'a'">
    {{ myVar }}
</div>


2016年10月20日 星期四

前端最近到底在紅些什麼??

前言:

這篇文章我希望可以快速帶大家了解一下近幾年前端發生的變化,那怕只有一點點,希望大家可以整理出一些頭緒,因為小編真的很不會教學= =

這篇文章全程將以Vue2.0(以下簡稱Vue)做為開發範例,依照之前我跟團隊提起這個名詞的經驗,很多人會問說這是啥洨,為什麼等問題,這很正常,小編會一一跟大家做解答。不過小編所學有限,大家想要有更詳細的了解,還是自行上網google一下比較好

在本文的一開始,必須重申,本篇的重點不是要教會大家一門技術,是希望帶給大家一些觀念,告訴大家現在前端正在流行的開發方式,只是藉由 Vue這個框架來達成這個目的。因為小編在摸完這麼多框架後發現,概念是一樣的,只是用的方法與語言不一樣。那既然是這幾年的新技術,那不免俗地有一拖拉庫的名詞必須解釋,那我們開始吧。


究竟什麼是Vue呢?

Vue(讀音聽起來像是view),這是一套前端JS的微型框架,比起有名的React & Angular算是後起之秀,他的開發者 尤雨溪,沒錯,看到這名子就知道他是個非常牛逼的中國人,他在美國讀研究所,畢業後曾任職於GOOGLE,不過現在已經離職(推測他的年紀好像跟小編差不多就是了),整個框架主打輕量化,所以每個套件都各司其職,因為程式全部由他一個人開發,所以修BUG也非常快,檔案非常小,相對加載速度也就快,且沒有相依。據官方數據,Vue在render的效能上是遠超過React跟NG2的(僅限2.0)。

隨著日漸穩定,也推出了 VueRouter(Router) & Vuex(Flux),甚至是 Vux(針對ui進行操作的涵式庫) 等套件,Vue日前幾乎跟NG2差不多時間推出了2.0,但不同於NG>NG2幾乎就是個打掉重寫的工程,兩個版本相似程度高達90%,甚至推出了升級工具 "Vue migration helper",裝完套件後,只要下一行指令,就可以照著他給你的提示一一做出修改,實在時非常便利。

小編之所以選擇 Vue的原因,是因為它開發模式偏向於NG(注重model),但他入門程度甚至比React還要低,而且非常低耦合,跟任何框架在一起都可以運作,也可以在專案開發中隨時導入,不一定要侷限於SPA或模塊式的開發。然後對於model的控制,你可以隨時改變model,甚至用第三方程式去改變都可以(例:jQuery ajax),自由度相當高。所以小編用完有點回不了頭。

Vue還有個最大的優點,因為作者是中國人,所以文件都是中文版!!!!(我葳葳中華,堂堂炎黃子孫,憑什麼叫我去學番邦語言!!),不過作者還是會先推出英文文件,才會逐漸翻譯成中文就是了。(人家畢竟在GOOGLE待過咩~是有世界觀的男人~)

官方說法,有基礎的工程師都可以在三天之後駕馭這套框架大半,可見這個框架真的不難,本篇重點是希望藉由這套框架讓大家知道這些年前端流行的開發趨勢,所以在環境架設開始之前,還有幾個技術名詞要了解一下

二話不說先附上官網


為什麼不選React or Angular2?

針對這三套框架的比較網路上文章一大堆,硬是要講原因就是

第一:vue夠簡單,隨時可以上手,輔助套件也相當充足穩定了現在

第二:相較React的learn once, write anywhere的jsx語法,專注於model的vue跟ng2寫法上算相當直覺

第三:它夠快,網路上也有很多效能比較的數據,大家可以自己GOOGLE看看。

第四:相依性超級低,我寫不下去的時候隨時可以回到jQuery繼續開發,等我哪天心情爽了之後再改回來。

 第五:更新速度也夠快,因為程式全部是作者一個人包,所以他承諾修BUG速度絕對比GOOGLE團隊修BUG速度快上許多(大家使用上有問題也可以去github上面反映issue,作者曾嗆angular2上面的 issue太多且很難關閉,另外,事實上有個團隊在維護拉,所以大家不用怕尤大如果萬一怎麼辦之類的事情發生)
 

它唯一的缺點:社群依然不夠龐大,使用者相較下是比較少的,在我今天介紹之前有人有聽過Vue的嗎?光看FB上 ReactJS .vs. Angular JS User Group Taiwan .vs. Vue.js台灣 三個社群人數比例是8000:5000:2000,React支持者一大堆我也不知道為什麼,明明就很難寫啊!!人數多少的影響應該是提問得到答案的速度,我初估大概差了五倍吧,不過都會在一天之內得到答案,所以都還可以接受。而且 Vue社群人數少,所以很團結跟友好,你有問題都會非常熱心回答(好吧~小編曾在ng社群提問被直接忽視= =)。另外你上百度發問還時不時會遇到作者出來幫你做專業的解答,作者在身邊,感覺就好像志玲姊姊坐在你隔壁上班,好像很厲害!(從小編撰寫這篇文章,人數只有1500至今短短兩個月,人數已上升了500,不塊是未來的新星)

就算你上面幾點通通都不認同,也沒有關係,小編就是愛用 Vue,你可以咬我,或者右上角有叉叉,君子請自重阿~~


那會碰到那些技術名詞呢?

Component:

大家可以把網頁上的任何一個tag都視為是一個元件(包含內部的其他子tag),至於這個元件要大到多大,要小到多小,並沒有明文規範,需要開發者憑經驗去判斷這個元件有沒有"必要"被寫成元件而已。
大家可以用mvc的技術去想像,這就像view裡面的layout一樣。

另外,在 Vue裡面的component跟ng2的component很像,有自己的html, css, js,只是它全部都寫在一起,並給予它副檔名為.vue的檔案,你會說這是什麼沒看過, 沒關係,你看不看得懂無所謂,不過webpack看得懂就好了。在檔案數量上,小編也比較喜歡 Vue的編制,不然ng2一個comonent就三個檔案,加上測試檔就四個了,這還讓不讓人生存阿。


模塊式開發:

為了避免所有程式碼都寫在同一頁,所以可以藉由 import 或 required 把原本要開發的程式拆成許多小塊(component, store.....)後引入,但browser 是看不懂 import 的,所以需要藉由 Browserify or systemJS or webpack or 百度的fis 和 支付寶的spm ......等套件做預處理,所謂預處理就是在borwser 讀取頁面之前,先做的處理。

開始體會到什麼叫做到名詞地獄了吧。就是很多的套件都在做相同的事情,不過雖然做的事情類似,卻還是有些許的不同,但那等我們有用到再討論吧,這種情況我們一般我們會選用最大宗去做,雖然不保證它不會被取代,但至少確保它會持續地更新,所以小編推薦使用webpack 做預處理工具。

 webpack 非常強大,我覺得可以再寫一篇文章專門在介紹這套工具了,大家可以把他理解成為一套 打包 與 編譯 的工具就可以了(剛剛提到的.vue或是.css .sass .less或是ES6的js等檔案也是在這裡做重新編譯),他可以把入口檔開始所import的所有檔案,包含CSS跟圖片檔,通通打包成 "1" 個JS檔,最後使用者只須要引入這個JS檔,網站就會開始運作了~是不是很利害呢。讓大家可以只專注在js的開發。

 依照個人需求,webpack也可以把所有引入的css另外合併成一個css,不過必須透過外掛處理,預設式全部寫在js中的。


Router:

有開發過.NET的人,應該對這個名詞不陌生,就是藉由網址路徑導道不同的controller & action 去引入不同的View,在前端也是一樣的,不過不是導到不同的Controller,而是在指定的tag中引入不同的Component,且頁面不會重新刷新面。

ps. 大家在裝 "vue-router" 的時候記得不要打成 "vue-route",這是完全不同的套件,小編曾經因為這個r卡了一整天。

一般在前端也有router的狀態下會建議前後端拆開,但就真的沒有預算或專案沒這麼大不需要拆開的狀況,遇到後端的router跟前端的router衝突怎麼辦呢?小編也還在研究,不多說直接附上網址


SPA:

我也很喜歡洗SPA,那寫SPA呢? SPA全文(Single Page Application),單頁應用程式,就是藉由Router引入不同的子頁面,達到不刷新頁面而局部更新的效果,以達到省流量,快速,還有局部開發的優勢。看一下示意圖


Flux:

關於這個名詞最早是由FB為了處理React資料流所提出來的一個設計模式。後來被廣泛開發成一許多套件,在Vue之中落實Flux設計模式的套件就叫做Vuex,大家可以直接理解為Vue之於Vuex就好像React之於Redux,一樣很多人會問Flux是做什麼的用的呢?大家想像一個三角形,每個節點都想像成一個Component,每個Component都有自己template, model, style, sub component.......,那元件之間該如何溝通呢? 答案就是藉由發射器 $emit,這個名詞有開發過nodejs的人應該不陌生,父層可以藉由屬性可以把物件或字串數值傳給sub component,而sub component可以藉由$emit可以把值再向上傳給父層,在vue之中還可以特別透過BusEvent物件,大家可以想像它就像個廣播器,讓這專案下的component都可以接收到事件觸發。只是如果component越來越多,或頁面一多,事件的處理就越來越繁瑣,所以最好的方法就是統一交由Store管理,每個component需要資料時統一跟Store存取,要變更資料時也統一直接對Store變更,接收到變更後,Store就會主動去通知所有有用到這個欄位的component更新顯示。

大家把剛剛的三角形壓成平面,把原本需要上上下下傳遞的事件全部弄成平面,這樣資料在傳遞上似乎就通暢多了。就好像員工電腦壞掉大家不用藉由MAIL向上司報告上司再向部門主管報告........這樣傳遞下去非常消耗時間成本。所以最好就是有位專員負責處理這些大小事務,員工(component) 直接去請(dispatch) IT(store) 來維修電腦(model),修好之後員工就可以直接用更新後的電腦上班了,這樣不是很棒嗎?


小結:

每個語言及框架,沒有對錯,只有其適合與不適合的地方,例如用傳統的寫法搭配jQuery不是沒辦法做到SPA,也不是沒辦法做到render跟two-way-binding,但就是撰寫起來會非常繁瑣,要綁定許多事件,外加問題可能會存在非常多,還有無論是開發或是渲染速度都會相對上較慢。

另外這邊要來小小澄清一下,React & NG2 也是可以做到非SPA的開發,只是因為撰寫語言或框架太大關係,要進入個人覺得綁手綁腳,所以儘管小編是先接觸這兩個技術,卻也還沒完全研究成功~

網路上曾經看過一篇文章,作者只是想要寫個AJAX,卻必須學會一拖拉庫的技術,就因為現在是2016年,用 jQuery已經是非常不潮的行為了。所以必須花上大把時間,學習一堆技術,最後達成我想要AJAX的這個需求。當然,這是個笑話,如果你只是想要個 ajax當然用 jQuery是最快的,但這也是個不爭的事實,確實,好的工程師必須要可以用最小的舉動達成最終的目的,但不懂得考量團隊合作與日後可維護性的工程師只是個自私的渾蛋,這也就是為什麼前端技術一直推出新技術,難道真的是吃飽沒事幹嗎? 當然是為了開發速度,效能,好維護,關注點分離,等因素被創造出來的。所以小編不希望各位看完這篇文章只是為了去寫個AJAX功能出來,而是希望各位抱持著擁抱學習,提升自己的想法看完這篇文章。

當然小編也不認為 jQuery已死,也不認為他在短時之內會死,你大可繼續使用你苦練了好幾年的技術繼續開發,只是現在前端的世界進步很快,沒有拋頭顱灑熱血的決心真的不適合待在這裡,據傳每18個月,技術就會困難一倍,所以不再是不進則退,進步得太慢也是退步!而且當我告訴你,學完這些技術之後你月薪起碼可以提升5000塊,儘管學習曲線陡峭,那你學不學呢?

不過很殘酷的是,用新技術開發完,就是開發者自己心裡覺得很屌,老闆覺得省了很多人事成本而已,就使用者的體驗上,並不會差太多,當然這是在 0 BUG的情況下比較。

FYI 連尤大也推薦的非同步套件axios

環境架設:

相信我,這絕對是所有語言都會到遇到,且是最麻煩的部分,這也是讓許多人不得其門而入的高牆,很多人學完 npm後打開 package.json看到一大堆沒看過的套件就繳械了,所以在外圍觀望了很久,小編就是個活生生的例子,但相信我,只要你勇敢踏出一步,你會發現其實沒有這麼難,很多東西只要配置好就可以一路順暢開發無阻礙,而且官方還推出 vue-cli或 angular2-cli之類的命令式語法,更加降低建置環境與開發的困難度。

不過我剛剛也說了 vue可以做到隨時導入,就算只學一點點也可以開發出簡易的data-flow,所以我這邊有兩種開發方式可以任君選擇,一種是無痛開發,另外一種是先苦後甘開發,大家可以依需求選擇


無痛開發:

只需要引用這麼一行,就可以開始寫vue程式了

優點:不需要經過太多手續,用傳統的方式引入library即可,對於要即時導入data-flow的專案有非常大的效果

缺點:如果引入的檔案過多效能差,且須留意引入順序,會在專案開發過程綁手綁腳,專案越大越明顯


先苦後甘開發:

附上我已經寫好的程式,各位只需要做好下面幾個步驟專案就可以運作了。

step1.

git clone https://github.com/lu7766lu7766/vue_practice.git

step2.

cd vue_practice

step3.

npm install

step4.

npm start

step5.

開啟瀏覽器輸入http://127.0.0.1:3000

裡面有個非常陽春的畫面,雖然是練習用,卻包含了上面我所陳述的所有技術,而且這個專案是我從無到全部有自己建置的,所以不會用到什麼深不可測的技術跟配置,畢竟小編也是魯蛇一條,所以一定很好懂,不懂也一定GOOGLE得到。


大家也可以直接透過vue-cli建置專案,免去環境建置的麻煩。

setp1.

npm install vue-cli -g

setp2.

vue init webpack project_name

step3.

cd project_name

step4.

npm run dev // dev是開發模式,待開發結束可以下npm run build就會產出最終檔案到 /dist資料夾,在把資料夾下的檔案放到伺服器即可執行

step5.

http://127.0.0.1:8080

既然有巨人的肩膀,就不要猶豫快站上去吧。當然如果大家想站在魯蛇的肩膀上也是可以拉。不過後面的範例說明都會用cli建立的專案說明喔。想要自學的人在用我建立的專案慢慢學習吧。

優點:可以真正達成模塊式開發,無須擔心引入的部分,速度快,適合團隊開發,落實關注點分離,適用於所有伺服器(但開發中仍是以nodejs為主),而且還有hot reload(熱加載,元件編輯完自動刷先新)

缺點:如果重頭開始件建置專案,第一次確實會頭很大,不過建置專案的重點都放在webpack即可,另外如果對建置環境不熟悉,就很難做到要從已開發專案中插入的需求,這也是為什麼一般人都會先選用無痛開發起步的原因

特別強調一下,為避免低級錯誤,用vue-cli建專案會引入 eslint, editorconfig套件,會特別強調撰寫風格,多一個空格少一個空格什麼的,這也會導致大家程式碼直接複製貼上卻run不出來的原因,沒關係大家改久就習慣了。或者大家可以自行修改 .editorconfig、.eslintrc.js 這兩個檔案,或直接刪掉。


程式開發:

下面程式所有的註解都是小編額外加上去的,如果有編譯失敗的情況記得要拿掉喔。

終於要進入我們的主題了,那主頁面index.html基本上我們只會動一行,程式碼如下

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>clitest</title>
    <script src="src/main.js"></script><!-- 加入入口檔案 -->
  </head>
  <body>
    <div id="app"></div><!-- built files will be auto injected -->
  </body>
</html>

我們來看一下主程式,小編會針對要講解的內容稍微做些簡化,未必會跟剛 init完的檔案一樣

/src/main.js
import Vue from 'vue'//引入/mode_modules/vue內的套件
import App from './App'//引入/src/App.vue檔案,因為webpack設定,附檔名可以省略
//import 'bootstrap/dist/css/bootstrap.css' //引入/mode_modules/bootstrap/dist/css/bootstrap.css,不過webpack也要做出相對應的設定才行
//下面就是Vue的宣告
new Vue({
  el: '#app',//指定id="app"的tag, selector的方法跟css一樣, id用#開頭
  template: '<App/>',//把template注入el所指定的element會被樣板取代變成 <div id="app"></div> => <App />
  components: { App }//宣告component and render,所以<App />就會被渲染過,至於渲染成什麼樣子,我們看App.vue這檔案
})

我們來看一下App component

/src/App.vue
<template> //開始宣告此component的template
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
  </div>
</template> //template宣告結束
<script> //開始宣告此component的vue物件
import Hello from './components/Hello'
export default {
  name: 'app',
  components: { //這裡可以在寫入sub component
    Hello
  }
}
</script> //vue物件宣告結束
<style> /*開始宣告css,這樣寫是否只有在此元件中才會吃到css,還是所有的子元件都會吃到,要試一下*/
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style> //cs宣告結束

跟ng2不太一樣的是,ng2會用一個資料夾去包 .html, .js, .css, .spec.js四個檔案,但這樣讓開發者看起來會很阿雜,所以保哥上課時就說開發者必須自行判斷元件有沒有必要被開發為元件,因為拆太細,檔案可是會一拖拉庫的,而在vue裡面,就只會有一個 .vue 檔案,就算你拆得比ng2細四倍,檔案也才一樣多而已,哼~

因為tag會自動被小寫化,所以命名規則,建議變數採駝峰式命名,tag採小寫,單字中間以"-"字號相連。如果和原本就有的 tag明相撞,那 component就不會產生作用,例如: header、footer,所以小編個人建議可以加上App後贅字,例如: HeaderApp,那tab就是<header-app></header-app>,這樣在render時就會辨識出sub component,並把component的template注入

因為SPA的開發,通常都會只有 一個入口,以這個專案來說就是 App component,你會發現它存在的位置跟其他component不太一樣,所以不會變動的 header, footer或 menu就可以放在這裡

接著我們來看一下Hello Component

/src/component/Hello.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1><!-- 大家有沒有看到跟ng一樣的寫法, 這行render出來換變成 => <h1> Welcome to Your Vue.js App </h1>-->
      <input type="text" v-model="msg"/><!-- vue跟ng1&ng2一樣,也充滿了語法糖,v-model,是要做到雙向綁定的意思,就是修改的同時,msg變數也會修改,那上面的這行字也會跟著修改,這就是雙向綁定神奇的地方啊 -->
      <br>
      <a v-bind:href="href">google</a><!-- 又是個語法糖,也可以簡化為:href,作用是用來綁定屬性,也可以藉由綁定屬性,component可以藉由prop取得資料或物件 -->
      <!-- 這裡render完會變成 => <a href="www.google.com">google</a> ,也有人會寫<a href="{{href}}">google</a>這樣是也可以運作的,只是會有少部份情況會出現不是你希望看到的畫面,或是效果,待各位自行研究了-->
      <br>
      <button v-on:click="onHandleClick">+1</button><!-- 又是個語法糖,也可以簡化為@click,作用是用來綁定事件 -->
      <span v-text="count"></span><!-- 如果客戶非常龜毛,跟你反應頁面剛開起來會看到一堆{{adf}}之類的亂碼,可以透過這個語法糖,讓vue啟動之後再把變數render出來 -->
      <h2 v-if="count>0">Hello Component</h2><!-- 如果不符合條件,tag將會被刪除,不是隱藏喔,隱藏要用v-show -->
    <br>
      <select v-model="selector">
          <option v-for="(val,index) in [1,2,3]" :value="index" v-text="val"></option>
          <!-- 今天講我們最後一個要說的語法糖v-for,類似foreach的效果,是在show陣列或物件資料不可或缺的存在 -->
          <!-- render完會變成如下
          <option value="0">1</option>
          <option value="1">2</option>
          <option value="2">3</option>
          -->
      </select>
    <!-- 當然還有v-once(只渲染一次) v-html(輸出內容是html格式,必須要這個語法) v-else....很多語法糖有待各位自行發掘,這就不一一展示 -->
  </div>
</template>
<script>
export default {
  name: 'hello',
  data () { // 這就是vue的model,各位當然也可以用ajax調回資料後,在一次把整包資料塞進來
    return {
      msg: 'Welcome to Your Vue.js App',
      href: 'http://www.google.com',
      count: 0
    }
  },
  methods: {//事件,function都寫在這裡
    onHandleClick () {
      this.count++ //this就可以直接指定到model
    }
  },
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> /*開始宣告此component的css,只有在此元件中才會吃到css,官方都這樣說了*/
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Vue 的撰寫風格,有寫過 ng的人說應該會很熟悉,都是在 html語法中寫入一堆語法糖。

Vue預設是one-way-binding的,當然也可以透過v-model來達到two-way-binding的效果,可以自由選擇,他不像react一樣,沒有綁成two-way-binding,input就不能進行修改。對新手來說感覺很差。

資料綁定跟畫面的花俏一點關係都沒有,只是讓大家可以把注意力放在model上面,以大幅減少data被複製出太多份,捉錯,或忘記render等 bug出現,所以這類框架就不適合形象網站,如果要動畫飛來飛去的你還是去找jQuery吧,不過如果你要寫的是系統,列表,後台,或是component會不斷重複利用的網站,例如社群網站,倒是會如魚得水。

這裡比起原始檔案,改了很多東西,因為我想把vue的基礎語法都在這裡說完。基礎就說到這了,接著我們來談談 router吧。


VueRouter

最直覺的想法就是router要寫在入口頁,也就是App component裡,那我們就來看一下要怎麼改吧。(下面的程式就完全都不一樣了,讀者可以參照著文章修改或加檔案)

首先在要在上層的 main.js Vue物件中增加 router設定,讓所有的 component都可以有自己的 router-view


/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router' //    引入router設定檔
/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App />',
  components: { App },
  router //same with router:router
})


/src/App.vue
<template>
  <div id="app">
    <router-link to="/">Hello</router-link> <!-- 會被編譯成為<a....> -->
    <router-link to="/bar/123">Go to Bar</router-link>
    <img src="./assets/logo.png">
    <router-view></router-view> <!-- router完的component就會被丟到這裡 -->
  </div>
</template>
<script>
export default {
  name: 'app'
}
</script>
<style>.......</style>

這裡應該只有一個檔案有問題,就是router這個檔案,我們來看一下,設定檔怎麼寫吧


/src/route.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Hello from 'component/Hello'
const Bar = {
  template: `<div>bar {{ $route.params.id }}</div>` //取得路由參數
}
const routes = [
  { path: '/', component: Hello }, //預設就是Hello物件
  { path: '/bar/:id', component: Bar }
]
Vue.use(VueRouter)
const router = new VueRouter({
  routes // same with routes: routes
})
export default router

在這樣就可以達成router了喔,且裡面也可以丟參數,可藉由$route.params取得,是不是很簡單呢 ~

router學完接著我們來學flux,那我們會用到vuex套件,沒有的人一樣,記得裝一下,裝完記得重新run專案。

另外來小小解釋一下 ` (模板字符 原文:template string)。這是ES6特有的符號 ,可以在裡面寫入多行的字串,不需要傳統的傳統的 結束字串符號,然後相加,然後在第二行.....,甚至可以寫入變數,(例如: var a=100; str = `a = ${a}` => b =  "a = 100";) ,程式會經過 babel轉譯成 ES5 (傳統的雙引號字串相加),另外小編推薦用VSCode開發也不會出現令人惱怒的紅色蝌蚪,所以各位可以不用顧慮,大方使用。

VSCode裡面也有許多好用的套件,可以加速大家開發的速度與激情便利性,等待大家去發掘喔~


Vuex

因為我們層數沒有很多,那我們資料就從最上層的App component塞,然後從Hello component取吧,來看一下程式該怎麼改。 

一樣要在最上層的main.js加入vuex設定,確保所有的 component都可以吃到 store


/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App />',
  components: { App },
  router,
  store
})

/src/App.vue
<template>
  <div id="app">
    .....
    <button @click="countAdd">+1</button>
  </div>
</template>
<script>
......
export default {
  name: 'app',
  methods:{
    countAdd:function(){ // 會增加存在於store的count變數
      this.$store.dispatch({ // 有沒有跟redux很像
        type: 'incrementAsync'
      })
    }
  }
  ......
}
</script>
<style>.......</style>

 那我們來看一下該怎麼接資料呢

/src/component/Hello.vue
<template>
  <div class="hello">
    <h1>{{ count }}</h1>
  </div>
</template>
<script>
export default {
  name: 'hello',
  computed: { //vue中的計算屬性,功能等同於.net當中的get{......}
    count () { // 因為store.state.count並不屬於此component,所以必須藉由這樣轉傳
      return this.$store.state.count // 直接回傳即可,當然想*2或加些料什麼的也是可以
    }
  }
}
</script>
<style scoped> ......</style>

跟React最大不同就是vue跟ng2都會有自己component的model,當template再取值的時候沒辦法直接取store中的值,必須靠計算屬性轉一手。

最後我們來看一下最重要的store怎麼設計


/src/store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    incrementAsync ({commit, state}, payload) {
      // setTimeout(() => {
      //   commit('increment',payload.amount)
      // }, 1000)
      commit('increment')
    }
  }
})

大家應該一樣沒然為什麼這裡也要經過 action commit再到 mutations之後才去更新count,依據官方的說法,mutations才有資格去更改state的值,但是mutations是同步處裡的,所以如果需要異步處裡的功能需要藉由action來做到,這也就是為什麼我留了個setTimeout給大家參考的原因。

 

當然ditpatch也是可以待參數的。另外如果有接觸過redux的人也可能會問,有沒有類似 reducer的東西可以用,不然專案越來越大,會越來越亂,有!你要什麼都有!不過在vuex不叫 reducer,而叫 module,或者一個畫面想要兩個路由 component,或 router裡面還要有router可以嗎?可以!至於怎麼用呢?

這又是另外一個故事了(遠目~),不難拉 ~ 自己去查 ~


總結:以上就是vue的所有基礎知識,以及前端目前流行的開發方式,對各位來說可能會是個知識爆炸的一天,沒關係,小編也是過來人,I got you ~~ 不過程式這種東西還是要自己實做過一次,才就懂每一行程式到底都在做些什麼,特別是前端 JS,不經過自己手上實做出來,並看到變化,實在抽象到難以想像。沒關係大家可以慢慢消化,大家還有18個月再去迎接更難的程式。希望今天的課程內容可以帶著大家從 網站設計師 朝向 前端攻城屍 之路向前邁進一步!剩下的大家就自行研究了,希望大家可以享受開發的過程,有不懂的地方,或想要更加深入了解的地方,也歡迎隨時提問或直接敲我討論,以上謝謝大家。


終於打完了~比我以前寫的小說還長~好想哭阿~~~~