> ## Content Index
> Fetch the complete content index at: https://iiong.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Vue3 中的 shallowRef 技巧
- URL: https://iiong.com/the-shallowref-trick-in-vue3/
- Published: 2025-03-14T11:13:38.000Z
- Updated: 2026-06-22T15:24:08.000Z
- Description: 关于一些 Vue3 的 shallowRef 一些使用方法介绍和一些奇淫技巧。
- Author: 淮城一只猫
- Tags: Vue.js, 奇技淫巧

### 前言

使用 `Vue3` 至今为止算是熟悉，但不能说很熟练了，比如针对 `shallowRef`, `shallowReactive` 这类就不算特别熟悉，但当初没有仔细看文档的回旋镖总会砸到自己头上的。

最近开发一个数据大量可视化的项目，引用大量外部类库文件，在更新数据的时候发现网页性能大幅度下降，使用性能定位工具看了下是深度响应式问题导致的：大量使用 `ref` 定义外部类库函数，导致系统一直索引内部深度响应式。

### shallowRef

因为之前开发几乎没有遇到大数据性能问题，直接梭哈 `ref` ，这次算是遇到坑了，其实解决方法也简单就是将 `ref` 换成 `shallowRef` 就行了，所以说 `shallowRef` 是有什么特性：`ref() 的浅层作用形式`，说人话就是只有替换最顶层的值才能响应式：

```javascript
import { shallowRef } from 'vue'

const state = shallowRef({ count: 0 })

// 直接修改嵌套属性不会触发更新
state.value.count = 1 // ❌ 不会触发视图更新

state.value = { count: 1 } // ✅ 这样才能触发响应

```

所以说这个使用场景在：

- 用在外部类库声明上，比如 `ECharts.js` 组件或者说富文本组件等等。
- 还有就是一些大量数据声明：比如后端给个数据，需要频繁变化，但不想关心内部结构，只需要在意是否有变化。
- 父组件管理子组件一些数据维护开支等。

### 进阶学习

思考后感觉没这么简单，实践后发现一些奇淫技巧，以后开发会用到的，就笔记下来了：

**手动更新**

如果现在有个场景一个数据使用 `shallowRef` 声明了很多，但发现替换顶层数据太麻烦了，只想更新其中一个对象属性，这时候可以把 `triggerRef` 拉出来使用了：

```javascript
import { shallowRef, triggerRef } from 'vue';

const state = shallowRef({ count: 0 });

// 直接修改嵌套属性不会触发更新
state.value.count = 1; // ❌ 不会触发视图更新

// 手动强制触发更新
triggerRef(state); // ✅ 强制触发响应

```

这一般可以用在数据频繁修改或者大型数据修改的时候，可以避免性能追踪开销，只要手动修改就行。

再比如在动画实时变化的场景下：

```javascript
import { shallowRef, onMounted } from 'vue';

const data = shallowRef({ x: 0, y: 0 });

onMounted(() => {
  requestAnimationFrame(() => {
    data.value.x++; // 高频修改属性
    if (data.value.x === 10000) triggerRef(data); // 手动触发更新（按需调用）
  });
});

```

**混合响应式数据**

之前遇到过一种数据，大概有五六层深度的对象数据，需要变动三层某个属性数据，这时候利用 `shallowRef` 和 `reactive` 完成：

```javascript
import { shallowRef, reactive } from 'vue';

const refState = shallowRef({ 
  config: reactive({ enabled: true }), // 部分属性保持响应式
  metadata: { version: '1.0' }         // 非响应式部分
});

refState.value.config.enabled = false; // ✅ 触发响应
refState.value.metadata.version = '2.0'; // ❌ 不触发响应

```

**customRef 的一些技巧**

正常来说能用到 `customRef` 场景很少，官方文档是不建议使用这个，除非需要对变量的细腻度追踪，`Demo` 代码建议去文档查看，这边就展示一些常用的代码技巧：

- 追踪一个变量的变化历史，比如编辑器的用户操作场景：

```javascript
function historyRef(initialValue) {
  let history = [initialValue];
  return customRef((track, trigger) => ({
    // 读取动作
    get() {
      track();
      return history[history.length - 1];
    },
    // 记录动作
    set(newValue) {
      history.push(newValue);
      trigger();
    },
    // 撤回动作
    undo() {
      if (history.length > 1) {
        history.pop();
        trigger();
      }
    }
  }));
}

```

- 数据验证拦截

```javascript
function validatedRef(initialValue, validator) {
  return customRef((track, trigger) => ({
    get() {
      track();
      return initialValue;
    },
    set(newValue) {
      if (validator(newValue)) {
        initialValue = newValue;
        trigger();
      } else {
        console.warn('Invalid value!');
      }
    }
  }));
}

// 使用
const age = validatedRef(18, (v) => v >= 0);

```

- 非响应库集成

```javascript
import { customRef } from 'vue';
import { NonReactiveStore } from 'some-library';

const store = new NonReactiveStore();

const storeRef = customRef((track, trigger) => ({
  get() {
    track();
    return store.getState();
  },
  set(newValue) {
    store.setState(newValue);
    trigger();
  }
}));

// 监听外部库变化
store.onChange(() => {
  trigger(storeRef); // 外部变化时手动触发更新
});

```

- 缓存计算

```javascript
function cachedRef(initialValue, computeFn) {
  let cache = null;
  return customRef((track, trigger) => ({
    get() {
      track();
      if (cache === null) {
        cache = computeFn(initialValue);
      }
      return cache;
    },
    set(newValue) {
      initialValue = newValue;
      cache = null; // 清空缓存
      trigger();
    }
  }));
}

// 使用
const expensiveData = cachedRef(0, (n) => heavyCalculation(n));

```