主要是在看一些人寫vue的原始碼,總結一些可能剛接觸前端框架的人,一些比較不好的寫法改進和建議
前言
筆者常常需要幫同事看程式碼解問題,或者是在社群看到一些發問的問題,常常會覺得有些寫法雖然效果有也能動,但就是覺得寫法不聰明或不太好,甚至只是一眛的over design,有時候想要糾正,但如果只是嘴巴講講,可能人家看不懂也無法體會,甚至覺得我只是會講(嘴炮)而已,根本都沒有動手寫出來讓人家看,所以特別整理一篇我曾經看過不太好的做法,在此分享給不管是剛入門vue的人,或者是未來自己身邊的同事朋友做參考。
導覽
只要看到任何入門前端框架的人,我都會奉勸這個觀念,但是有時候或許很多人看不懂官網,或者是懶得看官網,只求效果有就好了,就會再回到用dom的思維,就會取dom來塞回資料,其實綁定數據的做法才會更聰明和方便,任何時候都以資料影響畫面的方式,來思考你怎麼解決問題,除非萬不得已才用dom的方式來處理,但我真的很少遇到需要用dom才能處理的問題。
bad
<template>
<div>
<select ref="select1" @change="optionChange">
<option value=""></option>
<option v-for="option in options" :value="option.text" :key="option.id">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selectA }}</span>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
selectA: '',
options: [
{ id: 1, text: 'One' },
{ id: 2, text: 'Two' },
{ id: 3, text: 'Three' }
]
}
},
methods: {
optionChange () {
this.selectA = this.$refs.select1.value
}
}
}
</script>
good
<template>
<div>
<select v-model="selectA">
<option value=""></option>
<option v-for="option in options" :value="option.text" :key="option.id">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selectA }}</span>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
selectA: '',
options: [
{ id: 1, text: 'One' },
{ id: 2, text: 'Two' },
{ id: 3, text: 'Three' }
]
}
}
}
</script>
class和style用法差不多,一種是用對象綁定,一種則是用陣列的方式,要區分何時使用對象何時使用陣列可以這樣想像,對象只是確認是否要套class,而陣列可以確認要套用哪個class name,我曾經看過奇怪的寫法。
bad
<template>
<div>
<span :class="{'color-red':show,'color-white':!show}">hello world</span>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
show: true
}
}
}
</script>
<style scoped>
.color-red {
color: red;
}
.color-white {
color: white;
}
</style>
good
<template>
<div>
<span :class="helloWorldColor">hello world</span>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
show: true
}
},
computed: {
helloWorldColor () {
return [this.show ? 'color-red' : 'color-white']
}
}
}
</script>
<style scoped>
.color-red {
color: red;
}
.color-white {
color: white;
}
</style>
想像一下元件裡面完全沒有分門別類的如下樣子
data () {
return {
msg: 'Welcome to Your Vue.js App',
showDisplay: '',
isLogin: false,
name: '',
password: ''
}
}
有分類的資料應該會如下
data () {
return {
view: {
msg: 'Welcome to Your Vue.js App',
showDisplay: '',
isLogin: false
},
form: {
name: '',
password: ''
}
}
}
你覺得之後回來看程式碼的時候,哪個比較好理解?
bad
data () {
return {
msg: 'Welcome to Your Vue.js App',
showDisplay: '',
isLogin: false,
name: '',
password: ''
}
},
methods: {
submit () {
let formData = {
name: this.name, password: this.password
}
axios.post('api', formData)
}
}
good
data () {
return {
view: {
msg: 'Welcome to Your Vue.js App',
showDisplay: '',
isLogin: false
},
form: {
name: '',
password: ''
}
}
},
methods: {
submit () {
axios.post('api', this.form)
}
}
其實我們都知道元件的溝通是使用props和emit來溝通,但是vue還有兩種方式,一種是使用.sync,一種則是自定義v-model,這邊需要特別注意,雖然這兩種語法糖對使用元件的人來說是two way binding,但是對實做此功能的人來說,全部都是手動指定何時emit的,所以只是給調用者的方便而已,這跟angularjs元件間的two way binding是本質上的完全不相同
傳統的props和emit寫法
Parent.vue
<template>
<div class="hello">
<child :count="count" @getCount="onGetCount"></child>
{{count}}
</div>
</template>
<script>
import child from './Child.vue'
export default {
name: 'hello',
components: {
child
},
data () {
return {
count: 0
}
},
methods: {
onGetCount (value) {
this.count = value
}
}
}
</script>
Child.vue
<template>
<div>
<input type="number" v-model.number="localCount">
</div>
</template>
<script>
export default {
name: 'child',
props: {
count: Number
},
computed: {
localCount: {
get () {
return this.count
},
set (value) {
this.$emit('getCount', value)
}
}
}
}
</script>
sync的寫法
Parent.vue
<template>
<div class="hello">
<child :count.sync="count"></child>
{{count}}
</div>
</template>
<script>
import child from './Child.vue'
export default {
name: 'hello',
components: {
child
},
data () {
return {
count: 0
}
}
}
</script>
Child.vue
<template>
<div>
<input type="number" v-model.number="localCount">
</div>
</template>
<script>
export default {
name: 'child',
props: {
count: Number
},
computed: {
localCount: {
get () {
return this.count
},
set (value) {
this.$emit('update:count', value)
}
}
}
}
</script>
v-model的寫法
Parent.vue
<template>
<div class="hello">
<child v-model="count"></child>
{{count}}
</div>
</template>
<script>
import child from './Child.vue'
export default {
name: 'hello',
components: {
child
},
data () {
return {
count: 0
}
}
}
</script>
Child.vue
<template>
<div>
<input type="number" v-model.number="localCount">
</div>
</template>
<script>
export default {
name: 'child',
props: {
value: Number
},
computed: {
localCount: {
get () {
return this.value
},
set (value) {
this.$emit('input', value)
}
}
}
}
</script>
可以感受三段code差異並不大,但是對呼叫層來說方便多了,不過v-model和sync好像功能差不多,怎麼選擇呢?我個人比較偏好的方式則是類別和陣列就使用sync,其餘的則使用v-model。
我看過不少人只用到vue的binding功能,重覆的程式碼或html分散在不同頁面裡面,其實入門vue.js相對angular或react真的是最簡單的,但是很多人也只求功能可以動,根本不在乎後續的維護性,不管寫任何程式碼,DRY都是一個很重要的原則。
有很多入門者寫vue就一定得用vuex,寫react就一定得用redux,但在vue裡面根本不一定需要用vuex,因為vue在同層的two way binding相對方便許多,而且還有很多語法糖來幫忙元件溝通,全局的狀態也有event bus可以達成,所以你真的不一定需要vuex,就算情境複雜到需要用vuex了,也不是全部都要使用vuex,除非是為了統一所有團隊成員的做法,而犧牲效率來換取秩序。
以上是我觀察出來的一些問題,但是這些問題並沒有標準答案,所以很多寫法都是我的觀點,如果有任何高手覺得有更好的做法或建議,再請指導討論。