你也許不知道的Vuejs - 最佳實(shí)踐(1)

you-may-not-know-vuejs.png

by yugasun from https://yugasun.com/post/you-may-not-know-vuejs-13.html
本文可全文轉(zhuǎn)載食棕,但需要保留原作者和出處。

有了前面文章的鋪墊柬讨,相信一路看過來的新手的你開發(fā)一個(gè)中型的 Vuejs 應(yīng)用已經(jīng)不在話下罚舱,包括 Vuejs 生態(tài)核心工具(vue-router继薛,vuex)的使用也不成問題。但是在實(shí)際項(xiàng)目開發(fā)過程中诫隅,我們要做的工作不僅僅是完成我們的業(yè)務(wù)代碼,當(dāng)一個(gè)需求完成后帐偎,我們還需要考慮更多后期優(yōu)化工作逐纬,本篇主要講述代碼層面的優(yōu)化。

被忽視的 setter 之計(jì)算屬性

我們先回到上一篇的狀態(tài)管理案例肮街,使用 vuex 方式共享我們的 msg 屬性风题,先創(chuàng)建 src/store/index.js

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 中使用它:

<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>

同樣對(duì) comp2 做相同修改。當(dāng)然還得在 src/main.js 中引入:

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 基本使用嫉父,建議先閱讀官方文檔沛硅。

好了,我們已經(jīng)實(shí)現(xiàn) msg 的共享了绕辖,并且對(duì)其變化進(jìn)行了 watch摇肌,在輸入框發(fā)生改變時(shí),通過 $store.dispatch 來觸發(fā)相應(yīng) UPDATE_MSG actions 操作仪际,實(shí)現(xiàn)狀態(tài)修改围小。但是你會(huì)發(fā)現(xiàn)修改 comp1 中的輸入框,通過 vue-devtools 也可查看到 Vuex 中的的 state.msg 的確也跟著變了树碱,但是 comp2 中輸入框并沒有發(fā)生改變肯适,當(dāng)然這因?yàn)槲覀兂跏蓟?msg 時(shí),是直接變量賦值成榜,并未監(jiān)聽 $store.state.msg 的變化框舔,所以兩個(gè)組件沒法實(shí)現(xiàn)同步。

有人又會(huì)說了赎婚,再添加個(gè) watch 屬性刘绣,監(jiān)聽 $store.state.msg 改變,重新賦值組件中的 msg 不就行了挣输,確實(shí)可以實(shí)現(xiàn)纬凤,但是這樣代碼是不是不太優(yōu)雅,為了一個(gè)簡(jiǎn)單的 msg 同步撩嚼,我們需要給 data 添加屬性停士,外加兩個(gè)監(jiān)聽器,是不是太不劃算完丽?

其實(shí)這里是可以通過計(jì)算屬性很好地解決的向瓷,因?yàn)榻M件中的 msg 就是依賴 $store.state.msg 的,我們直接定義計(jì)算屬性 msg舰涌,然后返回不就可以了。

ok你稚,修改 comp1 如下:

<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>

我們?cè)俅涡薷?comp1 中的輸入框瓷耙,打開控制臺(tái)朱躺,會(huì)報(bào)如下錯(cuò)誤:

vue.esm.js?efeb:591 [Vue warn]: Computed property "msg" was assigned to but it has no setter.
...

因?yàn)槲覀兪褂玫氖?v-model 來綁定 msg 到 input 上的,當(dāng)輸入框改變搁痛,必然觸發(fā) msgsetter(賦值)操作长搀,但是計(jì)算屬性默認(rèn)會(huì)幫我定義好 getter,并未定義 setter鸡典,這就是為什么會(huì)出現(xiàn)上面錯(cuò)誤提示的原因源请,那么我們?cè)僮远x下 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 值得時(shí)候谁尸,將其新值傳遞到我們的 vuex 中,這樣豈不是一舉兩得了纽甘。同樣的對(duì) comp2 做相同修改良蛮。運(yùn)行項(xiàng)目,你會(huì)發(fā)祥悍赢,comp1 輸入框的值决瞳、comp2 輸入框的值store 中的值 實(shí)現(xiàn)同步更新了。而且相對(duì)與上面的方案左权,代碼量也精簡(jiǎn)了很多~

可配置的 watch

先來看段代碼:

// ...
watch: {
    username() {
      this.getUserInfo();
    },
},
methods: {
  getUserInfo() {
    const info = {
      username: 'yugasun',
      site: 'yugasun.com',
    };
    /* eslint-disable no-console */
    console.log(info);
  },
},
created() {
  this.getUserInfo();
},
// ...

