1. 这里是 Vue 学习笔记¶
1.1 一些整理¶
1.1.1 storeToRefs¶
storeToRefs 是 Pinia 提供的 Composition API 辅助函数。它的作用是 安全地解构 store 中的 state 属性,同时保持它们的响应性。
它遍历 Pinia Store 实例的 state 属性,并使用 Vue 的 toRef() 方法将每一个状态属性(例如 status, retryAttemptCount)转换为独立的 响应式引用 (Ref)。
在 Vue 3 的 Composition API 中,如果你直接解构一个响应式对象,会失去响应性
<script setup>
import { useAppStore } from '@/stores/useAppStore';
import { storeToRefs } from 'pinia'; // <-- 关键工具
import { inject } from 'vue';
// 1. 获取 Pinia Store 实例
// 这一步会访问到 Pinia 在内存中维护的 'app' store 的单例实例
const store = useAppStore();
// 2. 提取响应式引用 (Subscription的核心)
// storeToRefs 是 Pinia 提供的 Composition API 辅助函数。
// 它的作用是:安全地解构 store 中的 state 属性,同时保持它们的响应性。
const { status, retryAttemptCount, lastError } = storeToRefs(store);
// 3. 模板中使用(自动订阅)
// 此时,status、retryAttemptCount 等都是 Vue 的 Ref 对象。
// 只要 ConnectionManager 调用了 store.setStatus(),这些 Ref 就会更新,
// 从而触发依赖它们的组件模板自动重新渲染。
const appManager = inject('AppManager'); // 注入 Manager 实例,用于调用方法
// ... reconnect 逻辑 ...
</script>
<template>
<!-- 模板中直接使用提取出来的 Ref,无需 .value -->
<p>Current State: <strong>{{ status }}</strong></p>
<div v-if="status === 'CONNECTING'">
Attempt #{{ retryAttemptCount }}...
</div>
<!-- ... -->
</template>
1.1.2 vue 中的 toRef() 是什么意思¶
toRef() 是 Vue 3 Composition API 提供的一个工具函数,用于 将一个响应式对象上的某个属性,转换为一个独立的、保持连接的 Ref 对象。
当你从一个响应式对象中解构出某个属性时,你通常会失去该属性的响应性。toRef() 的核心价值就在于:它创建的 Ref 是对源对象属性的一个引用(指针),而不是值的拷贝。
import { toRef } from 'vue';
const propertyRef = toRef(source: object, key: string);
import { reactive, toRef } from 'vue';
const state = reactive({
count: 0,
name: 'Vue'
});
// 1. 使用 toRef 创建一个 count 的引用
const countRef = toRef(state, 'count');
// 2. 它们是相互关联的:
// 通过 Ref 修改:
countRef.value++;
console.log(state.count); // 输出: 1 (state 也更新了)
// 通过源对象修改:
state.count = 10;
console.log(countRef.value); // 输出: 10 (Ref 也更新了)
1.1.3 provide 和 inject¶
provide 和 inject 是 Vue 3 Composition API 中提供的一对功能,它们提供了一种强大的 跨组件层级传递数据 的机制,通常被称为 依赖注入 (Dependency Injection)。
它们解决了“Prop 逐级透传 (Prop Drilling)”的问题,即父组件需要将数据传递给一个非常深的孙子组件时,必须经过中间组件的层层传递。
provide/inject 主要用于解决 应用级的、跨层级的或基础服务的传递。
如何进行类型安全的 provide/inject
// 注入时提供默认值
const theme = inject('theme-mode', 'light');
// 结合 Symbol 和 TypeScript 确保 Key 的唯一性和类型安全
export const ConnectionManagerKey = Symbol('ConnectionManagerKey');
// 提供者
provide(ConnectionManagerKey, appManager);
// 注入者
const appManager = inject<ConnectionManager>(ConnectionManagerKey);
1.1.4 缩写指令¶
@v-on绑定事件:v-bind绑定属性#v-slot绑定插槽
1.1.5 app.config.globalProperties 如何使用¶
app.config.globalProperties 是 Vue 3 应用实例(app)上的一个配置选项,用于 在应用中全局注册属性。一旦注册,这些属性就可以在 任何组件实例(包括选项式 API 的 this 和 Composition API 的模板)中直接访问,而无需显式导入或注入。
这个机制主要用于注册应用级别的工具函数、常量、配置或服务实例。
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// 注册全局属性
app.config.globalProperties.$appName = 'MyApp V3';
// 注册一个全局方法
app.config.globalProperties.$formatDate = (date) => {
return new Date(date).toLocaleDateString();
};
// 注册您之前设计的 ConnectionManager 实例
import { ConnectionManager } from './services/ConnectionManager';
const appManager = new ConnectionManager();
app.config.globalProperties.$appManager = appManager;
app.mount('#app');
如何使用
- 在选项式 API 中访问
在选项式 API 中,注册的属性会被混入到组件实例 this 中。
<template>
<div>
<h1>Welcome to {{ $appName }}</h1>
<p>Current Date: {{ $formatDate(new Date()) }}</p>
<button @click="reconnect">Reconnect</button>
</div>
</template>
<script>
export default {
// 选项式 API 访问
methods: {
reconnect() {
// 通过 this 直接访问全局属性
this.$appManager.reconnectManually();
console.log(`Using manager from global properties: ${this.$appName}`);
}
}
}
</script>
- 在 Composition API 中访问
在 Composition API 的 <script setup> 中,你不能直接使用 this。但注册的属性仍然可以在模板中直接访问。
如果你需要在 <script setup> 的 JavaScript 逻辑中访问这些属性,你需要使用 getCurrentInstance()(但官方不鼓励过度依赖此方法,更推荐使用专门的 Composition API 工具)。
访问方法(使用 useRuntimeConfig 模拟):
由于 Vue 3 的设计理念是去 this 化,官方并没有直接推荐在 setup 逻辑中获取 globalProperties 的 Composition API 钩子。不过,在需要获取它们时,可以使用 getCurrentInstance
<script setup>
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
const globalProps = instance.appContext.config.globalProperties;
// 在 setup 逻辑中访问
const appManager = globalProps.$appManager;
const reconnect = () => {
appManager.reconnectManually();
}
// 模板中仍可直接访问 $appName
</script>
<template>
<div>
<h1>Welcome to {{ $appName }}</h1>
<button @click="reconnect">Reconnect (from script setup)</button>
</div>
</template>
注意
在 Vue 3 中,官方更推荐使用 provide/inject 或 Pinia 来传递依赖,而不是依赖 globalProperties,因为 globalProperties 会使组件的依赖关系变得隐式和难以追踪,尤其是在大型项目中。
1.1.5.1 监听对象中某个属性的变化¶
<template>
<div>
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<button @click="changeName">改变值</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
setup(){
const obj = reactive({
name:'zs',
age:14
});
const changeName = () => {
obj.name = 'ls';
};
watch(() => obj.name,() => {
console.log('监听的obj.name改变了')
})
return {
obj,
changeName,
}
}
}
</script>
watchEffect 会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听
1.1.6¶
| 名称 | 作用 |
|---|---|
| app.provide() | 提供一个可以在所有descendant组件使用的值 |
<KeepAlive> | 缓存组件实例 |
watchEffect() | 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。 |
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()
toValue() 是 3.3 版本引入的新的 API, 和 unref 类似,但可以接受函数作为输入值
1.1.7 一个 Setup Context 主要包含什么¶
- attrs
- slots
- emit
- exposee
1.1.8 nextTick() 的作用是什么?¶
存在原因
Vue 当数据发生变化时,向 DOM 写入并不是变化时立即生效的,而是异步的。
主要是效率方面的考虑,使用这种机制可以将多次修改合并到一次 DOM 更新中,类似于显示原理中的帧缓存
nextTick() 的调用方式
- 在 SFC 中直接调用
- 在 Component 中通过 this.$nextTick() 调用
nextTick() 的参数及返回值
- 如果入参传入一个 callback,则该 callback 会下一次变化真正生效时调用
- 返回值是一个 Promise,且
可以配合
await nextTick()进行调用
1.1.9 组合式API (Hooks / Composables)¶
无渲染组件或者轻量级组件?
为了实现带状态处理逻辑的复用
- 一个 Composable 中可以嵌套其它的 Composable,以便组成更复杂的 Composable
- 一个 Component / View/ Page 中可以引用多个 Composable,以组成更复杂的 Component / View/ Page
- 官方的例子都是和 Lifecycle 事件关联的 onMounted / onUnmounted,相当于和引用方的触发点
参考
1.1.9.1 和 Mixin 的对比¶
1.1.9.2 和 无渲染组件的对比¶
1.1.9.3 和 React Hooks 的对比¶
1.1.10 国际化¶
1.1.11 ref() 和 reactive() 的区别有哪些¶
- ref() 不仅可用于 object, 也可以用于 primitives 类型,如 boolean, int 等;reactive() 仅可用于 object
- ref() 有一个 .value 属性,你必须使用 .value 属性获取内容(模板里可以自动 unwrap ),但是使用 reactive() 的话可以直接访问
- 使用 ref() 函数可以替换整个对象实例,但是在使用 reactive() 函数时就不行
某种程度上, reactive() 可以看做是一个限制版的 ref()
参考
- https://blog.logrocket.com/reactivity-vue-3-composition-api-ref-reactive/ ⧉
- https://juejin.cn/post/7235118809605308471 ⧉
- https://cloud.tencent.com/developer/article/2397580 ⧉