[vue]入門者常見問題

  • 3046
  • 0
  • vue
  • 2017-09-07

主要是在看一些人寫vue的原始碼,總結一些可能剛接觸前端框架的人,一些比較不好的寫法改進和建議

前言

筆者常常需要幫同事看程式碼解問題,或者是在社群看到一些發問的問題,常常會覺得有些寫法雖然效果有也能動,但就是覺得寫法不聰明或不太好,甚至只是一眛的over design,有時候想要糾正,但如果只是嘴巴講講,可能人家看不懂也無法體會,甚至覺得我只是會講(嘴炮)而已,根本都沒有動手寫出來讓人家看,所以特別整理一篇我曾經看過不太好的做法,在此分享給不管是剛入門vue的人,或者是未來自己身邊的同事朋友做參考。

導覽

  1. 請以資料影響畫面,而不是已dom去影響畫面
  2. 把class和style的binding搞清楚
  3. 請把元件的數據分門別類放好
  4. 元件的溝通善用語法糖
  5. 沒有切分元件的概念
  6. 不是任何情境都適用vuex
  7. 結論

請以資料影響畫面,而不是已dom去影響畫面

只要看到任何入門前端框架的人,我都會奉勸這個觀念,但是有時候或許很多人看不懂官網,或者是懶得看官網,只求效果有就好了,就會再回到用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的binding搞清楚

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都是一個很重要的原則。

不是任何情境都適用vuex

有很多入門者寫vue就一定得用vuex,寫react就一定得用redux,但在vue裡面根本不一定需要用vuex,因為vue在同層的two way binding相對方便許多,而且還有很多語法糖來幫忙元件溝通,全局的狀態也有event bus可以達成,所以你真的不一定需要vuex,就算情境複雜到需要用vuex了,也不是全部都要使用vuex,除非是為了統一所有團隊成員的做法,而犧牲效率來換取秩序。

結論

以上是我觀察出來的一些問題,但是這些問題並沒有標準答案,所以很多寫法都是我的觀點,如果有任何高手覺得有更好的做法或建議,再請指導討論。