這里很好理解皮胡,組件創(chuàng)建的時(shí)候,獲取用戶信息赏迟,然后監(jiān)聽用戶名屡贺,一旦發(fā)生變化就重新獲取用戶信息,這個(gè)場(chǎng)景在實(shí)際開發(fā)中非常常見瀑梗。那么能不能再優(yōu)化下呢烹笔?

答案是肯定的。其實(shí)抛丽,我們?cè)?Vue 實(shí)例中定義 watcher 的時(shí)候谤职,監(jiān)聽屬性可以是個(gè)對(duì)象的,它含有三個(gè)屬性: deep亿鲜、immediate允蜈、handler,我們通常直接以函數(shù)的形式定義時(shí)蒿柳,Vue 內(nèi)部會(huì)自動(dòng)將該回調(diào)函數(shù)賦值給 handler饶套,而剩下的兩個(gè)屬性值會(huì)默認(rèn)設(shè)置為 false。這里的場(chǎng)景就可以用到 immediate 屬性垒探,將其設(shè)置為 true 時(shí)妓蛮,表示創(chuàng)建組件時(shí) handler 回調(diào)會(huì)立即執(zhí)行,這樣我們就可以省去在 created 函數(shù)中再次調(diào)用了圾叼,實(shí)現(xiàn)如下:

watch: {
  username: {
    immediate: true,
    handler: 'getUserInfo',
  },
},
methods: {
  getUserInfo() {
    const info = {
      username: 'yugasun',
      site: 'yugasun.com',
    };
    /* eslint-disable no-console */
    console.log(info);
  },
},

Url改變但組件未變時(shí)蛤克,created 無法觸發(fā)的問題

首先默認(rèn)項(xiàng)目路由是通過 vue-router 實(shí)現(xiàn)的捺癞,其次我們的路由是類似下面這樣的:

// ...
const routes = [
  {
    path: '/',
    component: Index,
  },
  {
    path: '/:id',
    component: Index,
  },
];

公用的組件 src/views/index.vue 代碼如下:

<template>
  <div class="index">
    <router-link :to="{path: '/1'}">挑戰(zhàn)到第二頁(yè)</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;
      // 模擬請(qǐng)求
      setTimeout(() => {
        if (id) {
          this.username = 'Yuga Sun';
        } else {
          this.username = 'yugasun';
        }
      }, 300);
    },
  },
  created() {
    this.getName();
  },
};
</script>

兩個(gè)不同路徑使用的是同一個(gè)組件 Index,然后 Index 組件中的 getName 函數(shù)會(huì)在 created 的時(shí)候執(zhí)行构挤,你會(huì)發(fā)現(xiàn)髓介,讓我們切換路由到 /1 時(shí),我們的頁(yè)面并未改變筋现,created 也并未重新觸發(fā)唐础。

這是因?yàn)?vue-router 會(huì)識(shí)別出這兩個(gè)路由使用的是同一個(gè)組件,然后會(huì)進(jìn)行復(fù)用矾飞,所以并不會(huì)重新創(chuàng)建組件一膨,那么 created 周期函數(shù)自然也不會(huì)觸發(fā)。

通常解決辦法就是添加 watcher 監(jiān)聽 $route 的變化凰慈,然后重新執(zhí)行 getName 函數(shù)汞幢。代碼如下:

watch: {
  $route: {
    immediate: true,
    handler: 'getName',
  },
},
methods: {
  getName() {
    const id = this.$route.params.id;
    // 模擬請(qǐng)求
    setTimeout(() => {
      if (id) {
        this.username = 'Yuga Sun';
      } else {
        this.username = 'yugasun';
      }
    }, 300);
  },
},

ok,問題是解決了微谓,但是有沒有其他不用改動(dòng) index.vue 的偷懶方式呢森篷?

就是給 router-view 添加一個(gè) key 屬性,這樣即使是相同組件豺型,但是如果 url 變化了仲智,Vuejs就會(huì)重新創(chuàng)建這個(gè)組件。我們直接修改 src/App.vue 中的 router-view 如下:

<router-view :key="$route.fullPath"></router-view>

被遺忘的 $attrs

大多數(shù)情況下姻氨,從父組件向子組件傳遞數(shù)據(jù)的時(shí)候钓辆,我們都是通過 props 實(shí)現(xiàn)的,比如下面這個(gè)例子:

<!-- 父組件中 -->
<Comp3
  :value="value"
  label="用戶名"
  id="username"
  placeholder="請(qǐng)輸入用戶名"
  @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>

這樣一階組件肴焊,實(shí)現(xiàn)起來很簡(jiǎn)單前联,也沒什么問題,我們只需要在子組件的 props 中寫一遍 id, value, placeholder... 這樣的屬性定義就可以了娶眷。但是如果子組件又包含了子組件似嗤,而且同樣需要傳遞 id, value, placeholder... 呢?甚至三階届宠、四階...呢烁落?那么就需要我們?cè)?props 中重復(fù)定義很多遍了,這怎么能忍呢豌注?

