Vue 3 于 2020 年 9 月发布,是 Vue.js 的重大升级版本,主要改进包括性能优化、新特性引入和更好的 TypeScript 支持。
性能:打包大小减少41%、初次渲染快51%、更新渲染快133%,内存占用减少54%。
使用Proxy 代替Object.defineProperty 实现双向数据绑定,重写虚拟DOM的实现和Tree-Shaking
更好的 TypeScript 支持:源码使用 TypeScript 重写,提供完整的类型定义,开发体验更友好,IDE 提示更精准。
快速生成vue3项目,创建vue项目可以使用vue-cli(webpack)和vite(更推荐)
1.node版本要不低于18.3
2.npm create vue@latest 、 npm init vue@latest、npx create-vue@latest(等效 )
自动从 npm 仓库拉取最新版
create-vue工具包,临时安装到本地,然后按提示操作即可
Vue3 的组件可以按两种不同的风格书写:选项式 API (Vue2)和组合式 API(Vue3新增加的)
选项式 API (Options API-Vue2写法)(配置风格):数据、方法、计算属性等分散在data,methods,computed中,新增或修改需求时,需要分别修改data、methods、computed,不便于维护和复用;
组合式 API (Composition API):用函数的方式更优雅的组织代码,让相关功能的代码更加有序的组织到一起,且data、methods、watch等在同一个位置,比较好找,不需要像大的Vue2组件中,从data找到对应的methods必须搜索,要不很难找到;
Vue3的变化
从vue中引入createApp创建应用 createApp(App).mount(#app)
<script lang="ts"></script>代表使用ts语言,不写默认js
vue3组件中可以有多个根标签
setup函数中的this是undefined,vue3已经弱化this了
setup和vue2中的data、methods、watch可以同时出现在vue3中,但是非常不推荐,vue2中可以读取到setup中的数据,但是setup中无法读取data配置项的数据,因为setup执行的时机更早
setup语法糖:setup也是一个特殊的钩子,专门用于组合式 API。
// 在编辑器中新建一个vue文件,输入vue可快速生成vue模板
<script lang="ts" setup>
let name ='abc'
function changeName(){
name = 'aaa'
}
</script>
组件命名
组件使用的名字和实际文件名字不一致时,可以使用vue-setup-extend插件代替组件命名:<script lang="ts"> export default {name:'ABC'} </script>
1.npm i vite-plugin-vue-setup-extend -D
2.在vite.config.ts文件中,import VueSetupExtend from 'vite-plugin-vue-setup-extend'
在plugins配置中加上VueSetupExtend()
3.在<script lang="ts" setup name="ABC">
ref函数
声明响应式状态,使数据变成响应式,
ref()接收参数,并将其包裹在一个带有.value属性的 ref 对象中返回import { ref } from 'vue'
基本数据类型:const count = ref(0) function changeCount(){ count.value = +=1}
复杂数据类型:const obj = ref({name: 'a'}) function changeObj(){ obj.vaklue.name = 'b}
当在模板中使用时,ref 会自动解包,不需要附加
.value在script标签中需要.value操作数据,可引入volar插件自动添加value
<button @click="count++"> {{ count }} </button>
ref可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如
Map,但基本数据只能使用ref来创建响应数据ref 会使它的值具有深层响应性,即使改变嵌套对象或数组时,变化也会被检测到
ref在将复杂数据类型转换为响应式数据时,底层还是借助了reactive的能力
reactive()
reactive()把对象本身具有响应性,只能用于对象类型 (对象、数组和如Map、Set)。它不能持有基本数据类型import { reactive } from 'vue'
const obj = reactive({ count: 0 })
reactive重新分配一个新对象,会丢失响应式,可以使用Object.assign(obj,{count:1})去替换整体,ref不存在类似问题
function changeObj() {
obj = {a: '456'} // 不会生效,因为重新分配对象导致丢失响应式
Object.assign(obj, {a: '456'}) // 需要利用Object.assign更新
}
层级不深的对象,ref和reactive都可以
如果是层级较深的对象,如表单功能建议使用reactive
toRefs、toRef
obj是响应对象
let {a} = obj, 再对a操作也不会响应式(直接解构的数据不是响应数据)
因为const {a} = obj相当于 let a=obj.a,
响应数据变量obj并没有变化,而新定义的a并不是响应式数据
let {a} = toRefs(obj)
toRefs可把解构出来多个值变成响应式,相当于使用了ref,解构出来的值需要使用.value来操作
let a2 = toRef(obj, ‘a’)
toRef可解构单个值,并把解构出来的值变为响应式,也需要使用.value来操作,使用场景较少
计算属性computed
import { comouted } from 'vue'
// 1.此种计算属性只可读取,不可修改
let fullName = computed(()=>{
return name1.value + name2.value
})
// 2.此种写法,可读可修改
let fullName = computed({
get() {
return name1.value + name2.value
}
set(value){
name1.vuale = value
}
})
watch和wactchEffect
import { watch, ref} from 'vue'; // 支持使用数组监听多个数据
let name = ref('小明')
// 1.监听ref创建的基本响应数据不可以加value
watch('name', (new, old)=>{
if(new === '张飞'){
stopWatch() // 可以停止监听
}
})
// 2.监听ref创建的对象响应数据,监听的对象地址,所以需要开启深度监听
watch('name', (new, old)=>{
// 修改的是ref响应对象的属性时,new===old,因为是同一个对象,所以old不能使用
// 若修改的是整个ref定义的对象,那么不再是同一个值了,old可以使用
if(new === '张飞'){
stopWatch() // 可以停止监听
}
},deep: true)
3.监视reacive创建的响应数据,默认隐式的开启的深度监听,且old===new
4.监视ref或者reactive对象中的一个属性时:
如果这个属性是基本数据类型,需要写成函数式,因为watch只能监视响应式数据,ref、reactive、computed或函数返回值
如果这个属性是对象可以直接写,
建议还是写成函数式,再手动开启深度监听,因为watch监听的是地址
watch(()=>return obj.a, (new,old)=> {
// a为基本数据时,new !=b
// a为对象类型,须手动开启深度监听,因为watch监听的是函数返回的地址(建议此写法)
})
watch( obj.a, (new,old)=> {
// a为对象类型时,如果a被整体替换无法被监听到,具有局限性
})
wactchEffect自动监听函数中使用的数据变化
wactchEffect(()=>{
if(sum > 5) {console.log(sum)}
// 上来会先执行一次,自动监视逻辑代码中的所有变量
})
标签的ref
<div ref="active"></div>
import { ref } from 'vue'
let active = ref() // active.value就可以拿到这个目标元素
在组件中使用时,父组件想获取子组件的数据,需要在子组件中使用defineExpose来导出数据,共父组件使用
import {defineExpose} from 'vue'
defineExpose({a,b})
Ts使用
可以在src文件下新建一个type文件夹,再新建一个index.ts文件
定义一个接口类型
export interface PersionType {
name: string, // 都是小写
age: number,
}
export type Persons = PersionType[] // 创建一个自定义类型
导入的接口需要在前边加一个type,用来和普通的方法或数据做区分
目标文件中可以直接import {type PersionType} from '@/type'导入使用
let abj:PersionType = {name: '张三',age:19}
let arr: Array<PersionType> = [{name: '张三',age:60}]
实际使用:let list = reactive<Persons>([ {name: '张三',age:19}...])
setup中添加lang="ts"时,引入组件会报错 会报错Module '"/Users/zhuzhu/Desktop/vue3/vue-project/src/components/TheWelcome.vue"' has no default export.Vetur(1192)
这个问题是因为 TypeScript 无法正确识别
<script setup>的默认导出,而 Vetur(VSCode 的 Vue 插件)在 TypeScript 模式下会进行更严格的检查Vetur 对
<script setup>+ TypeScript 的支持不够完善,解决办法是:VSCode 扩展中禁用禁用 Vetur,安装 Volar,重启vscode
props:
父组件:
<Person a=123 />
Person子组件:
<h2>{{a}}</h2>
import { defineProps, widthDefaults } from 'vue' //defineProps是宏函数,可不引入直接使用
// 此时子组件的模板可以正常使用a变量了,但是在script标签中不可以使用
defineProps(['a'])
接受并保存才能在script标签中使用:
let x = defineProps(['a']) // 可以使用x.a来访问a
接收数据+限制类型 :defineProps<{list: Persons }>() 或 :defineProps<{list?: Persons }>()
接收数据+限制类型+置顶默认值: :widthDefaults(defineProps<{list?: Persons }>() ,{
a:123... // 当父组件传值为空时的兜底
})

生命周期
创建:setup 中的代码就是创建阶段执行的,所以在 Vue3 中没有针对创建阶段专门的回调函数,直接在 setup 中写就好了
挂载: onBeforeMount 、onMounted
更新:onBeforeUpdate、onUpdated
卸载:onBeforeUnmount、onUnmounted
onBeforeMount(() => { console.log("挂载前"); });
自定义hooks
Composition API 中的组合式函数(Composables),它的命名灵感来自 React Hooks,但实现机制完全不同。
在 Vue 3 中,Hooks 就是以 use 开头的函数,用于将可复用的状态逻辑抽离到独立文件中。它基于 Vue 3 的响应式系统(ref、reactive、computed、watch 等)和生命周期钩子(onMounted、onUnmounted 等)来封装逻辑。
举个简单的例子:
// useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
return { count, double, increment, decrement }
}
在组件中使用:
<script setup lang="ts">
import { useCounter } from './composables/useCounter'
const { count, double, increment } = useCounter(10)
</script>
它解决了 Vue 2 Options API 的几个痛点:
第一是逻辑分散问题。在 Options API 中,一个功能的 data、methods、computed、watch 分散在不同选项里,代码量大时很难看清一个功能的完整逻辑。Composition API 可以把相关逻辑聚合在一起。
第二是复用困难。Vue 2 的 mixins 有命名冲突、来源不清晰等问题。composable 函数的返回值是显式的,不存在命名冲突,来源一目了然。
第三是 TypeScript 支持。Options API 的 this 类型推断很困难,而 composable 函数就是普通函数,天然对 TS 友好。
和 React Hooks 的核心区别: Vue 的 composable 只在 setup 阶段执行一次,之后响应式系统自动追踪依赖和触发更新;而 React Hooks 在每次组件渲染时都会重新执行,需要手动管理依赖数组(useEffect 的 deps)。所以 Vue 不存在 React 中闭包陈旧值、无限渲染循环等问题。
你工作区中的 vue3-calendar-notes 项目里就有实际的例子,比如 src/composables/useCalendar.ts 和 useDragSort.ts,都是典型的 Vue 3 Hooks 用法。
可以在
src下新建hooks文件夹,在其中创建单独的.js或.ts文件,将功能拆分到单独的文件中。
![]()
路由:使用路由可以通过不同的URL路径来访问不同的页面组件,而无需重新加载整个页面。
安装vue-route:npm install vue-router
router/index.tsimport { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') } ] }) export default router
main.js中引入路由配置:import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' // 引入路由 import router from './router' const app = createApp(App) // 使用路由 app.use(router) app.mount('#app')路由组件通常放在pages文件夹或views文件夹,一般组件放components文件夹
导航区to:可以使用字符串或对象
<router-link active-class="active" to="/home">主页</router-link>
<router-link active-class="active" :to="{path: '/home'}">主页</router-link>
展示区:
<RouterView></RouterView>
嵌套路由的子级不要写/,
在 Composition API 中不能直接使用
this来访问this.$router,因为 Composition API 是基于函数的,不使用基于类的组件结构。相反,你应该使用setup函数来设置组件,并通过useRouter钩子来获取路由实例。import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/home');
query传参:
获取:
import { useRouter, toRefs } from 'vue-router'
let { route } useRouter()
let { query } = toRefs(route) // 此时可以通过query.id读取参数了
params传参:(使用to的对象写法,必须使用name配置不能使用path,还必须提前在规则中占位)
<router-link active-class="active" to="/news/datail/hh/ss/pp">主页</router-link>
import { useRouter, toRefs } from 'vue-router'
let { route } useRouter()
let { parms } = toRefs(route) // 此时可以通过parms.id读取参数了
push会增加历史记录,replace不会增加记录(不能前进回退)
Pinia状态管理(支持vue2/3)

Vuex 有四个核心概念:state(数据)、getters(派生数据)、mutations(同步修改)、actions(异步操作)。修改数据必须通过 mutation,链路是 组件 → dispatch action → commit mutation → 修改 state。这套流程保证了可追踪性,但也带来了大量模板代码。
Pinia 简化为三个概念:state、getters、actions。去掉了 mutation,action 里可以直接修改 state,也可以是异步的。每个 store 是独立的,不需要像 Vuex 那样通过 modules 嵌套。支持 Options API 和 Composition API 两种写法。
Redux 核心是 store(单一数据源)、action(描述发生了什么)、reducer(纯函数,接收旧 state 和 action 返回新 state)。修改数据必须 dispatch 一个 action,reducer 里不能有副作用,必须返回新对象(不可变数据)。异步需要中间件(redux-thunk、redux-saga)。Redux Toolkit(RTK)是官方推荐的封装,大幅减少模板代码。
MobX 核心是 observable(可观察状态)、computed(派生值)、action(修改状态的方法)、reaction(副作用)。直接修改数据即可触发 UI 更新,不需要手动 dispatch,也不需要返回新对象。原理是通过 Proxy 自动追踪哪些组件用了哪些数据,做到精准更新。
npm i pinia或yarn add pinia // 在main.js文件中挂载到实例对象上
import { createApp } feom 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
pinia定义store // src/stores/count.ts文件
import { defineStore } from 'pinia'
在 Setup Store 中:
ref()就是state属性computed()就是gettersfunction()就是actions标准用法:
interface State {
userList: UserInfo[]
user: UserInfo | null
}const useStore = defineStore('storeId', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})interface UserInfo {
name: string
age: number
}方式2:
export const useCounterStore = defineStore('counter',{
state(){ // 真正存储数据的地方
return {
list:[]
}
}
})
方式3:
export const useCounterStore = defineStore('counter',{
const count = ref(0)
function increment(){
count.value++
}
return {count, increment}
})
方式4:复杂使用
import { defineStore } from 'pinia'
export const useTodos = defineStore('todos', {
state: () => ({
/** @type {{ text: string, id: number, isFinished: boolean }[]} */
todos: [],
/** @type {'all' | 'finished' | 'unfinished'} */
filter: 'all',
// 类型将自动推断为 number
nextId: 0,
}),
getters: {
finishedTodos(state) {
// 自动补全! ✨
return state.todos.filter((todo) => todo.isFinished)
},
unfinishedTodos(state) {
return state.todos.filter((todo) => !todo.isFinished)
},
/**
* @returns {{ text: string, id: number, isFinished: boolean }[]}
*/
filteredTodos(state) {
if (this.filter === 'finished') {
// 调用其他带有自动补全的 getters ✨
return this.finishedTodos
} else if (this.filter === 'unfinished') {
return this.unfinishedTodos
}
return this.todos
},
},
actions: {
// 接受任何数量的参数,返回一个 Promise 或不返回
addTodo(text) {
// 你可以直接变更该状态
this.todos.push({ text, id: this.nextId++, isFinished: false })
},
},
})
pinia使用
import { storeToRefs } from 'pinia'
import {useCountStore} from '@/store/count'
const countStore = useCountStore()
const {count, increment} storeToRefs(countStore)
counter.count++ // 自动补全!
counter.$patch({ count: counter.count + 1 })
// 或使用 action 代替
counter.increment()
自定义事件(子传父)





3433

被折叠的 条评论
为什么被折叠?



