Vue.js (二十)Vue選項(xiàng)之computed和watch

本文譯自Vue.js官方文檔

【約定:下文中的 “計(jì)算屬性” 即為 “computed屬性” 】

背景簡介

在模板內(nèi)使用表達(dá)式是非常方便的罕扎,但也只限于簡單的表達(dá)式而已,如果將過多的業(yè)務(wù)邏輯放在模板內(nèi)處理批幌,就會顯得臃腫而難以維護(hù)病曾。請看下列代碼:

<div id="example">
  {{ message.split( ' ' ).reverse().join( ' ' ) }}
</div>

由此可見這個模板不再那么簡潔和表述清晰了,你需要花費(fèi)好些時間才能看出它最終要顯示什么樣的結(jié)果嫩海。當(dāng)你在模板中多次使用這樣處理過的信息時結(jié)果將變得更糟捂刺。
這也就是為什么在碰到復(fù)雜邏輯運(yùn)算時我們應(yīng)該使用計(jì)算屬性的原因谣拣。

基本用法示例

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: ' Hello '
  },
  computed: {
    // a computed getter
    reversedMessage: function(){
    // `this` 指向vm實(shí)例
    return this.message.split( ' ' ).reverse().join( ' ' )
   }
  }
})

輸出結(jié)果:

Original message: "Hello"
Computed reversed message: "olleh"

上面的代碼中我們聲明了一個計(jì)算屬性reversedMessage,這里其實(shí)只是聲明了這個屬性的一個 getter 方法族展,關(guān)于它的 setter 方法我們在本文的后面有討論森缠。

console.log( vm.reversedMessage )  // => 'olleH'
vm.message = 'Goodbye'
console.log( vm.reversedMessage )  // => 'eybdooG'

我們可以在控制臺試著改變一下上例中的vm.message的值,你會發(fā)現(xiàn)輸出的vm.reversedMessage的值也會立刻跟著改變仪缸。
我們可以在模板中使用計(jì)算屬性來作數(shù)據(jù)綁定贵涵,跟使用一般的屬性一個樣,Vue知道這個計(jì)算屬性vm.reversedMessage依賴于數(shù)據(jù)屬性vm.message,所以當(dāng)vm.message的值變化時宾茂,vm.reversedMessage的返回值會重新計(jì)算(也就是它的 *getter 方法會被調(diào)用)瓷马,Vue將會更新所有的引用了vm.reversedMessage的數(shù)據(jù)。由此可見我們通過聲明了一個計(jì)算屬性的 getter 方法跨晴,使這個計(jì)算屬性與 getter 方法內(nèi)使用到的其它屬性產(chǎn)生了依賴關(guān)系欧聘,且這個方法不會產(chǎn)生副效應(yīng),使得我們更容易去測試和理解這個計(jì)算屬性的工作過程端盆。

計(jì)算屬性的緩存 vs 方法

我們可能會想到通過在模板中調(diào)用方法也能達(dá)到相同的效果:

<p>Reversed message: "{{ reverseMessage() }}"</p>
// 此處省略其它代碼
methods: {
  reverseMessage: function() {
    return this.message.split( ' ' ).reverse().join( ' ' )
  }
}

我們在methods內(nèi)定義了一個同樣的方法來替代計(jì)算屬性怀骤。其實(shí)使用這兩種方式所達(dá)到的最終結(jié)果完全一樣,唯一的區(qū)別是計(jì)算屬性可以基于它們的響應(yīng)式依賴(代表它所依賴的某些屬性或數(shù)據(jù)是響應(yīng)式的)對計(jì)算結(jié)果作緩存焕妙,只有它的響應(yīng)式依賴項(xiàng)message的值發(fā)生改變時蒋伦,這個計(jì)算屬性才會重新計(jì)算,也就是說只要message的值沒有發(fā)生改變焚鹊,所有引用reversedMessage計(jì)算屬性的地方將立刻返回先前的計(jì)算值凉敲,而不會再去調(diào)用那個 getter 方法重新計(jì)算值。這意味著下列代碼中的計(jì)算屬性now的值永遠(yuǎn)都不會更新寺旺,因?yàn)?code>Date.now()不是響應(yīng)式的:

computed: {
  now: function() {
    return Date.now()
   }
}

相比而言,通過在模板內(nèi)調(diào)用方法就不會緩存势决,只要頁面重新渲染阻塑, getter 方法就會被執(zhí)行。

我們?yōu)槭裁葱枰彺婺兀?/em>假設(shè)我們有一個開銷巨大的計(jì)算屬性A果复,A需要遍歷一個巨大的數(shù)組而且還要做大量計(jì)算陈莽,然后我們還有另外的一些計(jì)算屬性且它們依賴于A。如果沒有緩存的話虽抄,不管A的值有沒有發(fā)生改變走搁,A的值都要計(jì)算多次(A的 getter 被執(zhí)行多次);如果有緩存的話迈窟,在A沒有發(fā)生改變的情況下私植,那些依賴于A的計(jì)算屬性可以使用同一個A的緩存值。