于是 vm.$attrs 可以閃亮登場(chǎng)了伤塌,先來看官方解釋:

包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的特性綁定 (class 和 style 除外)。當(dāng)一個(gè)組件沒有聲明任何 prop 時(shí)轧铁,這里會(huì)包含所有父作用域的綁定 (class 和 style 除外)每聪,并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件—— 在創(chuàng)建高級(jí)別的組件時(shí)非常有用

作者還特別強(qiáng)調(diào)了 在創(chuàng)建高級(jí)別的組件時(shí)非常有用,他就是為了解決剛才我提到的問題的熊痴。它也沒什么難度他爸,那么趕緊用起來吧,代碼修改如下:

<!-- 父組件中 -->
<Comp3
  :value="value"
  label="用戶名"
  id="username"
  placeholder="請(qǐng)輸入用戶名"
  @input="handleInput"
  >

<!-- 子組件中 -->
<template>
  <label>
    {{ $attrs.label }}
    <input
      v-bind="$attrs"
      @input="$emit('input', $event.target.value)"
    />
  </label>
</template>
<script>
export default {
}
</script>

這樣看起來是不是清爽多了果善,而且就算子組件中再次引用類似的子組件,我們也不怕了系谐。因?yàn)橛辛?$attrs巾陕,哪里不會(huì)點(diǎn)哪里......

總結(jié)

當(dāng)然 Vuejs 的實(shí)踐技巧遠(yuǎn)不止如此,這里只是總結(jié)了個(gè)人在實(shí)際開發(fā)中遇到的纪他,而且正好是很多朋友容易忽視的地方鄙煤。如果你有更好的實(shí)踐方法,歡迎評(píng)論或者發(fā)郵件給我茶袒,一起交流學(xué)習(xí)梯刚。

源碼在此

專題目錄

You-May-Not-Know-Vuejs

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市薪寓,隨后出現(xiàn)的幾起案子亡资,更是在濱河造成了極大的恐慌,老刑警劉巖向叉,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锥腻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡母谎,警方通過查閱死者的電腦和手機(jī)瘦黑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇唤,“玉大人幸斥,你說我怎么就攤上這事∫龋” “怎么了甲葬?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)冗栗。 經(jīng)常有香客問我演顾,道長(zhǎng),這世上最難降的妖魔是什么隅居? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任钠至,我火速辦了婚禮,結(jié)果婚禮上胎源,老公的妹妹穿的比我還像新娘棉钧。我一直安慰自己,他們只是感情好涕蚤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布宪卿。 她就那樣靜靜地躺著的诵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佑钾。 梳的紋絲不亂的頭發(fā)上西疤,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音休溶,去河邊找鬼代赁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兽掰,可吹牛的內(nèi)容都是我干的芭碍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼孽尽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼窖壕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杉女,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤瞻讽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宠纯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卸夕,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年婆瓜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了快集。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廉白,死狀恐怖个初,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猴蹂,我是刑警寧澤院溺,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站磅轻,受9級(jí)特大地震影響珍逸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聋溜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一谆膳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撮躁,春花似錦漱病、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漓穿。三九已至,卻和暖如春注盈,著一層夾襖步出監(jiān)牢的瞬間晃危,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工当凡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留山害,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓沿量,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冤荆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朴则,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載 :OpenDiggawesome-github-vue 是由OpenDigg整理并維護(hù)的Vue相關(guān)開源項(xiàng)目庫(kù)...
    果汁密碼閱讀 23,130評(píng)論 8 124
  • 我有你裸照 尺度之大之色情之香艷 簡(jiǎn)直是不忍直視 嘖嘖嘖 想不想看 自己說 吶 是不是,被我嚇到了钓简。╰(*′︶`*...
    少主子來訪閱讀 190評(píng)論 0 0
  • 他說太陽(yáng)像女人的乳房 不敢直視 怕被說成流氓 我說,流氓也得喝酸奶啊 他說太陽(yáng)像女人的肥臀 又大又圓 可望而不可及...
    孫小拽閱讀 158評(píng)論 0 0
  • 學(xué)生時(shí)代最煩的就是歷史损话,中國(guó)教育最失敗的也是歷史吧侦啸!學(xué)歷史的記憶就是死記硬背編年史,公元xx年發(fā)生了什么事丧枪,xx生...
    雷光祥閱讀 137評(píng)論 0 0
  • 小時(shí)候,“別人家的孩子”是你永遠(yuǎn)無法追趕的標(biāo)桿恋博,成績(jī)永遠(yuǎn)名列前茅齐佳,好像24小時(shí)只有吃飯、睡覺和學(xué)習(xí)债沮;也有一種孩子什...
    90后呆顏仁閱讀 769評(píng)論 4 2