vue3中v-model的原理示例詳解
模擬 Vue 3 的完整 v-model 功能,我們需要實(shí)現(xiàn)以下特性:**
- 表單元素的雙向綁定:支持 、、 等表單元素,包括修飾符(如 .lazy、.number、.trim)。
- 自定義組件的雙向綁定:支持 v-model 在組件上的使用,基于 modelValue 和 update:modelValue,并支持多重 v-model(如 v-model:name)。
- 自定義修飾符:支持組件上的自定義修飾符(如 v-model.capitalize)。
- 響應(yīng)式系統(tǒng):實(shí)現(xiàn)一個(gè)簡(jiǎn)化的響應(yīng)式系統(tǒng),模擬 Vue 3 的 ref 或 Proxy 行為,確保數(shù)據(jù)變化觸發(fā)視圖更新。
原生 JavaScript 實(shí)現(xiàn),模擬 Vue 3 的 v-model 功能,包括表單元素和組件支持。我們將逐步構(gòu)建代碼,并提供使用示例。**
實(shí)現(xiàn)步驟
1. 響應(yīng)式系統(tǒng)(模擬 ref)
我們需要一個(gè)簡(jiǎn)單的響應(yīng)式系統(tǒng),類似于 Vue 3 的 ref,用于創(chuàng)建響應(yīng)式數(shù)據(jù)并在數(shù)據(jù)變化時(shí)通知依賴。
javascript
// 定義 Dep 對(duì)象,用于依賴收集 let Dep = { target: null // 當(dāng)前依賴的回調(diào)函數(shù) }; // 模擬 Vue 3 的 ref function ref(initialValue) { const deps = new Set(); let _value = initialValue; return { get value() { // 依賴收集 if (typeof Dep.target === 'function') { deps.add(Dep.target); } return _value; }, set value(newValue) { if (_value !== newValue) { _value = newValue; // 通知依賴 deps.forEach((dep) => dep()); } } }; } // 依賴收集的全局變量 Dep.target = null; // 模擬 watchEffect,用于依賴收集 function watchEffect(callback) { Dep.target = callback; callback(); Dep.target = null; }
- ref 創(chuàng)建響應(yīng)式數(shù)據(jù),getter 收集依賴,setter 通知依賴更新。
- watchEffect 模擬 Vue 的依賴收集機(jī)制,執(zhí)行回調(diào)并收集依賴。
2. 表單元素的 v-model
表單元素的 v-model 是 :value 和 @input(或 @change)的語(yǔ)法糖。我們需要:
- 綁定響應(yīng)式數(shù)據(jù)到元素的 value(或 checked)。
- 監(jiān)聽輸入事件,更新響應(yīng)式數(shù)據(jù)。
- 支持修飾符(如 .lazy、.number**、.trim)。**
javascript
// 處理修飾符邏輯 function applyModifiers(value, modifiers) { let result = value; if (modifiers.number) { result = isNaN(Number(result)) ? result : Number(result); } if (modifiers.trim && typeof result === 'string') { result = result.trim(); } return result; } // 表單元素的 v-model function vModelForm(element, reactiveRef, modifiers = {}) { const tag = element.tagName.toLowerCase(); const type = element.type; // 確定事件類型 const eventName = modifiers.lazy ? 'change' : tag === 'select' || type === 'checkbox' || type === 'radio' ? 'change' : 'input'; // 更新視圖 const updateView = () => { if (type === 'checkbox') { element.checked = reactiveRef.value; } else if (type === 'radio') { element.checked = reactiveRef.value === element.value; } else { element.value = reactiveRef.value; } }; // 初始綁定和依賴收集 Dep.target = updateView; updateView(); Dep.target = null; // 監(jiān)聽輸入事件 element.addEventListener(eventName, (event) => { let newValue; if (type === 'checkbox') { newValue = event.target.checked; } else if (type === 'radio') { if (event.target.checked) { newValue = event.target.value; } else { return; // 未選中時(shí)不更新 } } else { newValue = event.target.value; } // 應(yīng)用修飾符 newValue = applyModifiers(newValue, modifiers); reactiveRef.value = newValue; }); // 數(shù)據(jù)變化時(shí)更新視圖 reactiveRef.deps = reactiveRef.deps || new Set(); reactiveRef.deps.add(updateView); }
- 修飾符處理:applyModifiers 處理 .number 和 .trim 修飾符。
- 事件選擇:根據(jù)元素類型(checkbox、radio、select)或修飾符(.lazy)選擇 input 或 change 事件。
- 視圖更新:根據(jù)元素類型更新 value 或 checked。
- 事件監(jiān)聽:根據(jù)元素類型獲取用戶輸入的值,并應(yīng)用修飾符后更新響應(yīng)式數(shù)據(jù)。
3. 自定義組件的 v-model
組件的 v-model 基于 modelValue 和 update:modelValue,支持多重 v-model 和自定義修飾符。我們模擬組件為一個(gè)簡(jiǎn)單的 DOM 結(jié)構(gòu),包含子組件邏輯。
javascript
// 模擬組件的 v-model function vModelComponent(element, reactiveRef, propName = 'modelValue', modifiers = {}) { // 模擬組件的 props 和 emit const props = { [propName]: reactiveRef.value }; const emit = (eventName, value) => { if (eventName === `update:${propName}`) { // 應(yīng)用修飾符 let newValue = value; if (modifiers.capitalize && typeof newValue === 'string') { newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1); } reactiveRef.value = newValue; } }; // 更新視圖 const updateView = () => { element.value = reactiveRef.value; // 模擬組件內(nèi)部 input 的 value }; // 初始綁定 Dep.target = updateView; updateView(); Dep.target = null; // 模擬組件內(nèi)部的 input 事件 element.addEventListener('input', (event) => { emit(`update:${propName}`, event.target.value); }); // 數(shù)據(jù)變化時(shí)更新視圖 reactiveRef.deps = reactiveRef.deps || new Set(); reactiveRef.deps.add(updateView); }
- props 和 emit:模擬組件的 props(如 modelValue)和 $emit(如 update:modelValue)。
- 多重 v-model:通過(guò) propName 支持自定義屬性(如 name、age)。
- 修飾符:支持自定義修飾符(如 .capitalize),在 emit 時(shí)處理。
- 視圖更新:模擬組件內(nèi)部的 ,綁定 value 并監(jiān)聽 input 事件。
4. 統(tǒng)一的 v-model 接口
創(chuàng)建一個(gè)統(tǒng)一的 vModel 函數(shù),根據(jù)上下文(表單元素或組件)調(diào)用不同的實(shí)現(xiàn)。
javascript
function vModel(element, reactiveRef, options = {}) { const { modifiers = {}, propName } = options; const isComponent = propName || element.tagName.toLowerCase() === 'custom-component'; if (isComponent) { vModelComponent(element, reactiveRef, propName || 'modelValue', modifiers); } else { vModelForm(element, reactiveRef, modifiers); } }
- 選項(xiàng):modifiers 指定修飾符,propName 指定組件的綁定屬性。
- 上下文判斷:根據(jù) propName 或元素標(biāo)簽判斷是表單元素還是組件。
5. 完整代碼與使用示例
javascript
// 定義 Dep 對(duì)象,用于依賴收集 const Dep = { target: null, // 當(dāng)前依賴的回調(diào)函數(shù) }; // 模擬 Vue 3 的 ref function ref(initialValue) { const deps = new Set(); let _value = initialValue; return { get value() { // 依賴收集 if (typeof Dep.target === 'function') { deps.add(Dep.target); } return _value; }, set value(newValue) { if (_value !== newValue) { _value = newValue; // 通知依賴 deps.forEach((dep) => dep()); } }, }; } // 模擬 watchEffect,用于依賴收集 function watchEffect(callback) { Dep.target = callback; callback(); Dep.target = null; } // 處理修飾符邏輯 function applyModifiers(value, modifiers) { let result = value; if (modifiers.number) { result = isNaN(Number(result)) ? result : Number(result); } if (modifiers.trim && typeof result === 'string') { result = result.trim(); } return result; } // 表單元素的 v-model function vModelForm(element, reactiveRef, modifiers = {}) { const tag = element.tagName.toLowerCase(); const type = element.type; const eventName = modifiers.lazy ? 'change' : tag === 'select' || type === 'checkbox' || type === 'radio' ? 'change' : 'input'; const updateView = () => { if (type === 'checkbox') { element.checked = reactiveRef.value; } else if (type === 'radio') { element.checked = reactiveRef.value === element.value; } else { element.value = reactiveRef.value; } }; Dep.target = updateView; updateView(); Dep.target = null; element.addEventListener(eventName, (event) => { let newValue; if (type === 'checkbox') { newValue = event.target.checked; } else if (type === 'radio') { if (event.target.checked) { newValue = event.target.value; } else { return; } } else { newValue = event.target.value; } newValue = applyModifiers(newValue, modifiers); reactiveRef.value = newValue; }); reactiveRef.deps = reactiveRef.deps || new Set(); reactiveRef.deps.add(updateView); } // 組件的 v-model function vModelComponent( element, reactiveRef, propName = 'modelValue', modifiers = {}, ) { const props = { [propName]: reactiveRef.value }; const emit = (eventName, value) => { if (eventName === `update:${propName}`) { let newValue = value; if (modifiers.capitalize && typeof newValue === 'string') { newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1); } reactiveRef.value = newValue; } }; const updateView = () => { element.value = reactiveRef.value; }; Dep.target = updateView; updateView(); Dep.target = null; element.addEventListener('input', (event) => { emit(`update:${propName}`, event.target.value); }); reactiveRef.deps = reactiveRef.deps || new Set(); reactiveRef.deps.add(updateView); } // 統(tǒng)一 v-model 接口 function vModel(element, reactiveRef, options = {}) { const { modifiers = {}, propName } = options; const isComponent = propName || element.tagName.toLowerCase() === 'custom-component'; if (isComponent) { vModelComponent(element, reactiveRef, propName || 'modelValue', modifiers); } else { vModelForm(element, reactiveRef, modifiers); } } // 使用示例 // 創(chuàng)建 DOM 元素 const inputText = document.createElement('input'); inputText.placeholder = '輸入文本'; const inputNumber = document.createElement('input'); inputNumber.type = 'number'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; const select = document.createElement('select'); select.innerHTML = ` <option value="option1">選項(xiàng) 1</option> <option value="option2">選項(xiàng) 2</option> `; const customComponent = document.createElement('input'); // 模擬組件 customComponent.placeholder = '自定義組件輸入'; // 添加到頁(yè)面 document.body.appendChild(inputText); document.body.appendChild(document.createElement('br')); document.body.appendChild(inputNumber); document.body.appendChild(document.createElement('br')); document.body.appendChild(checkbox); document.body.appendChild(document.createElement('br')); document.body.appendChild(select); document.body.appendChild(document.createElement('br')); document.body.appendChild(customComponent); // 創(chuàng)建響應(yīng)式數(shù)據(jù) const message = ref('Hello'); const number = ref(42); const checked = ref(false); const selected = ref('option1'); const componentName = ref('alice'); const componentAge = ref(25); // 綁定 v-model vModel(inputText, message, { modifiers: { trim: true } }); vModel(inputNumber, number, { modifiers: { number: true } }); vModel(checkbox, checked); vModel(select, selected); vModel(customComponent, componentName, { propName: 'name', modifiers: { capitalize: true }, }); // 模擬多重 v-model const componentAgeInput = document.createElement('input'); componentAgeInput.type = 'number'; document.body.appendChild(document.createElement('br')); document.body.appendChild(componentAgeInput); vModel(componentAgeInput, componentAge, { propName: 'age', modifiers: { number: true }, }); // 打印數(shù)據(jù)變化 watchEffect(() => { console.log('message:', message.value); console.log('number:', number.value); console.log('checked:', checked.value); console.log('selected:', selected.value); console.log('componentName:', componentName.value); console.log('componentAge:', componentAge.value); }); // 程序化更新數(shù)據(jù) setTimeout(() => { message.value = 'Updated Text'; componentName.value = 'bob'; }, 2000);
執(zhí)行流程圖
初始化: 定義 Dep, ref, vModel 等 創(chuàng)建 DOM 元素 -> 添加到頁(yè)面 創(chuàng)建 ref 數(shù)據(jù) (message, number, ...) vModel 綁定: -> vModelForm 或 vModelComponent -> 確定事件 (input/change) -> 定義 updateView -> 初始更新 DOM (觸發(fā) getter, 收集 updateView) -> 綁定事件監(jiān)聽 -> 添加 updateView 到 deps watchEffect: -> 打印初始值 (觸發(fā) getter, 收集 watchEffect) 設(shè)置定時(shí)器 用戶交互 (例如輸入 " Hello Vue "): input 事件 -> 獲取值 " Hello Vue " applyModifiers (.trim) -> "Hello Vue" message.value = "Hello Vue" -> setter -> 更新 _value -> 通知 deps: -> updateView: inputText.value = "Hello Vue" -> watchEffect: 打印所有數(shù)據(jù) 程序化更新 (2秒后): message.value = 'Updated Text' -> setter -> 更新 _value -> 通知 deps: -> updateView: inputText.value = 'Updated Text' -> watchEffect: 打印數(shù)據(jù) componentName.value = 'bob' -> setter -> 更新 _value -> 通知 deps: -> updateView: customComponent.value = 'bob' -> watchEffect: 打印數(shù)據(jù)
到此這篇關(guān)于vue3中v-model的原理示例的文章就介紹到這了,更多相關(guān)vue v-model原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue 2 實(shí)現(xiàn)自定義組件一到多個(gè)v-model雙向數(shù)據(jù)綁定的方法(最新推薦)
- vue3?中v-model語(yǔ)法糖示例詳解
- vue3的組件通信&v-model使用實(shí)例詳解
- Vue3.4中v-model雙向數(shù)據(jù)綁定新玩法詳解
- vue3利用v-model實(shí)現(xiàn)父子組件之間數(shù)據(jù)同步的代碼詳解
- Vue3中v-model語(yǔ)法糖的三種寫法詳解
- vue3中使用v-model實(shí)現(xiàn)父子組件數(shù)據(jù)同步的三種方案
- 在Vue2中v-model和.sync區(qū)別解析
相關(guān)文章
詳解vue-cli快速構(gòu)建項(xiàng)目以及引入bootstrap、jq
本篇文章主要介紹了vue-cli快速構(gòu)建項(xiàng)目以及引入bootstrap、jq,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05關(guān)于element-ui中el-form自定義驗(yàn)證(調(diào)用后端接口)
這篇文章主要介紹了關(guān)于element-ui中el-form自定義驗(yàn)證(調(diào)用后端接口),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Vuex中actions優(yōu)雅處理接口請(qǐng)求的方法
在項(xiàng)目開發(fā)中,如果使用到了 vuex,通常我會(huì)將所有的接口請(qǐng)求單獨(dú)用一個(gè)文件管理,這篇文章主要介紹了Vuex中actions如何優(yōu)雅處理接口請(qǐng)求,業(yè)務(wù)邏輯寫在 actions 中,本文給大家分享完整流程需要的朋友可以參考下2022-11-11vue將時(shí)間戳轉(zhuǎn)換成自定義時(shí)間格式的方法
下面小編就為大家分享一篇vue將時(shí)間戳轉(zhuǎn)換成自定義時(shí)間格式的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03vue element-ui之怎么封裝一個(gè)自己的組件的詳解
這篇文章主要介紹了vue element-ui之怎么封裝一個(gè)自己的組件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05在Vue+Ts+Vite項(xiàng)目中配置別名指向不同的目錄并引用的案例詳解
這篇文章主要介紹了在Vue+Ts+Vite項(xiàng)目中配置別名指向不同的目錄并引用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Vue 組件組織結(jié)構(gòu)及組件注冊(cè)詳情
這篇文章主要介紹的是Vue 組件組織結(jié)構(gòu)及組件注冊(cè),為了能在模板中使用,這些組件必須先注冊(cè)以便 Vue 能夠識(shí)別。這里有兩種組件的注冊(cè)類型:全局注冊(cè)和局部注冊(cè)。至此,我們的組件都只是通過(guò) Vue.component 全局注冊(cè)的,文章學(xué)詳細(xì)內(nèi)容,需要的朋友可以參考一下2021-10-10