Vue響應式原理及雙向數據綁定示例分析
前言
之前公司招人,面試瞭一些的前端同學,因為公司使用的前端技術是Vue
,所以免不瞭問到其響應式原理和Vue
的雙向數據綁定。但是這邊面試到的80%的同學會把兩者搞混,通常我要是先問響應式原理再問雙向數據綁定原理,來面試的同學大都會認為是一回事,那麼這裡我們就說一下二者的區別。
響應式原理
是Vue的核心特性之一,數據驅動視圖,我們修改數據視圖隨之響應更新,就很優雅~
Vue2.x
是借助Object.defineProperty()
實現的,而Vue3.x
是借助Proxy
實現的,下面我們先來看一下2.x的實現。
Object.defineProperty(obj, key, { enumerable: true, configurable: true, //攔截get,當我們訪問data.key時會被這個方法攔截到 get: function getter () { //我們在這裡收集依賴 return obj[key]; }, //攔截set,當我們為data.key賦值時會被這個方法攔截到 set: function setter (newVal) { //當數據變更時,通知依賴項變更UI } })
我們通過Object.defineProperty
為對象obj
添加屬性,可以設置對象屬性的getter
和setter
函數。之後我們每次通過點語法獲取屬性都會執行這裡的getter
函數,在這個函數中我們會把調用此屬性的依賴收集到一個集合中 ;而在我們給屬性賦值(修改屬性)時,會觸發這裡定義的setter
函數,在次函數中會去通知集合中的依賴更新,做到數據變更驅動視圖變更。
3.x的與2.x的核心思想一致,隻不過數據的劫持使用Proxy
而不是Object.defineProperty
,隻不過Proxy相比Object.defineProperty在處理數組和新增屬性的響應式處理上更加方便。
let nObj=new Proxy(obj,{ //攔截get,當我們訪問nObj.key時會被這個方法攔截到 get: function (target, propKey, receiver) { console.log(`getting ${propKey}!`); return Reflect.get(target, propKey, receiver); }, //攔截set,當我們為nObj.key賦值時會被這個方法攔截到 set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}!`); return Reflect.set(target, propKey, value, receiver); } })
Proxy
的詳細使用方法參考ES6教程。
Vue
的響應式原理的實現細節相信大多數同學已經很熟悉瞭,這裡就不在展開細談瞭,如果還想更詳細的瞭解,或者想要做一個簡易的Vue
實現,可以參考這篇Vue原理,相信你會有不小收獲。
雙向數據綁定
雙向數據綁定通常是指我們使用的v-model
指令的實現,是Vue
的一個特性,也可以說是一個input
事件和value
的語法糖。 Vue
通過v-model
指令為組件添加上input
事件處理和value
屬性的賦值。
<template> <input v-model='localValue'/> </template>
上述的組件就相當於如下代碼
<template> <!-- 這裡添加瞭input時間的監聽和value的屬性綁定 --> <input @input='onInput' :value='localValue' /> <span>{{localValue}}</span> </template> <script> export default{ data(){ return { localValue:'', } }, methods:{ onInput(v){ //在input事件的處理函數中更新value的綁定值 this.localValue=v.target.value; console.log(this.localValue) } } } </script>
<template> <div> <input @input='onInput' :value='localValue' /> <span>{{localValue}}</span> </div> </template> <script> // import Vue from 'vue'; export default{ data(){ return { localValue:'hello', } }, methods:{ onInput(v){ this.localValue=v.target.value; console.log(this.localValue) } } } </script> <style> .count { color: red; } </style>
因此當我們修改input輸入框中的值時,我們通過v-model綁定的值也會同步修改,基於上述原理,我們可以很容易的實現一個數據雙向綁定的組件。
v-model實踐
首先我們定義一個Vue
組件,相信大傢已經很熟悉瞭。
<tempalte> <div class="count" @click="addCount">click me {{value}}</div> </template> <script> export default{ props:{ //關鍵的第一步:設置一個value屬性 value:{ type:Number, default:0 } }, watch:{ //監聽value變化,更新組件localvalue狀態 value(v){ this.localvalue=v; } }, methods:{ //關鍵的第二步:事件觸發localvalue變更,通過事件同步父組件狀態變更 addCount(){ this.localvalue++; this.$emit('input',this.localvalue); } }, data(){ return{ //組件狀態,遵守單項數據流原則,不直接修改props中的屬性 localvalue:0 } }, created(){ //初始化獲取value值 this.localvalue=this.value; } } </script>
上面的組件定瞭我們通過在props
中添加value
屬性,並且在值更新時觸發input
事件。created
鉤子和watch
中為localvalue
賦值是為瞭同步父組件狀態到子組件中。
通過上面👆的組件定義,我們就可以在組件上使用v-model
指令做雙向數據綁定瞭。
<template> <add-one v-model="count"></add-one> <span>父組件{{count}}</span> </tempalte> <script> export default{ data() { return { count: 0, }; }, methods: { }, created(){ } } </script>
下面是實際效果
import Vue from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js' Vue.directive('mymodel', { bind(el, binding, vnode, oldVnode) { //組件和原生input標簽需要分別處理, el.value = binding.value; if (vnode.tag == 'input') { //監聽綁定的變量 vnode.context.$watch(binding.expression, (v) => { el.value = v; }) //添加inout事件監聽 el.addEventListener('input', (e) => { //context是input所在的父組件,這一步是同步數據 vnode.context[binding.expression] = e.target.value; }) } else { //組件 //vnode的結構可以參見文檔。不過我覺得最直觀的方法就是直接在控制臺打印處理 let { componentInstance, componentOptions, context } = vnode; const { _props } = componentInstance; //處理model選項 if (!componentOptions.Ctor.extendOptions.model) { componentOptions.Ctor.extendOptions.model = { value: 'value', event: 'input' } } let modelValue = componentOptions.Ctor.extendOptions.model.value; let modelEvent = componentOptions.Ctor.extendOptions.model.event; //屬性綁定,這裡直接修改瞭屬性,沒有想到更好的辦法,友好的意見希望可以提出 console.log(binding) _props[modelValue] = binding.value; context.$watch(binding.expression, (v) => { _props[modelValue] = v; }) //添加事件處理函數,做數據同步 componentInstance.$on(modelEvent, (v) => { context[binding.expression] = v; }) } }, inserted() {}, update() {}, componentUpdated() {}, unbind() {}, }) Vue.component('add-one', { template: '<div class="count" @click="addCount">click me {{localvalue}}</div>', props: { value: { type: Number | String, default: -1 } }, //自定義value和事件 // model: { // value: 'count', // event: 'change' // }, watch: { //監聽value變化,更新組件localvalue狀態 value(v) { this.localvalue = v; } }, methods: { //事件觸發localvalue變更,通過事件同步父組件狀態變更 addCount() { this.localvalue++; this.$emit('input', this.localvalue); } }, data() { return { localvalue: 0 } }, created() { //初始化獲取value值 console.log(this.value) this.localvalue = this.value; } }) new Vue({ el: '#app', data() { return { count: 0, }; }, methods: {}, created() { } })
當然我們也可以不使用value
和input
事件這樣的組合,為瞭更使得組件的定義更加符合語義,我們也可以自定義要實現雙向綁定的屬性和事件。 我們在組件的model
選項中設置value
和event
即可。如下:
export default{ //這裡做瞭一個value和event的映射 model:{ value:'count', event:'change' }, props:{ //關鍵的第一步:設置一個value屬性 count:{ type:Number, default:0 } }, methods:{ //關鍵的第二步:事件觸發localvalue變更,通過事件同步父組件狀態變更 addCount(){ this.localvalue++; this.$emit('change',this.localvalue); } }, }
通過上面的組件定義
<add-one v-model="count"></add-one>
就相當於
<template> <add-one @change='onChange' :count='count'></add-one> <span>{{count}}</span> </template> <script> export default{ data(){ return { count:0, } }, methods:{ onChange(v){ this.count=v; console.log(this.count) } } } </script>
隻不過v-model
指令幫我們做上面的事件添加,屬性綁定和狀態同步操作罷瞭。
以上就是Vue響應式原理及雙向數據綁定示例分析的詳細內容,更多關於Vue響應式雙向數據綁定的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- vue在自定義組件上使用v-model和.sync的方法實例
- vue自定義組件如何通過v-model指令控制組件的隱藏、顯示
- vue 自定義指令directives及其常用鉤子函數說明
- vue3.0-props、computed、自定義事件方式
- vue使用v-model進行跨組件綁定的基本實現方法