下面是在我们平常在vue
中发起请求时常常编写的代码
const data = ref({
name: "liaoliao666",
subscribers_count: 1,
forks_count: 0
});
const getData = () => {
fetch('https://api.github.com/repos/liaoliao666/vu-query').then(async res =>
{
// {
// name: "vu-query",
// subscribers_count: 1,
// forks_count: 0
// }
const newData = await res.json();
data.value = newData
}
)
}
watchEffect(() => {
console.log("name 变化了", data.value?.name)
})
watchEffect(() => {
console.log("subscribers_count 变化了", data.value?.subscribers_count)
})
watchEffect(() => {
console.log("forks_count 变化了", data.value?.forks_count)
})
而当我们每次调用getData
这个方法时,你会发现所有依赖于data
的属性的所有组件和副作用都会重新渲染和执行,尽管subscribers_count
和forks_count
的原来一致
那么我们如何才能让新旧data
的数据结构共享呢?
其实很简单,只要我们对两个数据的属性进行对比,如果全等的话就跳过,不相等的情况下才赋值就可以了。
以下是代码的实现
// `replaceEqualDeep` 会深层地保留任何a中与b相同的属性
function replaceEqualDeep(a, b) {
if (a === b) return a
const array = Array.isArray(a) && Array.isArray(b)
const isSameObject = array || (isPlainObject(a) && isPlainObject(b))
// 当a和b都为数组或都为对象时,比较a和b的属性
if (isSameObject) {
if (array) {
const bSize = b.length
// 删除a中多余的长度
if (a.length > bSize) {
a.splice(bSize)
}
for (let i = 0; i < bSize; i++) {
// 递归比较
a[i] = replaceEqualDeep(a[i], b[i])
}
} else {
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
const hash = new Set(bKeys)
for (let i = 0, len = aKeys.length; i < len; i++) {
const key = aKeys[i]
// 删除a中多余的属性
if (!hash.has(key)) {
delete a[key]
}
}
for (let i = 0, len = bKeys.length; i < len; i++) {
const key = bKeys[i]
// 递归比较
a[key] = replaceEqualDeep(a[key], b[key])
}
}
}
// 为一样的对象时返回本身,否则返回新对象
return isSameObject ? a : b
}
// Copied from: https://github.com/jonschlinkert/is-plain-object
// 判断是否为普通的对象
function isPlainObject(o) {
if (!hasObjectPrototype(o)) {
return false
}
// If has modified constructor
const ctor = o.constructor
if (typeof ctor === 'undefined') {
return true
}
// If has modified prototype
const prot = ctor.prototype
if (!hasObjectPrototype(prot)) {
return false
}
// If constructor does not have an Object-specific method
if (!prot.hasOwnProperty('isPrototypeOf')) {
return false
}
// Most likely a plain Object
return true
}
function hasObjectPrototype(o) {
return Object.prototype.toString.call(o) === '[object Object]'
}
下面是经过优化过的getData
方法,你会发现这次只会打印 name 变化了 vu-query
const getData = () => {
fetch('https://api.github.com/repos/liaoliao666/vu-query').then(async res =>
{
// {
// name: "vu-query",
// subscribers_count: 1,
// forks_count: 2
// }
const newData = await res.json();
data.value = replaceEqualDeep(data.value, newData)
}
)
}
注意: 如果你有以下两个需求请不要使用该方法
- 如果由于数据过大而导致性能问题
- 如果对对象的
keys
顺序有需求,两比较对象的keys
不同,则可能会改变对象keys
的顺序,数组则不会,例如
const a = {
a: "a",
c: "c",
}
const b = {
a: "a",
b: "b",
c: "c"
}
console.log(replaceEqualDeep(a, b)) // {a: "a", c: "c", b: "b"}
在vu-query中已经默认内置了该功能,你如果想要禁用此功能(99.9%的场景您不需要禁用此功能),可以使用config.structuralSharing
,例
const query = useQuery('repoData', () =>
fetch('https://api.github.com/repos/liaoliao666/vu-query').then(res =>
res.json()
), {
structuralSharing: false
}
)