vue 中 v-model 的最佳实现

在利用 vue 开发一个组件时,可能需要它能够实时响应变化的值,这个时候我们就需要用到 v-model

然而在实际使用中,如果有两个相互影响的变量要进行实时变化时,就可能导致无限循环,提升编码难度。

本文给出了一个 v-model 的最佳实践,可以参考使用。

需求示例

假设我们需要编写一个部门用户选择组件,它能够同时返回选择的部门和用户,同时在初始化时,可以回显到界面。

下面大致分析一下可能导致的问题:

如果用户数据改变后,则返回的部门也需要触发变化,而返回的部门改变了,又会去触发用户改变,这样就构成了一个无限循环。

要如何解决这个问题呢?继续向下阅读。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div>
<DepartmentTree v-model="_departmentIds">部门树</DepartmentTree>
<UsersTable v-model="_users">用户列表</UsersTable>
</div>
</template>

<script>
import DepartmentTree from './departmentTree.vue'
import UsersTable from './usersTable.vue'

export default {
components: { DepartmentTree, UsersTable },
props: {
departmentIds: {
type: Array,
default: () => []
},
users: {
type: Array,
default: () => []
}
},

data() {
return {
_departmentIds: this.users,
_users: this.departmentIds
}
},

computed: {
// 使得每次返回的值不是同一个引用
// 在 watch 时可以解决新旧值一样的问题
usersCp() {
return JSON.parse(JSON.stringify(this.users))
}
},

watch: {
// 外部更改后,触发更新
departmentIds(newValues) {
// 判断与 this._departmentIds 的区别,如果是完全一样,则不继续执行
if (newValues.lenght === this._departmentIds.length) return

// 如果不一样时,通过部门来更新 _users 数据

// 触发 users 更改
this.usersChanged()

// 此处不需要触发 departments 修改,因为这个函数调用是由于外部的变化传递进来的
},

// 直接 watch users 的话,newValues 和 oldValues 是一样的
// 此处 watch 计算属性,这样就使得每次的结果不是同一个引用,新旧值就会不一样了
usersCp(newValues, oldValues) {
// 判断与 this._users 是否一样
if (this._users.length === newValues.lenght) return

// 将用户对应的 departmentIds 更新到 this._departmentIds 中
this._departmentIds = Array.from(new Set(this.users.map(x => x.departmentId)))

// 触发 departmentIds 改变
this.departmentIdsChanged()
// departmentIds 改变后,会触发 departmentIds watch 事件,
// 在这个事件执行时,会先匹配是否与当前实例保存的值一样,如果一样就不执行
// 这样就不会导致无限循环了
}
},

methods: {
usersChanged() {
this.$emit('update:users', this._users)
},

departmentIdsChanged() {
this.$emit('update:departmentIds', this._departmentIds)
}
}
}
</script>

<style>
</style>

上述代码的主要思想如下:

  1. 将外部变化与内部变化分开处理,外部变化处理通过 watch props 来实现,内部变化通过内部方法来实现
  2. 变化完成后,再向外传递变化结果
  3. 每次外部变化影响到内部时,都要进行验证,防止循环

watch 数组时新旧值相同处理

如果被 watch 的字段是一个数组时,它的新旧值都是一样的,因为引用相同,为了得到不一样的值,可以增加一个计算属性来进行过渡。

如上例中的 usersCp

参考

自定义组件的 v-model