computed屬性和watcher


 

computed屬性

在模板中使用表達式是非常方便直接的,然而這只適用於簡單的操作。在模板中放入太多的邏輯,會使模板過度膨脹和難以維護。例如: 

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

在這個地方,模板不再簡潔和如聲明式直觀。你必須仔細觀察一段時間才能意識到,這里是想要顯示變量 message 的翻轉字符串。當你想要在模板中多次引用此處的翻轉字符串時,就會更加難以處理。

這就是為什么對於所有復雜邏輯,你都應該使用 computed 屬性(computed property)。

基礎實例  

<body>
<div id="example">
<p>初始message是:"{{message}}"</p>
<p>計算后的翻轉message是:"{{reversedMessage}}"</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 一個computed屬性的getter函數
reversedMessage: function () {
// 'this'執行vm實例
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>

這里我們聲明了一個 computed 屬性 reversedMessage。然后為 vm.reversedMessage 屬性提供一個函數,作為它的 getter 函數:

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

computed緩存vs method方法  

可以在表達式中通過調用 method 方法的方式,也能夠實現與 computed 屬性相同的結果:  

<body>
<div id="example">
    <p>翻轉message是:"{{reverseMessage()}}"</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    var vm = new Vue({
        el: '#example',
        data: {
            message: 'Hello'
        },
        methods: {
            reverseMessage: function () {
                return this.message.split('').reverse().join('')
            }
        }
    })
</script>
</body>

這里不使用 computed 屬性,而是在 methods 中定義一個相同的函數。對於最終結果,這兩種方式確實恰好相同。然而,細微的差異之處在於,computed 屬性會基於它所依賴的數據進行緩存。每個 computed 屬性,只有在它所依賴的數據發生變化時,才會重新取值(re-evaluate)。這就意味着,只要 message 沒有發生變化,多次訪問 computed 屬性 reversedMessage,將會立刻返回之前計算過的結果,而不必每次都重新執行函數。  

這也同樣意味着,如下的 computed 屬性永遠不會更新,因為 Date.now() 不是一個響應式的依賴數據: 

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

相比之下,每當觸發重新渲染(re-render)時,method 調用方式將總是再次執行函數。

為什么我們需要將依賴數據緩存起來?假設一種場景,我們有一個高性能開銷(expensive)的 computed 屬性 A,在 computed 屬性的 getter 函數內部,需要遍歷循環一個巨大數組,並進行大量計算。然后還有其他 computed 屬性直接或間接依賴於 A。如果沒有緩存,我們將不可避免地多次執行 A 的 getter 函數,這遠多余實際需要執行的次數!然而在某些場景下,你可能不希望有緩存,請使用 method 方法替代。

computed屬性和watch屬性  

Vue 其實還提供了一種更加通用的方式,來觀察和響應 Vue 實例上的數據變化:watch 屬性watch 屬性是很吸引人的使用方式,然而,當你有一些數據需要隨着另外一些數據變化時,過度濫用 watch 屬性會造成一些問題 - 尤其是那些具有 AngularJS 開發背景的開發人員。因此,更推薦的方式是,使用 computed 屬性,而不是命令式(imperative)的 watch 回調函數。思考下面這個示例: 

<body>
<div id="demo">{{fullName}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    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
            }
        }
    });
</script>
</body>

以上代碼是命令式和重復的。對比 computed 屬性實現的版本:  

<body>
<div id="demo">{{fullName}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    var vm = new Vue({
        el: '#demo',
        data: {
            firstName: 'Foo',
            lastName: 'Bar'
        },
        computed: {
            fullName: function () {
                return this.firstName + ' ' + this.lastName
            }
        }
    })
</script>
</body>

computed屬性中設置setter  

computed 屬性默認只設置 getter 函數,不過在需要時,還可以提供 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]
                }
            }
        }

現在當你運行 vm.fullName = 'John Doe',將會調用 setter,然后會對應更新 vm.firstName 和 vm.lastName。  

watcher 

雖然在大多數情況下,更適合使用 computed 屬性,然而有些時候,還是需要一個自定義 watcher。這就是為什么 Vue 要通過 watch 選項,來提供一個更加通用的響應數據變化的方式。當你需要在數據變化響應時,執行異步操作,或高性能消耗的操作,自定義 watcher 的方式就會很有幫助。 

<body>
<div id="watch-example">
    <p>
        問一個答案是yes/no的問題:
        <input v-model="question">
    </p>
    <p>{{answer}}</p>
</div>
<!-- 對於 ajax 庫(ajax libraries)和通用工具方法的集合(collections of general-purpose utility methods)來說, -->
<!-- 由於已經存在大量與其相關的生態系統, -->
<!-- 因此 Vue 核心無需重新創造,以保持輕量的文件體積。 -->
<!-- 同時這也可以使你自由隨意地選擇自己最熟悉的。 -->
<script src="https://cdn.jsdelivr.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 src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    var watchExampleVM = new Vue({
        el: '#watch-example',
        data: {
            question: '',
            answer: '你要先提出問題,我才能給你答案!'
        },
        watch: {
            // 只要question發生改變,此函數就會執行
            question: function (newQuestion, oldQuestion) {
                this.answer = '等待輸入停止...'
                this.getAnswer()
            }
        },
        methods: {
            // _.debounce 是由 lodash 提供的函數,
            // 在運行特別消耗性能的操作時,
            // 可以使用 _.debounce 來限制頻率。
            // 在下面這種場景中,我們需要限制訪問 yesno.wtf/api 的頻率,
            // 等到用戶輸入完畢之后,ajax 請求才會發出。
            // 想要了解更多關於 _.debounce 函數(以及與它類似的 _.throttle)的詳細信息,
            // 請訪問:https://lodash.com/docs#debounce
            getAnswer: _.debounce(
                function () {
                    if (this.question.indexOf('?') === -1) {
                        this.answer = '問題通常需要包含一個中文問號。;-)'
                        return
                    }
                    this.answer = '思考中...'
                    var vm = this
                    axios.get('https://yesno.wtf/api')
                        .then(function (response) {
                            vm.answer = _.capitalize(response.data.answer)
                        })
                        .catch(function (error) {
                            vm.answer = '錯誤!API無法處理。' + error
                        })
                },
                // 這是用戶停止輸入操作后所等待的毫秒數。
                // (譯者注:500毫秒之內,用戶繼續輸入,則重新計時)
                500
            )
        }
    })
</script>
</body>

在這個場景中,使用 watch 選項,可以使我們執行一個限制執行頻率的(訪問一個 API 的)異步操作,並且不斷地設置中間狀態,直到我們獲取到最終的 answer 數據之后,才真正執行異步操作。而 computed 屬性無法實現。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM