Vue利用computed解決單項(xiàng)數(shù)據(jù)流的問題
Vue 是一個(gè)非常流行和強(qiáng)大的前端框架,它讓我們可以用簡潔和優(yōu)雅的方式來構(gòu)建用戶界面。
但是,Vue 也有一些需要注意和掌握的細(xì)節(jié)和技巧。
今天我們來分享一個(gè) Vue 中非常經(jīng)典的問題,也是一個(gè)非常實(shí)用的技巧。
這個(gè)問題涉及到 Vue 的一個(gè)核心特性:單向數(shù)據(jù)流。
讓人頭痛的單向數(shù)據(jù)流
如果你不了解單向數(shù)據(jù)流是什么,或者你不知道如何在 Vue 中正確地使用它,那么請繼續(xù)往下看,我保證你會(huì)有所收獲。
這個(gè)問題在你使用 Vue 去封裝一個(gè)表單組件的時(shí)候,就會(huì)非常明顯地體現(xiàn)出來。
表單組件是前端開發(fā)中非常常見和重要的一種組件,它可以讓用戶輸入和提交數(shù)據(jù),從而實(shí)現(xiàn)各種功能。
比如說,我們這里我們封裝了一個(gè)搜索條的簡單示例,來以小見大:

這個(gè)組件很簡單:
<template>
<el-input v-model="modelValue.keyword" :placeholder="modelValue.placeholder">
<template #prepend>
<el-select v-model="modelValue.selectedValue" placeholder="Select" style="width: 85px">
<el-option v-for="item in modelValue.options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</el-input>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue';
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
</script>通過 props 傳入一個(gè)對(duì)象,這個(gè)對(duì)象里包括所以我們需要的數(shù)據(jù),比如:占位符 placeholder、文本框的輸入值 keyword、下拉框的選中值 selectedValue、下拉框的選項(xiàng)值 options。
這些數(shù)據(jù)都給我們,我們將這個(gè)界面渲染出來。
那么父組件是這樣的:
<template>
<div>
<SearchBar v-model="searchData" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import SearchBar from './components/SearchBar.vue';
const searchData = ref({
keyword: '',
placeholder: '請輸入你要查詢的關(guān)鍵字',
options: [
{ label: '視頻', value: 'video' },
{ label: '文章', value: 'article' },
{ label: '用戶', value: 'user' },
],
selectedValue: 'video',
});
</script>父組件在使用時(shí)自然而然會(huì)傳遞一些數(shù)據(jù),這個(gè)數(shù)據(jù)也很簡單,一看就明白了。
我們使用 v-model 來綁定數(shù)據(jù),這樣只要這個(gè)組件改動(dòng)了這個(gè)數(shù)據(jù),那么我們父組件就能收到通知,能夠?qū)@個(gè)數(shù)據(jù)做相應(yīng)的變化。
這個(gè)組件結(jié)構(gòu)是非常清晰的,就是這么一種結(jié)構(gòu):

這都是基礎(chǔ)知識(shí),沒什么好說的,但是實(shí)際情況是什么樣的呢?
現(xiàn)在的問題是子組件的文本框使用的是 v-model 綁定數(shù)據(jù),但是這一綁定就把父組件傳遞的屬性它里邊的數(shù)據(jù)綁定進(jìn)去了,那現(xiàn)在就變成了這種結(jié)構(gòu)了:

