Skip to content

Commit

Permalink
feat: v-model绑定对象或数组,不使用源对象更改的解决方案
Browse files Browse the repository at this point in the history
  • Loading branch information
yuntian001 committed Apr 21, 2023
1 parent 890d352 commit 0a22b6e
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { proxyValue } from '@/utils/helper';
import { cloneDeep } from 'lodash-es';

/**
* 用于解耦v-model 对象/数组
* 深度代理 对象/数组 响应后触发update方法并传入深copy值
* @param v
* @param update
* @returns
*/
export const computedModel = <T extends Record<string | number, any> | any[]>(
v: () => T,
update: (value: T) => void,
) => {
const data = shallowRef({} as T);
watch(
v,
() => {
const value = cloneDeep(toRaw(v()));
data.value = proxyValue(value, () => {
update(cloneDeep(value));
});
},
{ immediate: true, deep: true },
);
return data;
};
8 changes: 8 additions & 0 deletions src/router/routes/example/7-modelObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RouteRecordRaw } from 'vue-router';
export const routes: RouteRecordRaw[] = [
{
path: 'modelObject',
component: () => import('@/views/example/modelObject/index.vue'),
meta: { title: 'v-model对象映射示例' },
},
];
24 changes: 24 additions & 0 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,27 @@ export const getColorLuma = function (color: string) {
const blue = parseInt(color.slice(5, 7), 16);
return red * 0.299 + green * 0.587 + blue * 0.114;
};

/**
* 递归代理对象/数组,响应后触发update方法
* @param value
* @param update
* @returns
*/
export const proxyValue = <T extends Record<string | number, any> | any[]>(value: T, update: () => void): T => {
return new Proxy<T>(value, {
get: function (obj, prop) {
if (typeof obj[prop as keyof T] === 'object') {
return proxyValue(obj[prop as keyof T] as T, update);
}
return obj[prop as keyof T];
},
set: function (obj, prop, value) {
const oldV = obj[prop as keyof T];
obj[prop as keyof T] = value;
update();
obj[prop as keyof T] = oldV;
return true;
},
});
};
39 changes: 39 additions & 0 deletions src/views/example/modelObject/components/testModel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<div class="test">
<div>modelValue: {{ JSON.stringify(modelValue) }}</div>
<div>value: {{ JSON.stringify(value) }}</div>
<el-button @click="a2(1)"> 内层设置 a.a1.a2 = 1</el-button>
<el-button @click="a2(2)"> 内层设置 a.a1.a2 = 2</el-button>
<el-button @click="b1(11)"> 内层设置 a.b1 = 11</el-button>
<el-button @click="b1(22)"> 内层设置 a.b1 = 22</el-button>
<el-button @click="b(111)"> 内层设置 b = 111</el-button>
<el-button @click="b(122)"> 内层设置 b = 222</el-button>
<div>a.a1.a2 :<el-input v-model="value.a.a1.a2"></el-input></div>
</div>
</template>

<script setup lang="ts" name="Test">
import { computedModel } from '@/hooks';
const props = defineProps<{
modelValue: Record<string, any>;
}>();
const emit = defineEmits<{ (e: 'update:modelValue', value: Record<string, any>): void }>();
const value = computedModel(
() => props.modelValue,
(value) => emit('update:modelValue', value),
);
const a2 = (v: number) => {
value.value.a.a1.a2 = v;
};
const b1 = (v: number) => {
value.value.a.b1 = v;
};
const b = (v: number) => {
value.value.b = v;
};
</script>

<style lang="scss" scoped>
.test {
}
</style>
30 changes: 30 additions & 0 deletions src/views/example/modelObject/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<div>
<div>对组件v-model 绑定对象结构props进行proxy代理映射,进行深copy并调用update emit更新</div>
<div class="index">外层 value值 {{ JSON.stringify(value) }}</div>
<el-button @click="a2(7)"> 外层设置 a.a1.a2 = 7</el-button>
<el-button @click="a2(8)"> 外层设置 a.a1.a2 = 8</el-button>
<el-button @click="b1(77)"> 外层设置 a.b1 = 77</el-button>
<el-button @click="b1(88)"> 外层设置 a.b1 = 88</el-button>
<el-button @click="b(777)"> 外层设置 b = 777</el-button>
<el-button @click="b(888)"> 外层设置 b = 888</el-button>
<test-model v-model="value"></test-model>
</div>
</template>

<script setup lang="ts" name="Test">
import TestModel from './components/testModel.vue';
const value = ref({
a: { a1: { a2: 3 }, b1: 3 },
b: 1,
});
const a2 = (v: number) => {
value.value.a.a1.a2 = v;
};
const b1 = (v: number) => {
value.value.a.b1 = v;
};
const b = (v: number) => {
value.value.b = v;
};
</script>

0 comments on commit 0a22b6e

Please sign in to comment.