by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但須要保留原做者和出處。javascript
有了前面文章的鋪墊,相信一路看過來的新手的你開發一箇中型的 Vuejs 應用已經不在話下,包括 Vuejs 生態核心工具(vue-router,vuex)的使用也不成問題。可是在實際項目開發過程當中,咱們要作的工做不只僅是完成咱們的業務代碼,當一個需求完成後,咱們還須要考慮更多後期優化工做,本篇主要講述代碼層面的優化。html
咱們先回到上一篇的狀態管理案例,使用 vuex
方式共享咱們的 msg
屬性,先建立 src/store/index.js
:vue
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const types = {
UPDATE_MSG: 'UPDATE_MSG',
};
const mutations = {
[types.UPDATE_MSG](state, payload) {
state.msg = payload.msg;
},
};
const actions = {
[types.UPDATE_MSG]({ commit }, payload) {
commit(types.UPDATE_MSG, payload);
},
};
export default new Vuex.Store({
state: {
msg: 'Hello world',
},
mutations,
actions,
});
複製代碼
而後在組件 comp1
中使用它:java
<template>
<div class="comp1">
<h1>Component 1</h1>
<input type="text" v-model="msg">
</div>
</template>
<script> export default { name: 'comp1', data() { const msg = this.$store.state.msg; return { msg, }; }, watch: { msg(val) { this.$store.dispatch('UPDATE_MSG', { msg: val }); }, }, }; </script>
複製代碼
一樣對 comp2
作相同修改。固然還得在 src/main.js
中引入:git
import Vue from 'vue';
import App from './App';
import store from './store';
Vue.config.productionTip = false;
/* eslint-disable no-new */
new Vue({
store,
el: '#app',
template: '<App/>',
components: { App },
});
複製代碼
若是還不知道 vuex 基本使用,建議先閱讀官方文檔。github
好了,咱們已經實現 msg
的共享了,而且對其變化進行了 watch
,在輸入框發生改變時,經過 $store.dispatch
來觸發相應 UPDATE_MSG
actions 操做,實現狀態修改。可是你會發現修改 comp1
中的輸入框,經過 vue-devtools
也可查看到 Vuex 中的的 state.msg
的確也跟着變了,可是 comp2
中輸入框並無發生改變,固然這由於咱們初始化 msg
時,是直接變量賦值,並未監聽 $store.state.msg
的變化,因此兩個組件無法實現同步。vue-router
有人又會說了,再添加個 watch
屬性,監聽 $store.state.msg
改變,從新賦值組件中的 msg
不就好了,確實能夠實現,可是這樣代碼是否是不太優雅,爲了一個簡單的 msg
同步,咱們須要給 data
添加屬性,外加兩個監聽器,是否是太不划算?vuex
其實這裏是能夠經過計算屬性很好地解決的,由於組件中的 msg
就是依賴 $store.state.msg
的,咱們直接定義計算屬性 msg
,而後返回不就能夠了。api
ok,修改 comp1
以下:bash
<template>
<div class="comp1">
<h1>Component 1</h1>
<input type="text" v-model="msg">
</div>
</template>
<script> export default { name: 'comp1', computed: { msg() { return this.$store.state.msg; }, }, }; </script>
複製代碼
咱們再次修改 comp1
中的輸入框,打開控制檯,會報以下錯誤:
vue.esm.js?efeb:591 [Vue warn]: Computed property "msg" was assigned to but it has no setter.
...
複製代碼
由於咱們使用的是 v-model
來綁定 msg
到 input 上的,當輸入框改變,必然觸發 msg
的 setter(賦值)
操做,可是計算屬性默認會幫我定義好 getter
,並未定義 setter
,這就是爲何會出現上面錯誤提示的緣由,那麼咱們再自定義下 setter
吧:
<template>
<div class="comp1">
<h1>Component 1</h1>
<input type="text" v-model="msg">
</div>
</template>
<script> export default { name: 'comp1', computed: { msg: { get() { return this.$store.state.msg; }, set(val) { this.$store.dispatch('UPDATE_MSG', { msg: val }); }, }, }, }; </script>
複製代碼
能夠看到,咱們正好能夠在 setter
中,也就是修改 msg
值得時候,將其新值傳遞到咱們的 vuex
中,這樣豈不是一箭雙鵰了。一樣的對 comp2
作相同修改。運行項目,你會發祥,comp1 輸入框的值
、comp2 輸入框的值
和 store 中的值
實現同步更新了。並且相對與上面的方案,代碼量也精簡了不少~
先來看段代碼:
// ...
watch: {
username() {
this.getUserInfo();
},
},
methods: {
getUserInfo() {
const info = {
username: 'yugasun',
site: 'yugasun.com',
};
/* eslint-disable no-console */
console.log(info);
},
},
created() {
this.getUserInfo();
},
// ...
複製代碼
這裏很好理解,組件建立的時候,獲取用戶信息,而後監聽用戶名,一旦發生變化就從新獲取用戶信息,這個場景在實際開發中很是常見。那麼能不能再優化下呢?
答案是確定的。其實,咱們在 Vue 實例中定義 watcher
的時候,監聽屬性能夠是個對象的,它含有三個屬性: deep
、immediate
、handler
,咱們一般直接以函數的形式定義時,Vue 內部會自動將該回調函數賦值給 handler
,而剩下的兩個屬性值會默認設置爲 false
。這裏的場景就能夠用到 immediate
屬性,將其設置爲 true
時,表示建立組件時 handler
回調會當即執行,這樣咱們就能夠省去在 created
函數中再次調用了,實現以下:
watch: {
username: {
immediate: true,
handler: 'getUserInfo',
},
},
methods: {
getUserInfo() {
const info = {
username: 'yugasun',
site: 'yugasun.com',
};
/* eslint-disable no-console */
console.log(info);
},
},
複製代碼
首先默認項目路由是經過 vue-router 實現的,其次咱們的路由是相似下面這樣的:
// ...
const routes = [
{
path: '/',
component: Index,
},
{
path: '/:id',
component: Index,
},
];
複製代碼
公用的組件 src/views/index.vue
代碼以下:
<template>
<div class="index">
<router-link :to="{path: '/1'}">挑戰到第二頁</router-link><br/>
<router-link v-if="$route.path === '/1'" :to="{path: '/'}">返回</router-link>
<h3>{{ username }} </h3>
</div>
</template>
<script> export default { name: 'Index', data() { return { username: 'Loading...', }; }, methods: { getName() { const id = this.$route.params.id; // 模擬請求 setTimeout(() => { if (id) { this.username = 'Yuga Sun'; } else { this.username = 'yugasun'; } }, 300); }, }, created() { this.getName(); }, }; </script>
複製代碼
兩個不一樣路徑使用的是同一個組件 Index
,而後 Index 組件中的 getName
函數會在 created
的時候執行,你會發現,讓咱們切換路由到 /1
時,咱們的頁面並未改變,created
也並未從新觸發。
這是由於
vue-router
會識別出這兩個路由使用的是同一個組件,而後會進行復用,因此並不會從新建立組件,那麼created
周期函數天然也不會觸發。
一般解決辦法就是添加 watcher
監聽 $route
的變化,而後從新執行 getName
函數。代碼以下:
watch: {
$route: {
immediate: true,
handler: 'getName',
},
},
methods: {
getName() {
const id = this.$route.params.id;
// 模擬請求
setTimeout(() => {
if (id) {
this.username = 'Yuga Sun';
} else {
this.username = 'yugasun';
}
}, 300);
},
},
複製代碼
ok,問題是解決了,可是有沒有其餘不用改動 index.vue
的偷懶方式呢?
就是給 router-view
添加一個 key
屬性,這樣即便是相同組件,可是若是 url
變化了,Vuejs就會從新建立這個組件。咱們直接修改 src/App.vue
中的 router-view
以下:
<router-view :key="$route.fullPath"></router-view>
複製代碼
大多數狀況下,從父組件向子組件傳遞數據的時候,咱們都是經過 props
實現的,好比下面這個例子:
<!-- 父組件中 -->
<Comp3 :value="value" label="用戶名" id="username" placeholder="請輸入用戶名" @input="handleInput" >
<!-- 子組件中 -->
<template>
<label>
{{ label }}
<input :id="id" :value="value" :placeholder="placeholder" @input="$emit('input', $event.target.value)" />
</label>
</template>
<script> export default { props: { id: { type: String, default: 'username', }, value: { type: String, default: '', }, placeholder: { type: String, default: '', }, label: { type: String, default: '', }, }, } </script>
複製代碼
這樣一階組件,實現起來很簡單,也沒什麼問題,咱們只須要在子組件的 props
中寫一遍 id, value, placeholder...
這樣的屬性定義就能夠了。可是若是子組件又包含了子組件,並且一樣須要傳遞 id, value, placeholder...
呢?甚至三階、四階...呢?那麼就須要咱們在 props
中重複定義不少遍了,這怎麼能忍呢?
因而 vm.$attrs 能夠閃亮登場了,先來看官方解釋:
包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件—— 在建立高級別的組件時很是有用。
做者還特別強調了 在建立高級別的組件時很是有用
,他就是爲了解決剛纔我提到的問題的。它也沒什麼難度,那麼趕忙用起來吧,代碼修改以下:
<!-- 父組件中 -->
<Comp3 :value="value" label="用戶名" id="username" placeholder="請輸入用戶名" @input="handleInput" >
<!-- 子組件中 -->
<template>
<label>
{{ $attrs.label }}
<input v-bind="$attrs" @input="$emit('input', $event.target.value)" />
</label>
</template>
<script> export default { } </script>
複製代碼
這樣看起來是否是清爽多了,並且就算子組件中再次引用相似的子組件,咱們也不怕了。由於有了 $attrs
,哪裏不會點哪裏......
固然 Vuejs 的實踐技巧遠不止如此,這裏只是總結了我的在實際開發中遇到的,並且正好是不少朋友容易忽視的地方。若是你有更好的實踐方法,歡迎評論或者發郵件給我,一塊兒交流學習。