于是這種情況就打破了單項(xiàng)數(shù)據(jù)流,打破單向數(shù)據(jù)流是要付出代價(jià)的,打破一次你的工程就距離“ shǐ山”更進(jìn)一步。
那么我們希望不要打破單項(xiàng)數(shù)據(jù)流,回歸到正常的模式,那該怎么做呢?
解決辦法
最笨的辦法就是在子組件里不使用 v-model ,不然的話文本框一變這個(gè)父組件的數(shù)據(jù)就會(huì)跟著變,所以我們把 v-model 拆成原始的形式。
<template>
<!-- 將 v-model 拆分 -->
<el-input
:modelValue="modelValue.keyword"
@update:modelValue="handleKeywordChange"
:placeholder="modelValue.placeholder">
<!-- etc... -->
</el-input>
</template>
<script setup>
// etc...
// 定義一個(gè) emit 事件
const emit = defineEmits(['update:modelValue'])
function handleKeywordChange(val) {
console.log('val >>> ', val)
// 觸發(fā)子組件的 update:modelValue 事件
emit('update:modelValue', {
// 因?yàn)檫@里我們只是修改了 keyword 的值
// 所以我們將 props.modelValue 展開之后,單獨(dú)將 keyword 的值賦值為新的值
...props.modelValue,
keyword: val
})
}
</script>一個(gè)是 modelValue 用于綁定值。
另外一個(gè)是 update:modelValue 用于監(jiān)控這個(gè)組件的 update 事件,當(dāng)事件觸發(fā)的時(shí)候調(diào)用 handleKeywordChange 函數(shù)。
handleKeywordChange 函數(shù),要做的事情就是去觸發(fā)子組件 update:modelValue 事件,通知父組件去更改數(shù)據(jù),所以我們定義了一個(gè) emit 事件,數(shù)據(jù)變化的時(shí)候調(diào)用 emit 返回更新的數(shù)據(jù)。

雖然說這樣很麻煩,但是我們保證了單項(xiàng)數(shù)據(jù)流了。
那么有沒有一種簡介的方法呢?
其實(shí)面對(duì)這個(gè)問題,Vue 官方也好還是一些第三方庫,比如:vueuse 他們都有一種解決辦法,就是使用計(jì)算屬性去給它包一層:

父組件的數(shù)據(jù)傳遞過來之后,并沒有直接綁定到內(nèi)部的文本框,而是在中間加了一個(gè)計(jì)算屬性,然后用這個(gè)計(jì)算屬性去綁定這個(gè)文本框,這個(gè)計(jì)算屬性要同時(shí)設(shè)置它的 getter 和 setter,當(dāng)讀這個(gè)計(jì)算屬性的時(shí)候,讀的其實(shí)就是 modelValue 里的東西,所以讀是沒問題的。
但是這個(gè)文本框由于綁定了 v-model 這個(gè)文本框會(huì)變動(dòng)的,變動(dòng)的話改的就是這個(gè)計(jì)算屬性,也就觸發(fā)了這個(gè)計(jì)算屬性的 setter,那么在 setter 里邊我們就可以寫代碼去觸發(fā)這個(gè) emit 事件。
這樣就簡化了代碼,同時(shí)又保證了單項(xiàng)數(shù)據(jù)流,官方就是這樣建議的,我們?nèi)L試一下好不好用:
<template>
<!-- 將計(jì)算屬性綁定到文本框之上 -->
<el-input v-model="keyword" :placeholder="modelValue.placeholder">
<!-- etc... -->
</el-input>
</template>
<script setup>
// etc...
// 定義一個(gè) emit 事件
const emit = defineEmits(['update:modelValue'])
// 寫一個(gè)計(jì)算屬性,同時(shí)提供 get 和 set
const keyword = computed({
// 讀取的時(shí)候直接返回讀取的值
get() {
return props.modelValue.keyword;
},
// 當(dāng)修改的時(shí)候我們執(zhí)行 emit 的操作
set(val) {
console.log('val >>> ', val)
emit('update:modelValue', {
...props.modelValue,
keyword: val
})
}
})
</script>
這樣我們就不需要拆分 v-model 了,雖然有所簡化,但是簡化的并不多,因?yàn)橄吕虻倪x中值 selectedValue、下拉框的選項(xiàng)值 options 都要做成計(jì)算屬性。
那么我們能不能想一個(gè)辦法,就是說這個(gè)計(jì)算屬性不要只返回給我們一個(gè)字段,而是字節(jié)把整個(gè)對(duì)象返回,像這種模式:
<template>
<!-- 綁定的時(shí)候直接綁定計(jì)算屬性上的字段 -->
<el-input v-model="model.keyword" :placeholder="model.placeholder">
<template #prepend>
<el-select v-model="model.selectedValue" placeholder="Select" style="width: 85px">
<el-option v-for="item in model.options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</el-input>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue';
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
const emit = defineEmits(['update:modelValue'])
const model = computed({
get() {
return props.modelValue;
},
set(val) {
emit('update:modelValue', val)
}
})
</script>將來修改計(jì)算屬性的時(shí)候就觸發(fā)事件,綁定值得到話就綁定計(jì)算屬性的字段。
這樣就能通過一個(gè)計(jì)算屬性屬性,搞定全部的問題了。
但是現(xiàn)在修改是無效的,因?yàn)榻壎ǖ氖?model 里的一個(gè)字段,并不是 model,所以修改的也是 model 的字段,所以并不會(huì)觸發(fā) set 的更新:

