从vue2过渡到vue3
1. 响应式基础
1.1 基础语法
- ref()
import { ref } from 'vue'
// 基本类型
const count = ref(0)
// 对象类型
const obj = ref({ name: 'Vue' })
// 访问/修改
console.log(count.value) // 0
count.value++
- reactive()
import { reactive } from 'vue'
// 只能处理对象类型
const state = reactive({
count: 0,
user: { name: 'John' }
})
// 访问/修改
console.log(state.count) // 0
state.count++
1.2 特性
特性 | ref() | reactive() |
---|---|---|
适用类型 | 所有类型 | 仅对象/数组 |
访问方式 | 需要 .value | 直接访问属性 |
模板自动解包 | 自动解包(无需 .value ) | 直接使用属性名 |
TypeScript 支持 | 更好的类型推断 | 复杂对象类型需要明确类型定义 |
响应式保持 | 始终保持引用 | 替换整个对象会丢失响应式 |
1.3 典型使用场景
1. ref() 更适合:
- 基本类型数据(string/number/boolean)
- 需要保持引用的响应式对象
- 需要传递到组件外部的响应式值
- 需要直接替换整个对象时
2. reactive() 更适合:
- 复杂对象结构(嵌套对象/数组)
- 局部状态管理(不需要暴露到组件外)
- 需要直接操作属性的对象
- 需要自动深度响应式的场景
2. 注册
2.1 全局注册
全局注册可以在当前Vue应用全局可用
// src/plugins/myPlugin.js
import MyComponentA from '../components/MyComponentA.vue';
import MyComponentB from '../components/MyComponentB.vue';
export const MyPlugin = {
install(app, options = {}) {
// 全局注册组件
app.component('MyComponentA', MyComponentA);
app.component('MyComponentB', MyComponentB);
// 根据选项自定义行为
if (options.customDirective !== false) {
app.directive('focus', {
mounted(el) {
el.focus();
}
});
}
// 可以添加更多基于选项的功能
}
}
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import MyPlugin from './plugins/myPlugin';
const app = createApp(App);
// 使用插件并传入配置项
app.use(MyPlugin);
app.mount('#app');
2.2 局部注册
局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<component-a />
</template>
3. props
3.1 语法
使用defineProps宏函数声明
<script setup>
const props = defineProps({
title: String,
likes: Number
})
console.log(props.foo)
</script>
3.2 命名格式
在setup中推荐 camelCase。
<script setup>
const props = defineProps({
greetingMessage: String
})
</script>
在模板中推荐使用kebab-case。
<template>
<MyComponent :greeting-message />
</template>
3.3 props校验
defineProps({
title: {
type: String,
default: '',
},
likes: {
type: Number,
required: true
},
interest: {
type: Object,
default: () => {}
}
})
4. emit
4.1 语法
<script setup>
const emit = defineEmit(['submit', 'login'])
const submit = () => {
emit('submit', 'hello')
}
</script>
5. pinia
5.1 安装pinia
pnpm install pinia
创建pinia实例,并且使用
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
5.2 定义store
在setup下的store
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
useCounterStore是在其他组件引用的的名字
import { useCountStore } from '@/store/count.js'
useCounterStore这个返回函数的名称是通常是use开头Store结尾。
counter是这个仓库的唯一Id,Pinia 将用它来连接 store 和 devtools。
ref()
就是state
属性
const count = ref(0),count就是这个仓库存的数据computed()
就是getters
就是计算属性,只完成计算逻辑function()
就是actions
在这里完成同步或者异步的逻辑书写
5.3 使用store
5.3.1 直接使用
在setup中,可以直接使用
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `countStore` 变量 ✨
const countStore = useCounterStore()
const addCount = computed(()=> countStore.doubleCount)
</script>
5.3.2 从Store解构
如果从Store中直接解构变量和计算属性,它们会失去响应式特性,为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()
。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>