vue 组件封装单向数据流
vue 组件封装想要杜绝双休数据流,不然离代码shi山越来越近
在 Vue 中,双向绑定是通过将数据流向子组件并在子组件中使用
v-model
实现的。如果在子组件中直接修改了父组件中的数据,就会打破单向数据流的规则,从而导致一些问题。具体来说,如果在子组件中直接修改了父组件中的数据,那么这个修改会立即反映在父组件中,但是父组件并不会触发更新,也就是说父组件的视图不会更新。这会导致父组件和子组件之间的数据不一致,进而导致一些难以排查的 bug。
本文将介绍 vue 组件中封装单向数据流的方法。
在 vue 组件中,为了保证数据的单向流动,我们通常会将数据定义在父组件中,通过 props 传递给子组件。子组件通过事件向父组件发送数据,保证数据的单向流动。
下面是封装的一个简单的不考虑单项数据流的组件:
- 具体实现方法:
- 在父组件通过 v-model 将数据传递给子组件
- 子组件通过props接受参数
- 子组件直接使用v-model 使用传递过来的值
父组件:
<script setup lang="ts">
import { ref } from "vue";
import SearchBar from "./SearchBar.vue";
const searchData = ref({
keyword: "",
placeholder: "请输入你想要查询的关键字",
options: [
{ label: "视频", value: "video" },
{ label: "文章", value: "article" },
{ label: "用户", value: "user" },
],
selectedValue: "video",
});
</script>
<template>
<SearchBar v-model="searchData"></SearchBar>
</template>
<style scoped>
</style>
子组件:
<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>
<style scoped></style>
数据变化视图:
这样会产生的问题是:
如果在子组件中直接修改了父组件中的数据,那么这个修改会立即反映在父组件中,但是父组件并不会触发更新,也就是说父组件的视图不会更新。这会导致父组件和子组件之间的数据不一致,进而导致一些难以排查的 bug。
所以需要杜绝类似的问题,就不能打破单项数据流。
初步解决办法:
通过model-value
和**@update:modelValue
解决**
具体实现方法如下:
- 在父组件中定义数据并将数据通过 props 传递给子组件。
- 在子组件中定义一个名为 emit 的方法,用于向父组件发送数据。
- 在子组件中使用 $emit 方法调用 emit 方法,并传递需要发送的数据。
示例代码如下:
<template>
<el-input
:model-value="modelValue.keyword"
@update:modelValue="handleKeywordChange"
: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,
},
});
const emit = defineEmits(["update:modelValue"]);
function handleKeywordChange(value) {
emit("update:modelValue", { ...props.modelValue, keyword: value });
}
</script>
<style scoped></style>
但是数据多的话这样和麻烦。真的很麻烦
进阶解决办法:
通过com
puted 和**get set
解决**
具体实现方法如下:(官方建议的)
- 子组件将父组件传递的值通过***
computed
*** 处理。 - 在子组件的***
computed
*** 属性中设置get() {}
和set(val) {}
方法。 - 父子组件直接使用
v-model
。
示例代码如下:
<template>
<el-input v-model="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";
import { computed } from "vue";
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
const emit = defineEmits(["update:modelValue"]);
const keyword = computed({
get() {
return props.modelValue.keyword;
},
set(val) {
emit("update:modelValue", { ...props.modelValue, keyword: val });
},
});
</script>
<style scoped></style>
但是数据多和数据结构是复杂数据类型的时候,要么一个一个设置get
和set
。不然会导致改复杂数据类型的时候,set
不会触发。
更好的解决办法:
通过***computed
和***get set
和***Proxy
* 解决
具体实现方法如下:
- 子组件将父组件传递的值通过***
computed
*** 处理。 - 在子组件的***
computed
*** 属性中设置get() {}
get方法获取的值是通过*Proxy
代理过的*。 - 父子组件直接使用
v-model
。
示例代码如下:
<template>
<el-input v-model="model1.keyword" :placeholder="model1.placeholder">
<template #prepend>
<el-select
v-model="model1.selectedValue"
placeholder="Select"
style="width: 85px"
>
<el-option
v-for="item in model1.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 { computed } from "vue";
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
const emit = defineEmits(["update:modelValue"]);
const model1 = computed({
get() {
return new Proxy(props.modelValue, {
set(obj, name, val) {
console.log("emit", val);
emit("update:modelValue", { ...obj, [name]: val });
return true;
},
});
},
});
</script>
<style scoped></style>
通过以上方法,我们可以很容易地实现 vue 组件中的单向数据流封装,保证了数据的单向流动,降低了组件之间数据流动的耦合度,提高了组件的可维护性。