因?yàn)橹挥懈膭?dòng)了 model 本身的時(shí)候,它才會(huì)去運(yùn)行 setter,改動(dòng)的是某一個(gè)字段就不會(huì)運(yùn)行 setter,那現(xiàn)在就不好辦了。
但是,轉(zhuǎn)折來了,有一個(gè)奇招可以解決這個(gè)問題:
const model = computed({
get() {
// 我們這里返回一個(gè)代理對(duì)象,代理 props.modelValue 這個(gè)屬性
return new Proxy(props.modelValue, {
// 因?yàn)檫@是一個(gè)代理對(duì)象,那么將來修改代理對(duì)象的某個(gè)值時(shí)
// 就會(huì)運(yùn)行這個(gè) set 函數(shù)
// 函數(shù)中可以拿到
// obj:改動(dòng)的對(duì)象
// name:改動(dòng)的屬性名
// val:改動(dòng)的屬性值
set(obj, name, val) {
console.log('Emit >>> ', name, val)
// 當(dāng)我們想改的一個(gè)對(duì)象的屬性時(shí)并不去真正的修改
// 而是在這里也觸發(fā) emit,然后生成一個(gè)新的對(duì)象
emit('update:modelValue', {
...obj, // 展開以前對(duì)象的值
[name]: val // 將其中的修改的屬性修改為新的值
})
return true; // 最后返回一個(gè) true
},
});
},
set(val) {
emit('update:modelValue', val)
}
})
這就正常的觸發(fā)了事件函數(shù),那么這樣一來代碼就進(jìn)一步得到簡化了。
我們使用一個(gè)計(jì)算屬性屬性就可以替代里邊的所有字段,特別是在一個(gè)大表單里,有很多很多的字段,這一招非常的好用。
在子組件里無論有多少個(gè)文本框選項(xiàng),都去用這個(gè)計(jì)算屬性去綁定就可以了。
既不會(huì)打破單向數(shù)據(jù)流,而且實(shí)現(xiàn)代碼也非常少。
擴(kuò)展
其實(shí)我們還可以把這個(gè)問題擴(kuò)展一下,因?yàn)槲覀冊趯?shí)際開發(fā)中,封裝表單是一件常事,所以在每一次封裝表單的都是都去寫一次這樣的代碼有點(diǎn)繁瑣,我們可以把它提出去,寫成一個(gè)輔助函數(shù)。
import { computed } from 'vue';
/**
* props:屬性對(duì)象
* propName:要做成計(jì)算屬性的名字
* emit:emit 函數(shù)
*/
export function useVModel(props, propName, emit) {
return computed({
get() {
return new Proxy(props[propName], {
set(obj, name, val) {
console.log('emit', name, val);
emit('update:' + propName, {
...obj,
[name]: val,
});
return true;
},
});
},
set(val) {
emit('update:' + propName, val);
},
});
}這樣就可以通過一個(gè)輔助函數(shù)幫我們把要做的事情實(shí)現(xiàn),使用起來就非常的舒服了:
<template>
<el-input v-model="model.keyword" :placeholder="model.placeholder">
<template #prepend>
<el-select v-model="model.selectedValue" placeholder="Select" style="width: 85px">
<el-option v-for="item in model.options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</el-input>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue';
import { useVModel } from './useVModel'; // 導(dǎo)入輔助函數(shù)
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
const emit = defineEmits(['update:modelValue']);
// 調(diào)用函數(shù)將需要的參數(shù)傳遞進(jìn)去
const model = useVModel(props, 'modelValue', emit);
</script>
以后無論是非常簡單的表單封裝,還是非常龐大的表單封裝,都可以用這么幾行代碼來解決問題了。
既保護(hù)了單項(xiàng)數(shù)據(jù)流,又簡化了代碼的書寫,在實(shí)際開發(fā)中用起來是非常的好用。
總結(jié)
通過這篇文章,你應(yīng)該對(duì) Vue 的單向數(shù)據(jù)流有了更深入的理解和掌握。
你學(xué)習(xí)了如何在封裝表單組件時(shí)避免打破單向數(shù)據(jù)流,以及如何使用計(jì)算屬性和輔助函數(shù)來簡化和優(yōu)化你的代碼。
這些技巧不僅能讓你寫出更高質(zhì)量和更易維護(hù)的代碼,還能讓你提高你的開發(fā)效率和水平。
希望你能在你的項(xiàng)目中運(yùn)用這些技巧,讓你的 Vue 組件更加完美和高效!
以上就是Vue利用computed解決單項(xiàng)數(shù)據(jù)流的問題的詳細(xì)內(nèi)容,更多關(guān)于Vue computed解決單項(xiàng)數(shù)據(jù)流的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中的虛擬dom知識(shí)點(diǎn)總結(jié)
這篇文章主要介紹了vue中的虛擬dom知識(shí)點(diǎn)總結(jié),文章圍繞主題內(nèi)容展開詳細(xì)介紹,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助2022-04-04
vue組件傳值的實(shí)現(xiàn)方式小結(jié)【三種方式】
這篇文章主要介紹了vue組件傳值的實(shí)現(xiàn)方式,結(jié)合實(shí)例形式總結(jié)分析了vue.js組建傳值的三種實(shí)現(xiàn)方式,包括父傳子、子傳父及非父子傳值,需要的朋友可以參考下2020-02-02
vue實(shí)現(xiàn)自定義日期組件功能的實(shí)例代碼
這篇文章主要介紹了vue自定義日期組件的實(shí)現(xiàn)代碼,代碼簡單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11
Vue3使用ECharts實(shí)現(xiàn)?;鶊D的代碼示例
?;鶊D是一種用于直觀顯示流向數(shù)據(jù)的可視化工具,特別適合展示復(fù)雜的網(wǎng)絡(luò)關(guān)系和資源流動(dòng),在前端項(xiàng)目中,通過結(jié)合?Vue?3?和?ECharts,可以快速實(shí)現(xiàn)交互性強(qiáng)、樣式美觀的?;鶊D,本文將通過完整的代碼示例,帶你一步步完成一個(gè)?;鶊D的實(shí)現(xiàn),需要的朋友可以參考下2025-01-01
一個(gè)因@click.stop引發(fā)的bug的解決
這篇文章主要介紹了一個(gè)因@click.stop引發(fā)的bug的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01
Vite創(chuàng)建Vue3項(xiàng)目及Vue3使用jsx詳解
vite是新一代的前端構(gòu)建工具,下面這篇文章主要給大家介紹了關(guān)于Vite創(chuàng)建Vue3項(xiàng)目以及Vue3使用jsx的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08

