Vue利用computed解決單項數據流的問題
Vue 是一個非常流行和強大的前端框架,它讓我們可以用簡潔和優(yōu)雅的方式來構建用戶界面。
但是,Vue 也有一些需要注意和掌握的細節(jié)和技巧。
今天我們來分享一個 Vue 中非常經典的問題,也是一個非常實用的技巧。
這個問題涉及到 Vue 的一個核心特性:單向數據流。
讓人頭痛的單向數據流
如果你不了解單向數據流是什么,或者你不知道如何在 Vue 中正確地使用它,那么請繼續(xù)往下看,我保證你會有所收獲。
這個問題在你使用 Vue 去封裝一個表單組件的時候,就會非常明顯地體現出來。
表單組件是前端開發(fā)中非常常見和重要的一種組件,它可以讓用戶輸入和提交數據,從而實現各種功能。
比如說,我們這里我們封裝了一個搜索條的簡單示例,來以小見大:
這個組件很簡單:
<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 傳入一個對象,這個對象里包括所以我們需要的數據,比如:占位符 placeholder、文本框的輸入值 keyword、下拉框的選中值 selectedValue、下拉框的選項值 options。
這些數據都給我們,我們將這個界面渲染出來。
那么父組件是這樣的:
<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: '請輸入你要查詢的關鍵字', options: [ { label: '視頻', value: 'video' }, { label: '文章', value: 'article' }, { label: '用戶', value: 'user' }, ], selectedValue: 'video', }); </script>
父組件在使用時自然而然會傳遞一些數據,這個數據也很簡單,一看就明白了。
我們使用 v-model
來綁定數據,這樣只要這個組件改動了這個數據,那么我們父組件就能收到通知,能夠對這個數據做相應的變化。
這個組件結構是非常清晰的,就是這么一種結構:
這都是基礎知識,沒什么好說的,但是實際情況是什么樣的呢?
現在的問題是子組件的文本框使用的是 v-model
綁定數據,但是這一綁定就把父組件傳遞的屬性它里邊的數據綁定進去了,那現在就變成了這種結構了:
于是這種情況就打破了單項數據流,打破單向數據流是要付出代價的,打破一次你的工程就距離“ shǐ山”更進一步。
那么我們希望不要打破單項數據流,回歸到正常的模式,那該怎么做呢?
解決辦法
最笨的辦法就是在子組件里不使用 v-model
,不然的話文本框一變這個父組件的數據就會跟著變,所以我們把 v-model
拆成原始的形式。
<template> <!-- 將 v-model 拆分 --> <el-input :modelValue="modelValue.keyword" @update:modelValue="handleKeywordChange" :placeholder="modelValue.placeholder"> <!-- etc... --> </el-input> </template> <script setup> // etc... // 定義一個 emit 事件 const emit = defineEmits(['update:modelValue']) function handleKeywordChange(val) { console.log('val >>> ', val) // 觸發(fā)子組件的 update:modelValue 事件 emit('update:modelValue', { // 因為這里我們只是修改了 keyword 的值 // 所以我們將 props.modelValue 展開之后,單獨將 keyword 的值賦值為新的值 ...props.modelValue, keyword: val }) } </script>
一個是 modelValue
用于綁定值。
另外一個是 update:modelValue
用于監(jiān)控這個組件的 update 事件,當事件觸發(fā)的時候調用 handleKeywordChange
函數。
handleKeywordChange 函數,要做的事情就是去觸發(fā)子組件 update:modelValue
事件,通知父組件去更改數據,所以我們定義了一個 emit 事件,數據變化的時候調用 emit 返回更新的數據。
雖然說這樣很麻煩,但是我們保證了單項數據流了。
那么有沒有一種簡介的方法呢?
其實面對這個問題,Vue 官方也好還是一些第三方庫,比如:vueuse 他們都有一種解決辦法,就是使用計算屬性去給它包一層:
父組件的數據傳遞過來之后,并沒有直接綁定到內部的文本框,而是在中間加了一個計算屬性,然后用這個計算屬性去綁定這個文本框,這個計算屬性要同時設置它的 getter 和 setter,當讀這個計算屬性的時候,讀的其實就是 modelValue 里的東西,所以讀是沒問題的。
但是這個文本框由于綁定了 v-model
這個文本框會變動的,變動的話改的就是這個計算屬性,也就觸發(fā)了這個計算屬性的 setter,那么在 setter 里邊我們就可以寫代碼去觸發(fā)這個 emit 事件。
這樣就簡化了代碼,同時又保證了單項數據流,官方就是這樣建議的,我們去嘗試一下好不好用:
<template> <!-- 將計算屬性綁定到文本框之上 --> <el-input v-model="keyword" :placeholder="modelValue.placeholder"> <!-- etc... --> </el-input> </template> <script setup> // etc... // 定義一個 emit 事件 const emit = defineEmits(['update:modelValue']) // 寫一個計算屬性,同時提供 get 和 set const keyword = computed({ // 讀取的時候直接返回讀取的值 get() { return props.modelValue.keyword; }, // 當修改的時候我們執(zhí)行 emit 的操作 set(val) { console.log('val >>> ', val) emit('update:modelValue', { ...props.modelValue, keyword: val }) } }) </script>
這樣我們就不需要拆分 v-model
了,雖然有所簡化,但是簡化的并不多,因為下拉框的選中值 selectedValue、下拉框的選項值 options 都要做成計算屬性。
那么我們能不能想一個辦法,就是說這個計算屬性不要只返回給我們一個字段,而是字節(jié)把整個對象返回,像這種模式:
<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'; 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>
將來修改計算屬性的時候就觸發(fā)事件,綁定值得到話就綁定計算屬性的字段。
這樣就能通過一個計算屬性屬性,搞定全部的問題了。
但是現在修改是無效的,因為綁定的是 model 里的一個字段,并不是 model,所以修改的也是 model 的字段,所以并不會觸發(fā) set 的更新:
因為只有改動了 model 本身的時候,它才會去運行 setter,改動的是某一個字段就不會運行 setter,那現在就不好辦了。
但是,轉折來了,有一個奇招可以解決這個問題:
const model = computed({ get() { // 我們這里返回一個代理對象,代理 props.modelValue 這個屬性 return new Proxy(props.modelValue, { // 因為這是一個代理對象,那么將來修改代理對象的某個值時 // 就會運行這個 set 函數 // 函數中可以拿到 // obj:改動的對象 // name:改動的屬性名 // val:改動的屬性值 set(obj, name, val) { console.log('Emit >>> ', name, val) // 當我們想改的一個對象的屬性時并不去真正的修改 // 而是在這里也觸發(fā) emit,然后生成一個新的對象 emit('update:modelValue', { ...obj, // 展開以前對象的值 [name]: val // 將其中的修改的屬性修改為新的值 }) return true; // 最后返回一個 true }, }); }, set(val) { emit('update:modelValue', val) } })
這就正常的觸發(fā)了事件函數,那么這樣一來代碼就進一步得到簡化了。
我們使用一個計算屬性屬性就可以替代里邊的所有字段,特別是在一個大表單里,有很多很多的字段,這一招非常的好用。
在子組件里無論有多少個文本框選項,都去用這個計算屬性去綁定就可以了。
既不會打破單向數據流,而且實現代碼也非常少。
擴展
其實我們還可以把這個問題擴展一下,因為我們在實際開發(fā)中,封裝表單是一件常事,所以在每一次封裝表單的都是都去寫一次這樣的代碼有點繁瑣,我們可以把它提出去,寫成一個輔助函數。
import { computed } from 'vue'; /** * props:屬性對象 * propName:要做成計算屬性的名字 * emit:emit 函數 */ 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); }, }); }
這樣就可以通過一個輔助函數幫我們把要做的事情實現,使用起來就非常的舒服了:
<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'; // 導入輔助函數 const props = defineProps({ modelValue: { type: Object, required: true, }, }); const emit = defineEmits(['update:modelValue']); // 調用函數將需要的參數傳遞進去 const model = useVModel(props, 'modelValue', emit); </script>
以后無論是非常簡單的表單封裝,還是非常龐大的表單封裝,都可以用這么幾行代碼來解決問題了。
既保護了單項數據流,又簡化了代碼的書寫,在實際開發(fā)中用起來是非常的好用。
總結
通過這篇文章,你應該對 Vue 的單向數據流有了更深入的理解和掌握。
你學習了如何在封裝表單組件時避免打破單向數據流,以及如何使用計算屬性和輔助函數來簡化和優(yōu)化你的代碼。
這些技巧不僅能讓你寫出更高質量和更易維護的代碼,還能讓你提高你的開發(fā)效率和水平。
希望你能在你的項目中運用這些技巧,讓你的 Vue 組件更加完美和高效!
以上就是Vue利用computed解決單項數據流的問題的詳細內容,更多關于Vue computed解決單項數據流的資料請關注腳本之家其它相關文章!
相關文章
Vite創(chuàng)建Vue3項目及Vue3使用jsx詳解
vite是新一代的前端構建工具,下面這篇文章主要給大家介紹了關于Vite創(chuàng)建Vue3項目以及Vue3使用jsx的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-08-08