shallowRef
源码实现
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
//shallow为真则直接赋值value本身
this._value = __v_isShallow ? value : toReactive(value)
}
}
场景用途(From ChatGPT,不一定可信)
- 组件中使用 props 数据时:由于父组件传递给子组件的 props 数据在子组件内部是只读的,因此可以通过 shallowRef 创建一个响应式对象来进行赋值和修改。这样就可以在子组件内部观察这个对象的变化,完成相应的 data 和 UI 界面的更新。
<template>
<Child :data="shallowData" />
</template>
<script>
import { shallowRef } from "vue";
import Child from "./Child.vue";
export default {
components: {
Child,
},
setup() {
const shallowData = shallowRef({ name: "Vue", version: 3 });
...
return { shallowData };
},
};
</script>
- 视图部分的性能优化:对于复杂的、嵌套层次较深的数据结构,如果每次都全部遍历并监测其变化,会占用大量计算资源和内存空间,从而影响页面的性能。使用 shallowRef 可以避免这种情况,只观测引用类型数据对象本身,可以更有效地避免不必要的计算和重新渲染。
<template>
<div>{{ computedData }}</div>
</template>
<script>
import { shallowRef, computed } from "vue";
export default {
setup() {
const data = { num: 0, arr: [1, 2, 3], obj: { name: "Vue" } };
const shallowData = shallowRef(data);
// 监听 data 数组变化
const computedData = computed(() => {
console.log("computedData compute");
return shallowData.value.arr.map((item) => item * 2).join(",");
});
return { computedData };
},
};
</script>
不可变数据:
- 不可变数据结构是指一旦创建并初始化之后,就不能被修改或改变的数据结构。在JavaScript中,一个不可变数据结构包括一个原始值(如字符串或数字)、一个数组或一个对象。
- 尽管不可变数据结构看似“不可变”,但实际上是有界限的。例如,在JavaScript中,可以通过重新赋值来更改对一个不可变数据结构的引用。而对于一个对象或数组中的深度嵌套属性进行修改时,仍然需要在新的副本上完成操作,因此仍然会带来性能问题。
- 因此,从严格意义上来说,不可变数据结构并非完全不可变,只是不允许通过常规方式进行更改,包括添加、删除、编辑、移动和复合等方式。总体而言,不可变数据结构的优点还是非常显著的,它们能够提高代码的维护性和安全性,并减少并发问题的发生。
immer
immer是一个JavaScript库,它提供了一种方便的方式来创建和处理不可变数据结构。通过immer,开发人员可以更轻松地编写具有更高性能、更少副作用和易于维护的代码。
immer的主要作用如下:
简化不可变数据结构的创建:通过immer,开发人员可以使用与普通JS对象相同的语法创建不可变数据结构,而不需要手动进行深度复制或手动跟踪不变性标记。
方便的状态更新:在不可变数据结构中进行状态更新可能会比较麻烦,而immer可以使这个过程更加简单明了。通过提供一个“draft”对象,immer让开发人员可以直接修改其属性,并自动保留所有先前的状态以及对被修改路径上的所有对象进行快速有效的比较。
避免错误:由于在JavaScript中对象是可变的,因此非常容易出现误修改已经存在的数据的情况。由于immer使用不可变数据结构,因此它可以避免这些问题,使得应用程序更加健壮和安全。
性能问题
- React无法得知虚拟dom会在哪里被更改,所以会进行全局对比
- 您说的情况是 React 在进行 Virtual DOM 对比时,会将整个 Virtual DOM 树与之前的 Virtual DOM 树进行对比。这里所说的“全局对比”并非字面意义上的 “全局”,而是相对于部分更新而言的。也就是说,React 会将现有的 Virtual DOM 树和之前的 Virtual DOM 树都合并起来,只去更新需要被更新的部分。
- 由于 React 无法判断哪一部分会被更改,因此在进行 Virtual DOM 对比时,会默认将整个 Virtual DOM 树都进行对比。但在实际更新时,React 会根据具体变化情况,仅对需要更新的部分进行实际的更新操作,而不是将整个应用程序的 DOM 重新渲染。
- 总之,虽然 React 在进行 Virtual DOM 对比时可能需要对整棵树进行遍历,但它仍然可以通过智能的更新机制来尽量减少不必要的更新操作来提高性能。
- vue却可以对模板进行静态优化,减少一下静态内容的对比,从而性能优于react是吗?
是的,Vue 在模板编译阶段可以进行静态分析和优化,尤其是针对纯 HTML 的静态内容,可以将其作为常量进行缓存,避免在每次组件渲染时都重新生成。这样可以大幅度减少 Virtual DOM 对比的时间和开销。
而 React 的 JSX 语法是动态生成的,无法像 Vue 那样直接在编译期间进行优化。因此,React 的 Virtual DOM 对比基本上是在运行时进行的。
当然,需要注意的是优化的程度和实际应用场景相关。在一些应用中,如果组件结构比较复杂,依然会导致 Virtual DOM 对比的性能瓶颈。但总体来说,Vue 在模板编译上对纯 HTML 的优化确实会使其在一些情况下比 React 更具性能优势。
- React为什么需要memo,vue却不用,react如果默认对比prop会怎样?
React 不默认进行 prop 的比较并决定是否渲染的原因主要是出于性能方面的考虑。如果在每次更新组件时都进行 props 的比较,这将会给应用带来一定的开销,使得程序变得更加缓慢。Vue 内部已经实现了一套检测变化的机制(包括观察对象或数组的变化),只要你在父组件中修改了数据,那么子组件就会依据新的值重新渲染。
- v-memo指令的作用
- 判断依赖数组,不变的话子树直接不动了.不运行,放个[]就等于v-once.针对于模板,而react的memo,是判断prop然后确定要不要渲染组件
Proxy性能问题
在 JavaScript 中,Proxy 可以用来拦截对象的某些操作,例如读取/写入属性、调用函数等。虽然 Proxy 增强了 JS 的功能,但是其本身的实现并不像原生对象那样高效,因此使用时需要注意性能问题。
具体来说,Proxy 对象的性能问题主要有两个方面:
- 绕过了对象的隐藏类和形态(Shape) 在 V8 引擎中,对象的隐藏类和形态是提高访问速度的重要机制。当引擎遇到一个新的对象时,会创建一个隐藏类来跟踪该对象的属性,并为其分配空间。如果这个对象的属性发生更改,V8 引擎就需要创建新的隐藏类或更新已有的隐藏类,这可能会导致一定的性能开销。 然而,Proxy 对象的可拦截操作不能被隐藏类所优化,因为 Proxy 拦截器可以随时更改它们代理的对象的属性,从而打破对象的隐藏类。这意味着在处理大型和复杂的对象时,使用 Proxy 代理可能比直接访问对象属性更慢,因为每次访问属性时都需要重新计算隐藏类和形态。
在 V8 引擎中,每个 JavaScript 对象都有一个隐藏类(hidden class)和一个隐藏形态(hidden shape)。这是 V8 引擎用来提高对象访问速度的重要机制。
隐藏类是一个包含对象的所有已定义属性的列表及其顺序的编号。当引擎遇到一个新的对象时,它会为该对象创建一个新的隐藏类并将其与对象关联起来。如果在创建之后,对象的属性发生了变化,那么引擎就会根据需要更新隐藏类,使其能够正确地跟踪属性。
使用隐藏类可以让 V8 引擎为对象的属性分配固定偏移量的存储空间,并且在访问属性时可以直接使用偏移量进行操作,而不是通过名称查找属性的位置。这样可以大大提高对象属性访问的速度。
隐藏形态是指对象结构的表示方式。V8 引擎将每个 JavaScript 对象表示为一组“插槽”(slots)和“元素”(elements),其中插槽是对象自身的属性,而元素则是数组或类似数据结构中的值。V8 引擎会根据对象属性的数量和类型动态决定对象的“形态”,并在必要时调整其内置结构。这样,在处理多个类型相同的对象时,可以共享同一种形态,从而提高程序的效率。
总之,隐藏类和形态是 V8 引擎中提高对象访问速度的核心机制。通过这些机制,V8 引擎可以在处理大量对象时快速跟踪属性位置和对象内部结构,并根据需要优化对象的内存布局。
- 拦截器方法的执行成本 另一个导致性能问题的因素是拦截器方法的执行成本。如果对每个属性读取/写入操作都执行一些预处理、验证或转换等额外操作,那么就会增加对象访问的开销。
因此,在实际使用中,需要谨慎地考虑是否需要使用 Proxy 对象来代理对象,并且确保只拦截必要的操作。
大量数据
如果数组内对象很多,每次调用 setState 时都需要创建一个全新的对象数组副本(如在 handleEdit 中所做),可能会影响应用的性能。
要避免这个问题,您可以考虑使用类似于 Vue 的实现方式,通过将每个表格行单独作为一个子组件来避免不必要的全局状态更新。在应用中,仅更改一个子组件的状态也不会触发父组件或其他兄弟姐妹组件的重新渲染,从而提高应用程序的渲染效率。
另外,React 推荐使用不可变数据结构来管理组件状态,因为它们可以避免原地修改数据,减少开销以及方便重新使用和比较。您可以使用 Immutable.js 这样的库,或者使用 Object.freeze() 方法和其他 JavaScript 技术来实现,只要确保如果对象相等,则它们引用的内存地址也相同。
最后,如果您需要使用大量的可编辑表格,可以考虑使用一些第三方表格库,如 Material-UI 或 React Table 等,这些库已经对性能进行了优化,提供了丰富的特性和易于实现的 API。
immer性能问题
Immer是一款优秀的JavaScript库,可以更加轻松地处理不可变状态。在常规使用下,它可以帮助我们提高代码的可读性和健壮性。然而,在某些情况下,Immer可能存在一些性能问题。
首先,在大型应用中处理复杂数据结构时,可能会因为不可变性的复制操作而导致性能瓶颈。通常来说,当我们创建一个新的状态并对其进行多次深度修改时,这种 overhead 是微小的,因为只到在第一个 draft 创建時才会产生开销,并在之后的所有修改中重用其内部结构。但是,在每次创建新的 draft 时都需要执行全面的重新分配或拷贝,可能会导致性能问题。
另外,由于 Immer 的实现机制涉及了Javascript语言运行时的Proxy代理机制,针对某些十分庞大,且递归访问层数特别深的对象以及非常频繁的运算,也有可能出现明显的性能问题。
在解决上述情况时,可以考虑以下一些方案:
将 immutable 数据存储于单个 JavaScript 对象中,不定义很多额外属性。减少对象产生时浅层拷贝的费用。 在每次更新时,直接改变状态,只需要在必要的时候才将其变成不可变的。这样可以避免复制大型数据结构的开销。 在特定情况下,我们可以使用 Object.freeze() 方法来让对象变为只读状态,以避免被修改。但是,这种方法不能递归地冻结整个数据结构,并且会增加内存消耗和其他一些损失。 最后需要注意的是,我们应该根据具体需求选择措施,在保证代码质量的同时,确保程序的性能高效运行。
immer和proxy的关系
Immer是一个JavaScript库,它可以更加容易地处理不可变状态,并且使用标准的JavaScript对象来表示这些不可变数据。同时,Immer底层也是基于ES6中新增了Proxy和Reflect两个功能对象实现的。
Proxy是ES6中引入的一个全新的元编程API,用于创建可以代理JavaScript对象的代理器。这些代理器允许你在读取或修改对象时执行所需的自定义逻辑。例如,在Immer中,我们可以使用Proxy来劫持JavaScript对象的属性读写操作,从而在对象的内部结构上进行一些特殊的处理,例如变更检测、附加元数据等。这样就使得我们可以通过维护一个原始的 immutable 数据结构版本及其需要变更的信息(所谓draft tree),然后自动追踪其所有 mutative 操作,以便在最后生成优化的结果时,大量减少中间对象的复制和扩散成本。
在Immer的实现过程中,它构建了一个Immutable树形结构来管理当前数据模型的状态,然后使用Proxy代理模式,利用 JavaScript 的语言特性实现比较高效率的支持人类直觉数据访问和更新操作。因此,可以说Immer库的核心实现机制离不开ES6 Proxy与Reflect提供的显式元编程能力,而正是由于ES6中支持了Proxy和Reflect功能对象的出现,使得Immer能够自然且优化地实现我们期待的功能。