計(jì)算屬性 vs watch

Vue明確的提供了一個通用的手段來發(fā)現(xiàn)數(shù)據(jù)的變化并為之做出響應(yīng)车酣,這就是使用watch選項(xiàng)曲稼。當(dāng)一些數(shù)據(jù)需要根據(jù)其它數(shù)據(jù)的變化做出相應(yīng)的改變時,使用watch確實(shí)是一種誘人的辦法湖员,尤其對于那些很熟悉AngularJS的同學(xué)更是如此贫悄。不過在這里我們還是更提倡使用計(jì)算屬性而不是watch,看看下面的例子就能區(qū)分它們的優(yōu)劣了:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function ( val ) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function ( val ) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面的代碼顯得很繁瑣娘摔,但又是不可避免的窄坦。再看看下列使用計(jì)算屬性實(shí)現(xiàn)的代碼:

var vm = new Vue( {
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
} )

顯而易見,使用計(jì)算屬性的代碼簡潔多了!
通過以上的了解鸭津,我們知道在大多數(shù)情況下使用計(jì)算屬性是更合適的彤侍,然而有個時候我們確實(shí)需要自定義watcher來滿足某些需求,這也是Vue提供了一個watch選項(xiàng)的原因曙博。假設(shè)有這樣一個需求:我們要對數(shù)據(jù)的改變做出異步響應(yīng)或做些消耗巨大的操作拥刻。請看下列代碼:

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<script src="https://cdn.jsdeliver.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
  var watchExampleVM = new Vue({
    el: '#watch-example',
    data: {
      question: ' ',
      answer: 'I cannot give you an answer until you ask a question!'
    },
    watch: {
      question: function ( newQuestion, oldQuestion ) {
        this.answer = 'Waiting for you to stop typing...'
        this.debouncedGetAnswer()
      }
    },
    created: function () {
      this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
    },
    methods: {
      getAnswer: function () {
        if (this.question.indexOf('?') === -1) {
          this.answer = 'Questions usually contain a question mark. ;-)'
          return
        }
        this.answer = 'Thinking...'
        var vm = this
        axios.get('https://yesno.wtf/api').then(function (response) {
          vm.answer = _.capitalize(response.data.answer)
        }).catch(function (error) {
            vm.answer = 'Error! Could not reach the API. ' + error
        })
      }
    }
  })
</script>

在上面的代碼中,我們在watch選項(xiàng)中執(zhí)行了一個異步操作(通過axios訪問api)父泳,并且限制了訪問這個api的頻次般哼,還可以在服務(wù)器返回最終結(jié)果之前顯示不同的提示信息。這些東西都是計(jì)算屬性實(shí)現(xiàn)不了的惠窄。除了在Vue實(shí)例的選項(xiàng)中使用watch蒸眠,還可以通過vm.$watch的方式來使用它。

計(jì)算屬性的 setter 方法

計(jì)算屬性默認(rèn)只提供 getter 方法杆融,必要的話我們可以提供對應(yīng)的 setter 方法楞卡,操作如下:

// 此處省略其它代碼
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function ( newValue ) {
      var names = newValue.split( ' ' )
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

現(xiàn)在我們可以運(yùn)行vm.fullname = ' John Doe '來測試一下,會發(fā)現(xiàn) setter 被調(diào)用了且vm.firstNamevm.lastName的值
被更新脾歇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒋腮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子藕各,更是在濱河造成了極大的恐慌池摧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件激况,死亡現(xiàn)場離奇詭異作彤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)乌逐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門竭讳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浙踢,你說我怎么就攤上這事绢慢。” “怎么了洛波?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵呐芥,是天一觀的道長。 經(jīng)常有香客問我奋岁,道長思瘟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任闻伶,我火速辦了婚禮滨攻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己光绕,他們只是感情好女嘲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诞帐,像睡著了一般欣尼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上停蕉,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天愕鼓,我揣著相機(jī)與錄音,去河邊找鬼慧起。 笑死菇晃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚓挤。 我是一名探鬼主播磺送,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼灿意!你這毒婦竟也來了估灿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缤剧,失蹤者是張志新(化名)和其女友劉穎馅袁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞭执,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年芒粹,在試婚紗的時候發(fā)現(xiàn)自己被綠了兄纺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡化漆,死狀恐怖估脆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情座云,我是刑警寧澤疙赠,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站朦拖,受9級特大地震影響圃阳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜璧帝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一捍岳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦锣夹、人聲如沸页徐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽变勇。三九已至,卻和暖如春贴唇,著一層夾襖步出監(jiān)牢的瞬間搀绣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工滤蝠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豌熄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓物咳,卻偏偏與公主長得像锣险,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子览闰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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