主题
了解 Vue3与了解相关信息(Lesson23 1/2)
本页对应 Lesson23,共 7 段视频,约 2 小时 8 分。
学习目标
- 能说出 Vue3 与 Vue2 的核心差异,并至少列举 3 项 Vue3 新增特性或性能优化点。
- 能使用脚手架创建 Vue3 项目,识别并说明
src/main.ts、App.vue、index.html等关键文件的作用。 - 能对比 Vue3 脚手架与 Vue2 在入口文件、根组件创建方式、模板根节点数量、
defineComponent等方面的书写差异。 - 能在 Vue3 项目中沿用 Vue2 的
data、methods、computed、指令和生命周期等语法完成基础页面交互,并指出不支持的 Vue2 语法。 - 能说明
setup的执行时机、返回值要求及与data的优先级和响应式差异,并使用setup返回模板所需数据。 - 能使用
ref创建响应式数据,在模板中自动解包、在setup中通过.value读写,并解释Object.defineProperty中get/set的拦截行为。
知识点
认识 Vue3 与本节目标
为什么需要:Vue3 是 Vue 的第三个大版本。转写稿里反复强调,Vue3 和 Vue2 的关系类似 ES6 与 ES5:Vue3 中仍可写部分 Vue2 语法,但官方更推荐组合式 API。学习前先明确目标:重点掌握组合式 API,而不是把官网所有新特性一次看完。
怎么想:先区分“选项式 API”和“组合式 API”。选项式 API 是 Vue2 常见写法,在对象里写 data、methods、computed 等;组合式 API 是按需引入方法,在 setup 中组织逻辑。官网默认文档已是 Vue3,并提供 API 风格偏好选择,官方建议组合式 API。
怎么做:打开官方文档 https://cn.vuejs.org/,在“快速上手”中确认当前阅读的是 Vue3 文档。转写稿提到,组合式 API 可按需引入,有利于减小打包体积、减少依赖文件,从而提升运行效率。
易错点:不要以为学 Vue3 就完全不能写 Vue2 语法;也不要混着用。转写稿建议:选择 Vue3 开发,就尽量使用 Vue3 语法。
脚手架与入口文件差异
为什么需要:Vue2 常结合 vue-cli,Vue3 可用 Vite,也支持 vue-cli。需要先创建项目并找到入口,才能理解后续差异。
怎么想:转写稿演示创建项目时,在默认 Vue3 基础上增加 TypeScript,并按需选择 Router、Vuex、ESLint 等;一路回车安装。启动后用 npm run serve 或脚本启动,终端会给出本机访问地址。
怎么做:Vue3 入口 main.ts 不再 new Vue,而是:
ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')转写稿调试时,先写 createApp() 不传根组件,报错提示必须指定根组件;传入 App 后才正常。createApp(App) 返回应用对象,.mount('#app') 把根组件挂到 index.html 中 id 为 app 的容器。
易错点:createApp 必须传根组件;在 main.ts 中引入 .vue 文件时,转写稿演示不能省略 .vue 扩展名。Vite 配置仍在项目根目录,server.open、端口、代理等配置与原来思路相同。
Vue3 中写 Vue2 语法与 setup
为什么需要:实际项目可能从 Vue2 迁移,需要知道哪些 Vue2 写法还能用。转写稿演示了 data、methods、computed、v-if、v-for、组件引入、挂载和更新生命周期等仍可使用;但演示到销毁生命周期时指出不支持。
怎么想:Vue3 的 setup 是组合式 API 的入口,执行时机比 beforeCreate 还早。它返回对象中的属性可直接在模板使用。
怎么做:
ts
export default defineComponent({
setup() {
return { name: '张三', a: 10 }
}
})转写稿调试:setup 返回数字会警告“应返回对象”;写成 async 会返回 Promise,也不合适;setup 与 data 同名时,以 setup 为准;但 setup 中普通数据默认不是响应式,改数据不会推动视图更新。
易错点:setup 不能写成 async;要返回对象;不要把 setup 数据当响应式数据直接改视图。
ref 的基本使用
为什么需要:setup 普通数据不响应,需要响应式组合 API。ref 是转写稿重点演示的响应式 API。
怎么想:ref 是函数,接收任意类型数据,返回一个拥有 value 属性的 RefImpl 实例。模板中会自动解包,可直接写 count;但在 setup/脚本中必须写 count.value。
怎么做:
ts
import { ref } from 'vue'
setup() {
const count = ref(100)
const user = ref({ name: '张三', age: 12 })
function change() {
count.value = 200
user.value.name = '李四'
}
return { count, user, change }
}转写稿调试:模板里写 count 能显示 100;脚本里直接比较 count === 100 不成立,因为 count 是实例,值在 count.value。ref 接收对象时,value 是 Proxy;接收基本类型时,value 是基本值。
易错点:模板自动解包,脚本必须 .value;不要拿 ref 实例和原始值直接比较。
响应式底层:defineProperty 的 get/set
为什么需要:理解 ref 响应式底层,转写稿先复习 Object.defineProperty。它可拦截对象属性的读取和设置。
怎么想:Object.defineProperty(obj, key, descriptor) 接收对象、属性名、描述对象。get 在读取时执行,返回什么,读取结果就是什么;set 在赋值时执行,可接收新值。
怎么做:
js
let value = obj.username
Object.defineProperty(obj, 'username', {
get() {
return value
},
set(v) {
value = v
}
})转写稿调试:只写 get 返回 '阿迪' 时,输出 obj.username 得到 '阿迪';比较、复制、传参都会触发 get。写 set 后赋值会触发 set,但读取仍由 get 决定;因此要用闭包变量保存新值,再在 get 中返回,才能读到最新值。
易错点:get 的返回值就是读取结果;set 里不保存值,后续读取仍是旧值;ref 接收对象时底层会用到 Proxy,转写稿说明这部分后续再展开。
先建立Vue3的版本坐标
为什么需要:很多同学一上来把Vue3当成“全新框架”,结果既不敢用Vue2写法,又分不清组合式API和选项式API。转写稿里讲师先梳理版本:Vue1用的人很少,Vue2在17—19年前后带动了Vue火起来,Vue3推出后也需要时间沉淀,去年10月左右用Vue3的人才陆续多起来;目前Vue2和Vue3大概1比1,较真说Vue2稍多一点。面试时简历写了Vue2和Vue3,常会被问两者区别,所以要先建立版本坐标。怎么想:把Vue3理解成Vue的第三个大版本,类似ES5到ES6,不是推翻Vue2;Vue3支持Vue2的大多数特性,但生命周期等有变化。怎么做:学习时以组合式API为主线,同时知道Vue3里仍可写Vue2语法;但建议不要混着用。易错点:不要把“Vue3支持Vue2大多数特性”理解成“全部支持”;也不要在Vue3项目里无脑混写选项式和组合式。
用贡献数据判断生态成熟度
为什么需要:判断一个技术是否值得学、公司项目能不能用,不能只看宣传,要看社区活跃度和迭代情况。怎么想:笔记给出“2年多开发、100+位贡献者、2600+次提交、600+次PR(Pull Request)”,说明Vue3不是刚出的试验品,已经经过较多贡献者参与和提交迭代;官网也已默认Vue3文档,API风格偏好默认偏向组合式API。怎么做:看官网时注意“当前默认版本”是Vue3,学习重点放在组合式API;转写稿说官方建议使用组合式API,因为选项式API是在组合式API基础上处理的。易错点:不要因为数据好看就认为Vue2马上淘汰;转写稿明确目前Vue2仍稍多。也不要把“贡献者多”当成“所有API都要学”,先抓常用组合式API。
兼容Vue2大多数特性,但要认清边界
为什么需要:实际项目迁移和面试都会问“Vue3能不能写Vue2”。怎么想:像ES6里还能写ES5一样,Vue3里可以写data、methods、computed、v-if、v-for、@click等;但生命周期有一部分改变。怎么做:转写稿在App.vue里写data返回name和arr,写methods改name,模板用{{name}}、v-for、v-if、@click,验证可运行;再测beforeCreate等生命周期,发现部分可用,但Vue2的销毁生命周期在Vue3里不支持。转写稿还演示了Vue3模板可有多个根节点。易错点:不要以为“大多数支持”等于“全部支持”;不要在Vue3里混用选项式和组合式;多个根节点是Vue3特性,不是Vue2语法。
TypeScript支持:从脚手架到代码约束
为什么需要:TypeScript能对类型进行约束,让代码更严谨,Vue3对TS支持更好。怎么想:创建项目时可在默认基础上勾选TS;入口从main.js变main.ts,挂载方式从new Vue()变为createApp()。怎么做:转写稿中main.ts核心代码是:
ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')App.vue里可用defineComponent,setup返回对象;ref可写ref<number>(0)。易错点:转写稿提醒main.ts中引入组件不能省略.vue扩展名;defineComponent主要帮助类型推断,可省略;setup中返回ref在模板自动解包,但在setup内必须写.value;setup不能写成async,否则返回Promise会警告;setup返回对象与data同名时以setup为准;setup数据默认不是响应式,需用ref/reactive。
调试小实验:入口、setup与ref/defineProperty
为什么需要:只看结论容易忘,跟着转写稿的调试过程能理解“为什么”。怎么想:先保存原脚手架,再改main.ts,关闭ESLint,配置devServer的open,创建App组件,用createApp(App).mount('#app')挂载到#app;然后观察ref(0)返回RefImpl实例,值在.value,模板中直接写count。转写稿还用Object.defineProperty演示get/set:读取时调用get,返回get的返回值;设置时调用set,set只接收新值。怎么做:写一个setup小例子:
ts
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
}
}易错点:转写稿中设置userName为“李四”后读取仍是100,因为get里返回固定100;要用变量保存值并在get中返回变量。ref接收对象时value会是Proxy,基本类型则不是;setup内写count.value,模板写count,不要混淆。
先看结论:Vue3 相对 Vue2 的性能数据
本节笔记给出的核心结论是:打包大小减少 41%,初次渲染快 55%,更新渲染快 133%,内存减少 54%,并且 Vue3 重写了虚拟 DOM 的实现,同时支持 Tree-Shaking。
为什么需要:学习 Vue3 不能只记“新语法”,还要知道它为什么值得升级。面试中常问 Vue2 与 Vue3 的区别,项目选型也需要能说清收益。
怎么想:先把这组数据理解为“相对 Vue2 的整体收益”,不要把它当成每个页面、每个组件都必然完全相同的固定倍数。转写稿中讲师反复强调,Vue3 依然可以写 Vue2 的选项式语法,但官方更推荐组合式 API;性能收益要和写法、构建方式一起看。
怎么做:记住五项关键词:包体积、初次渲染、更新渲染、内存、虚拟 DOM 与 Tree-Shaking。后续学习时,把“组合式 API 按需引入”与“打包变小”联系起来,而不是孤立背数字。
易错点:不要把“快 55%”“快 133%”理解成所有业务代码无条件提速;也不要用开发环境的热更新速度去推断生产构建性能。
性能提升从哪里来:组合式 API 的按需引入
为什么需要:Vue2 的选项式 API 通常把 data、methods、computed 等写在一个对象里交给 Vue;Vue3 的组合式 API 允许按需引入能力,减少不必要的依赖进入打包结果。
怎么想:转写稿给出一条很清楚的推导链:组合式 API 是“按需引入”的,按需引入可以降低所引入包的大小;包变小后,执行时依赖的文件减少,运行效率自然提升。讲师还提到,选项式 API 最终也会在组合式 API 的基础上处理,所以学习重点应放在组合式 API 上。
怎么做:在 Vue3 中常见的写法是按需引入,例如转写稿中出现的:
js
import { ref } from 'vue'创建应用时也不再是 new Vue,而是使用 createApp:
js
import { createApp } from 'vue'
createApp(App).mount('#app')学习时先明确:Vue3 的重点是组合式 API;项目里尽量按需引入,不要把所有能力一次性挂到全局。
易错点:Vue3 支持部分 Vue2 语法,不等于“继续全写 Vue2 就能拿到全部性能收益”;也不建议在同一项目里随意混用两套写法。转写稿明确说“不建议混着用”。
重写虚拟 DOM 实现与 Tree-Shaking
为什么需要:初次渲染、更新渲染和内存占用,主要受运行时实现影响;打包体积则与模块组织和 Tree-Shaking 有关。Vue3 重写虚拟 DOM 实现,是为了让渲染过程更高效;Tree-Shaking 则用于删除未被使用的模块导出。
怎么想:不要只把 Tree-Shaking 理解成“自动瘦身”。它依赖 ES 模块的静态结构,项目写法越静态、副作用越少,构建工具越容易判断哪些代码可以删除。转写稿中讲师的思路是:组合式 API 按需引入,配合 Tree-Shaking,才能更好地降低包体积。
怎么做:项目中使用生产构建观察产物;业务代码按需引入;避免无意义的全局注册和副作用代码。学习阶段可以先关注“我引入了什么、是否真的用到了”。
易错点:Tree-Shaking 不是万能的,动态调用、副作用代码会影响删除效果;开发构建的包体积不能直接代表生产构建结果;不要为了追求体积把所有代码拆得过度零碎。
把性能数据落到学习与项目验证
为什么需要:只背结论容易忘,也容易在面试中被追问“为什么”。转写稿中讲师建议先明确学习目标:Vue3 主要学组合式 API,官方也默认推荐组合式 API。
怎么想:把性能提升拆成两条线:一条是“写法线”——组合式 API 按需引入;一条是“实现线”——重写虚拟 DOM、Tree-Shaking。两条线共同影响包大小、渲染速度和内存。
怎么做:创建 Vue3 项目后,先按组合式 API 写一个小页面;再用生产构建对比引入方式变化带来的产物差异;调试时重点看“是否按需引入”“是否混用旧写法”“是否把未用代码带入包中”。
易错点:不要编造具体 benchmark 命令和额外数据;本节笔记只给了上述对比结论。真正做项目时,应结合自己的构建产物和页面场景判断,而不是把课程数据当成绝对承诺。
小结
Vue3 的性能提升可以记成一句话:组合式 API 按需引入降低包体积,重写虚拟 DOM 提升渲染效率,Tree-Shaking 进一步减少无用代码,最终体现为打包更小、初次渲染和更新渲染更快、内存更少。 学习时把重点放在组合式 API,项目中避免混用,并用生产构建验证,而不是只背五个数字。
本节笔记列出:Composition API(组合 API)、setup、ref 和 reactive、computed 和 watch、新的生命周期函数、自定义 hooks 函数。转写稿的重点落在“Vue3 为什么推荐组合式 API”“setup 怎么用”“ref 怎么实现响应式”,以及用 Object.defineProperty 推导响应式读写过程。下面按这条线索整理成教程正文。
先理解组合式 API 与选项式 API 的差别
为什么需要: Vue2 的选项式 API 把 data、methods、computed 等分门别类写在对象里,小项目清晰,但逻辑一多,同一功能的代码会被拆到不同选项里。Vue3 推出组合式 API,是为了让相关逻辑能集中组织,并且可以按需引入。
怎么想: 转写稿用 ES5 和 ES6 类比 Vue2 和 Vue3:Vue3 里仍可写部分 Vue2 语法,但官方默认更推荐组合式 API。组合式不是“换一种对象写法”,而是从 vue 中按需 import 方法,在 setup 里组织逻辑。
怎么做: 入口不再是 new Vue(...),而是 createApp 创建应用实例,再挂载容器:
js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')易错点: 不要把 createApp 当成 new Vue 的简单替换;转写稿调试时明确,createApp 必须接收根组件,返回的是应用对象,不是以前的 Vue 实例。Vue3 可以兼容部分 Vue2 语法,但不建议在一个项目里混着写。
setup 是组合式 API 的入口
为什么需要: 组合式 API 需要一个统一入口来声明数据、函数和后续的响应式逻辑,这个入口就是 setup。
怎么想: 转写稿通过打印顺序验证:setup 在 beforeCreate 之前执行。它返回一个对象,对象里的属性可以直接给模板使用。
怎么做: 可以先写最朴素的 setup:
js
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
const name = '张三'
return { name }
}
})易错点: 转写稿现场验证了几个坑:setup 可以不返回,但一旦返回,必须是对象;不能写成 async,否则返回的是 Promise;setup 返回的普通数据不是响应式的;如果 data 和 setup 返回同名属性,以 setup 为准;setup 里不要依赖 Vue2 的 this 写法。
ref 的基本使用与模板解包
为什么需要: setup 返回的普通变量改值后不会驱动视图,所以需要 ref 把数据变成响应式。
怎么想: 转写稿通过控制台输出看到,ref(100) 返回的是 RefImpl 实例,值放在 .value 里。模板中 Vue 会自动解包,所以模板写 count;但在 setup/JS 里必须写 count.value。
怎么做:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(100)
const obj = ref({ userName: '张三', age: 12 })
function change() {
count.value += 1
obj.value.userName += '!'
}
return { count, obj, change }
}
}模板中可直接使用:
html
<button @click="change">{{ count }} {{ obj.userName }}</button>易错点: 模板里不要写 count.value;JS 里不要漏掉 .value;ref 可接收数字、字符串、对象等任意类型。转写稿还验证了:ref 接收对象时,.value 会被代理成 Proxy,所以 JS 中操作对象内部属性仍要经过 .value。reactive 在转写稿中只点到“对象响应式/代理”这一层,具体用法应结合后续示例,不要在本节硬套。
用 Object.defineProperty 理解 get 和 set 的调试过程
为什么需要: 理解响应式底层,才能明白“为什么改值视图会变”“为什么有时改了却没反应”。
怎么想: 转写稿用 Object.defineProperty 演示拦截:读取属性走 get,赋值走 set。get 返回什么,外部读到的就是什么;set 收到新值后要保存,get 再返回保存值,读写才能联动。
怎么做:
js
const obj = { userName: '张三' }
let value = obj.userName
Object.defineProperty(obj, 'userName', {
get() {
return value
},
set(v) {
value = `《${v}》`
}
})易错点: 如果 get 里写死返回值,外部读到的永远是写死值;set 里收到新值但不在 get 中返回,读取就不会联动。转写稿提醒:Vue2 响应式常用 Object.defineProperty;Vue3 对对象会使用 Proxy 代理,这是后续理解 reactive 和 ref 对象值的关键。
computed、watch、生命周期与自定义 hooks 的位置
为什么需要: 笔记把 computed、watch、新的生命周期函数、自定义 hooks 列为组合式 API 的后续重点;它们分别解决派生状态、副作用监听、生命周期时机和逻辑复用。
怎么想: 转写稿没有展开 computed、watch 和自定义 hooks 的具体代码,因此本节不编造用法。先明确:它们和 setup、ref 一样,属于组合式 API 体系,需要按需引入、在 setup 中组织。转写稿还验证了 Vue3 仍支持部分 Vue2 生命周期,但销毁相关生命周期不能照旧写,需按 Vue3 的新生命周期处理。
怎么做: 先把 setup + ref 练熟,再学 computed/watch;自定义 hooks 的本质是把 setup 中可复用的响应式逻辑抽成函数。易错点: 不要用选项式思维硬套组合式;不要把普通工具函数和依赖响应式的 hooks 混为一谈;生命周期不要混写新旧名字。
先明确本节笔记的范围
本节笔记列了三个新增点:Teleport、Suspense、全局API的修改。需要先说明:转写稿主要讲的是 Vue3 入门、脚手架、setup、ref 以及用 Object.defineProperty 推导响应式,并没有展开 Teleport 和 Suspense 的具体语法。因此下面不编造这两个特性的代码,只按笔记做定位;全局API修改则结合转写稿里的 createApp 推导。
Teleport:瞬移组件的位置
为什么需要:弹窗、模态框、全屏遮罩这类组件,逻辑上属于当前组件,但 DOM 如果留在父级里,容易被父级的 overflow、z-index、定位上下文影响。
怎么想:组件逻辑仍写在原组件中,只把渲染出来的 DOM“瞬移”到更合适的位置,例如 body 下。
怎么做:本节笔记只给出“Teleport - 瞬移组件的位置”这一句,转写稿未展开具体写法,所以这里不写未出现的代码。学习时先记住它的定位:解决“组件位置”和“DOM位置”不一致的问题。
易错点:不要把它当路由或状态管理;目标容器必须存在;样式作用域、事件冒泡仍要按真实 DOM 结构考虑。
Suspense:异步加载组件的 loading 界面
为什么需要:异步组件加载需要时间,如果不处理,页面会出现白屏或内容突然跳变。
怎么想:把异步依赖包在 Suspense 里,在等待期间展示备用内容,也就是笔记说的 loading 界面。
怎么做:转写稿没有给出 Suspense 的模板代码,因此这里只保留笔记定位,不补造 API。实际写法应以后续官方文档或课堂代码为准。
易错点:fallback 是等待界面,不是完整的请求状态管理;异步组件要真正返回 Promise;错误处理不能只靠 loading 界面兜底。
全局API修改:从 new Vue 到 createApp
为什么需要:Vue2 里常见 new Vue({...}),全局配置容易和实例绑在一起;Vue3 把“创建应用”和“挂载”拆开,便于多应用隔离。
怎么想:先创建应用实例,再指定根组件,最后挂载到容器。转写稿中反复强调:createApp 是一个函数,必须传根组件,返回的是应用对象,不再是 new Vue 得到的实例。
怎么做:
js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')转写稿的调试过程是:先输出 createApp,确认它是函数;调用时不传根组件会报错;传入 App 后得到应用对象;再 .mount('#app') 挂到 index.html 中 id 为 app 的容器。
易错点:不要再写 new Vue;根组件不能漏;mount 的选择器对应容器必须存在;入口文件里引入组件时路径和扩展名要写清楚。
setup 与 ref:组合式API的入口
为什么需要:Vue3 重点学习组合式API,而 setup 是组合式API的入口。转写稿明确说:所有组合式API要在 setup 里配置。
怎么想:需要什么就按需引入什么,例如 ref 从 vue 中引入;setup 返回的对象属性可直接给模板使用。
怎么做:
js
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
function add() {
count.value++
}
return { count, add }
}
})模板里可直接写 count,因为 setup 返回的 ref 在模板中会自动解包;但在 setup 的 JS 逻辑里必须写 count.value。
易错点:setup 要返回对象;不要写成 async,否则返回 Promise 会报警告;data 与 setup 同名时以 setup 为准;setup 里的普通变量不是响应式,要用 ref 等API。
用 defineProperty 理解 get/set 调试
转写稿用 Object.defineProperty 演示读取和设置拦截,帮助理解响应式底层:
js
const obj = { userName: '张三' }
let value = obj.userName
Object.defineProperty(obj, 'userName', {
get() {
return value
},
set(v) {
value = v
}
})
console.log(obj.userName) // 张三
obj.userName = '李四'
console.log(obj.userName) // 李四怎么想:读取 obj.userName 走 get,设置走 set;如果 get 不返回、set 不保存值,就会得到 undefined 或仍旧值。
易错点:不要在 get/set 里直接写 obj.userName = obj.userName,容易递归;ref 对原始值通过 .value 存取,对对象值会生成 proxy,转写稿提到这一点,后续再展开。
安装入口与本节目标
本节笔记只给出一条安装参考:
https://blog.csdn.net/hebiwen95/article/details/126268585
转写稿里反复强调:装插件不是为了“图省事”,而是为了让 VSCode 在写 Vue3、TS 时能给出语法高亮、代码提示和类型检查。先把工具装好,再手写代码验证,才知道提示为什么出现、报红为什么出现。
为什么需要:Vue3 项目里会同时出现 .vue、main.ts、setup、ref 等内容,没有插件时编辑器对模板和 TS 的提示很弱。
怎么想:插件是“辅助理解”的工具,不是替代手写练习的生成器。
怎么做:按上面链接安装;装完后新建 .vue 文件,观察是否有 Vue 语法高亮和 TS 提示。
易错点:不要装完插件就直接让插件一键生成整段模板,转写稿明确说“刚开始不要图省事,先自己慢慢写”。
插件提示与 TS 报红的调试
转写稿里有一个典型调试过程:在 script 上写 lang="ts" 后,再写 A: number = 1,编辑器会标红;把类型标注去掉或把 TS 写完整,报红就消失。这说明插件和 TS 检查是联动的。
为什么需要:TS 能约束类型,让代码更严谨;但初学阶段容易被类型报红打断。
怎么想:先判断报红是“语法错误”还是“类型未写完整”。
怎么做:保留一个最小入口文件理解结构:
ts
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')vue
<!-- App.vue -->
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return { name: '张三' }
}
})
</script>易错点:转写稿里提到 createApp 必须接收根组件;不要写成 Vue2 的 new Vue(...)。另外,setup 是组合式 API 的入口,返回对象里的属性才能在模板中直接使用。
不要混用 Vue2 与 Vue3 语法
转写稿用大量时间对比 Vue2/Vue3:Vue3 仍支持部分 Vue2 语法,但生命周期、实例化方式、setup、ref 都有差异。插件提示会把这些差异暴露出来。
为什么需要:面试和项目里常问 Vue2 与 Vue3 区别,混写会导致“看似能跑、实际不规范”。
怎么想:Vue3 项目优先用组合式 API;看到 new Vue、this.xxx 时要先确认是不是旧写法。
怎么做:在 setup 中按需引入:
ts
import { ref } from 'vue'
const count = ref(100)
// setup 中读取要 .value
console.log(count.value)模板中可直接写 count,因为 ref 在模板中会自动解包;但在 setup 内部必须写 .value。
易错点:setup 返回的对象如果被写成 Promise 会出问题;返回数据要用对象形式。转写稿还提醒:setup 中普通数据默认不是响应式的,需要 ref 等响应式 API。
插件安装后的练习建议
为什么需要:只看安装文章容易“装完就忘”。
怎么想:用一个小页面验证插件是否工作:模板高亮、TS 提示、保存时检查。
怎么做:先手写 main.ts 和 App.vue,再逐步加入 setup、ref;遇到报红先读提示,不要立刻关掉所有检查。转写稿里为了练习先关闭语法检查,是为了避免组件名单词数等规则干扰,但正式项目不应长期关闭。
易错点:插件不是万能的;它不会替你理解 createApp、setup、ref 的职责。安装链接只解决工具入口,真正要练的是转写稿里反复演示的“写一遍、看报错、再改对”的过程。
一、为什么需要先安装脚手架
为什么需要: 本节要创建 Vue 3 项目,手写目录和构建配置成本高,先用脚手架生成标准工程,后续才能专注写组件。转写稿里反复强调:先要有 vue create 这个命令,才能创建项目。
怎么想: 把脚手架理解为“项目生成器”。它不是业务代码,而是帮你把入口文件、依赖、构建脚本先搭好。转写稿提到先安装脚手架命令,安装后才能执行创建命令。
怎么做: 确认终端里能使用 vue create。本节笔记给出的核心命令是:
shell
vue create first其中 first 是项目名,也是生成目录名。
易错点: 不要在没有脚手架命令时直接执行 vue create;项目名按终端提示使用合法名称,转写稿演示用的是 first,不要随意改成带特殊字符的名字。
二、执行创建命令并选择手动安装
为什么需要: 默认预设不一定包含 TypeScript。本节要求“选择手动安装”,就是为了在默认基础上追加 TypeScript,而不是直接套用现成模板。
怎么想: 转写稿的思路是:先进入手动选择流程,保留默认已选的基础项,再额外勾选 TypeScript。也就是“在原来基础上选择 typescript”,不是从零全选。
怎么做: 执行:
shell
vue create first在终端出现选择方式时,选择“手动安装”。转写稿演示了用空格键勾选、用 A 键全选/反选;这里不要按 A 全选,只保留默认项并追加 TypeScript。
易错点: 不要选成 Vue 2 预设;不要因为想省事而全选,转写稿明确说“在默认安装的基础之上增加了 1 个 TS”。TypeScript 勾选后,后续选项一路回车采用默认即可。
三、一路回车完成依赖安装
为什么需要: 手动选择后还会出现若干配置询问。本节笔记要求“一路回车即可”,目的是减少无关配置,先得到可运行工程。
怎么想: 转写稿的调试过程是:选好 TypeScript 后不再逐项纠结,直接回车接受默认值,等待依赖安装完成。安装完成后终端会提示进入项目并启动。
怎么做: 按提示回车,等待安装。完成后按终端提示进入目录并启动:
shell
cd first
npm run serve易错点: 不要在中途随意改选项导致和笔记不一致;安装过程中不要重复执行创建命令;启动命令是 npm run serve,不是直接双击 index.html。
四、启动后访问地址与调试
为什么需要: 项目装好后要验证脚手架是否正常。转写稿演示了启动后终端会给出访问地址,但默认不自动打开浏览器。
怎么想: 终端通常会给 localhost 和 192.168.x.x 形式的本机地址。localhost 代表本机;局域网 IP 用于同一网络下其他设备访问。转写稿还演示了在 vue.config.js 的 devServer 中配置 open: true 来自动打开浏览器。
怎么做: 启动后复制终端地址到浏览器访问;若想让其自动打开,可参考转写稿配置:
js
devServer: {
open: true
}易错点: 不要照抄转写稿里讲师的 192.168.23.111,每台机器 IP 不同;端口被占用时按终端提示换端口;看到页面即说明脚手架创建成功。
先看脚手架:入口文件与挂载过程
为什么需要:分析脚手架文件,先要知道项目从哪里启动,否则改错文件、找不到页面来源。
怎么想:转写稿明确“从 main.ts 入手”,它是入口;启动后先执行入口,再创建应用、挂载容器。
怎么做:重点看 src/main.ts,Vue3 不再 new Vue,而是 createApp 创建应用对象,再 mount('#app') 挂到 index.html 的容器上。
ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')易错点:createApp 必须传入根组件;mount('#app') 对应 index.html 中 id="app" 的元素;不要按 Vue2 写成 new Vue({ el:'#app' })。
目录与配置文件:src、index.html、vite.config
为什么需要:知道哪些是开发代码、哪些是模板和构建配置,才能定位问题。
怎么想:转写稿把 src 称为开发环境目录,App.vue 是根组件,components 放依赖组件;index.html 提供挂载容器。
怎么做:index.html 里保留 <div id="app"></div>;vite.config 可配置 server.open、port、proxy 和别名。转写中调试时先加 open: true 自动开浏览器,并配置端口、代理。
易错点:localhost、127.0.0.1、局域网 IP 都指向不同访问场景;别把别人的局域网 IP 当成自己的服务地址。ESLint 若要求组件名多个单词,练习时可先关闭语法检查,但项目里不要长期混用。
App.vue:Vue3 与 Vue2 的写法差异
为什么需要:根组件是页面呈现的起点,必须看懂模板、脚本、样式三部分。
怎么想:转写稿演示 Vue3 的 App.vue 中模板可以出现多个根节点,不再强制单根;脚本可用 defineComponent,也可用 <script setup>。
怎么做:先保留一个最小根组件,模板写内容,脚本按需引入组件,样式可加 scoped。
vue
<template>
<h3>Hello Vue3</h3>
<p>{{ msg }}</p>
</template>
<script setup>
const msg = '脚手架分析'
</script>易错点:defineComponent 的主要作用是类型推断,不是必须写;模板多根是 Vue3 支持点,但不要因此把结构写乱。
在 Vue3 中写 Vue2 语法:兼容与边界
为什么需要:实际项目有 Vue2 迁移场景,面试也常问两者区别。
怎么想:转写稿把 Vue3 与 Vue2 类比 ES6 与 ES5:Vue3 中仍可写部分 Vue2 语法,但生命周期等有变化。
怎么做:data、methods、computed、watch、v-if、v-for、挂载和更新阶段生命周期可继续验证;但销毁类生命周期在 Vue3 中不再按原方式支持。
易错点:不要在一个项目里随意混用选项式和组合式;setup 中拿不到 Vue2 那种 this 组件实例,调试时看到的 this 往往是代理对象。
setup:组合式 API 的入口
为什么需要:Vue3 重点学习组合式 API,而 setup 是所有组合式逻辑的入口。
怎么想:转写稿说它比 beforeCreate 还早执行;返回对象中的属性可给模板使用。
怎么做:在 setup 中声明数据、函数,再 return 一个对象;返回必须是对象,不能是 async 函数。
ts
setup() {
const name = '张三'
return { name }
}易错点:setup 返回 Promise 会警告;同名数据与 data 冲突时以 setup 为准;setup 返回的普通数据默认不是响应式的。
ref:让 setup 数据响应式
为什么需要:setup 普通变量改了页面不更新,需要响应式 API。
怎么想:ref 是函数,返回带 .value 的 RefImpl 实例;模板中自动解包,脚本中要写 .value。
怎么做:
ts
import { ref } from 'vue'
const count = ref(0)
const user = ref({ name: '张三' })
// 脚本中:count.value++;user.value.name = '李四'模板里直接写 {{ count }}。易错点:脚本里忘写 .value 是高频错误;模板里不要写 .value;ref 接收对象时,.value 内部会变成 Proxy。
响应式底层:defineProperty 与 Proxy
为什么需要:理解响应式才能解释“为什么改数据页面会变”。
怎么想:转写稿复习 Vue2 的 Object.defineProperty 拦截读写;Vue3 对对象使用内置 Proxy。
怎么做:defineProperty(obj, 'userName', { get(){...}, set(v){...} }) 中,读取走 get,赋值走 set;get 的返回值就是读取结果。
js
let value = obj.userName
Object.defineProperty(obj, 'userName', {
get() { return value },
set(v) { value = '书名号' + v }
})易错点:get 里不要再次读取被拦截属性,否则可能递归;调试时看控制台输出、源码编译结果,再判断是数据问题还是模板问题。
课堂画面






代码示例
示例一:用 Object.defineProperty 手动拦截属性的读写(Vue2 响应式原理)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>defineProperty 拦截演示</title>
</head>
<body>
<script>
// 1. 原始对象
let obj = { userName: '张三' };
// 2. 用闭包变量缓存"真实值",否则 get 里再读 obj.userName 会无限递归
let value = obj.userName;
// 3. 拦截 obj.userName 这个属性
Object.defineProperty(obj, 'userName', {
get() {
console.log('get 被执行了');
return '《' + value + '》'; // 返回什么,读取到的就是什么
},
set(v) {
console.log('set 被执行了,新值是', v); // v 就是赋进来的值
value = v; // 存进缓存,get 才能拿到最新值
}
});
console.log(obj.userName); // 《张三》 —— 触发 get
obj.userName = '李四'; // 触发 set,v = '李四'
console.log(obj.userName); // 《李四》
console.log(obj.userName === '李四'); // false —— 读到的是 get 的返回值
</script>
</body>
</html>关键行与易错点
Object.defineProperty(对象, '属性名', 描述对象)接收三个参数,第三个描述对象里放get/set。get的返回值就是"读取到的值"。所以示例里输出《张三》、比较obj.userName === '李四'得到false,都是因为读到的是get的返回值。get在读取、比较、赋值给别的变量、作为实参传入函数时都会执行;set在赋值时执行。- 最大易错点:不要在
get里直接return obj.userName,会无限递归爆栈。必须像示例那样用外部变量value缓存,在set里更新value。 - 只拦截了
userName这一个属性,其他属性(如age)不受影响——这正是 Vue2 需要用Object.keys遍历所有属性逐个拦截的原因。
示例二:Vue3 setup + ref 基本使用(APP.vue)
vue
<template>
<div>
<p>{{ a }}</p>
<p>{{ obj.userName }} --- {{ obj.age }}</p>
<button @click="changeName">改用户名</button>
<button @click="addAge">年龄 +1</button>
</div>
</template>
<script>
// 组合式 API 需要按需引入
import { ref } from 'vue'
export default {
// setup 是所有组合式 API 的入口,比 beforeCreate 执行还早
setup() {
// ref 接收任意类型;基本类型返回 ref 实例,对象也会被包成 ref 实例
const a = ref(1)
const obj = ref({ userName: '张三', age: 12 })
// 在 setup 内部操作 ref,必须写 .value
const changeName = () => {
obj.value.userName += '!' // 注意是 obj.value.userName
}
const addAge = () => {
obj.value.age += 1
}
// 必须 return 出去,模板里才能用;return 必须是对象(不能返回 Promise)
return { a, obj, changeName, addAge }
}
}
</script>关键行与易错点
setup是组合式 API 的入口,执行时机在beforeCreate之前,此时拿不到this(this是undefined)。ref(1)返回的是RefImpl实例对象,真实值挂在该实例的.value上;在setup/ 方法里必须写.value,在模板里 Vue 会自动"解包",直接写a、obj即可,多写.value反而不对。return出去的对象属性才能被模板使用;setup不能写成async(会返回 Promise 实例而报警告)。setup返回的数据与data同名时,以setup为准;但data里的数据是响应式的,setup里没经过ref/reactive包装的普通变量不是响应式的,改值不会推动视图更新——这是最容易被忽略的坑。- 模板里可以写多个根节点(Vue3 支持),不必再像 Vue2 那样套一层唯一根元素。
示例三:Vue3 入口文件 main.js(替代 new Vue())
js
// main.js —— 项目入口,从这开始执行
import { createApp } from 'vue' // 不再是 import Vue from 'vue'
import App from './App.vue' // App.vue 是根组件
// createApp 是一个函数,必须传入一个根组件,否则报错
const app = createApp(App)
// 把根组件挂载到 index.html 中 id="app" 的容器上
app.mount('#app')关键行与易错点
- Vue3 没有
new Vue()了,改为createApp(根组件),返回的是一个app对象(普通对象,不是 Vue 实例)。 createApp()必须传根组件,不传会报错;因此App.vue必须先import进来。mount('#app')的#app对应public/index.html里<div id="app"></div>,挂载后该容器内容会被组件替换。- 因为不再有 Vue 构造函数实例,Vue2 里往原型上挂公共属性的
Vue.prototype.xxx写法失效,Vue3 改用app.config.globalProperties.xxx。 import App from './App.vue'中的.vue后缀在入口文件里建议写全,且main.ts中引入组件不支持省略后缀。
常见错误
- 现象:
vue create first后没有main.ts,写defineComponent或 TS 类型就报错。原因:创建时选了 Vue2 preset,或没在默认 Vue3 基础上勾选 TypeScript。修法:重新创建,选手动配置,在默认 Vue3 基础上勾 TypeScript;不要在 Vue2 preset 上硬改。 - 现象:入口仍写
new Vue({ render: h => h(App) }).$mount('#app'),启动报错或页面不挂载。原因:Vue3 入口已改为createApp。修法:改为import { createApp } from 'vue'; import App from './App.vue'; createApp(App).mount('#app')。 - 现象:
setup写成async setup()后控制台警告,组件行为异常。原因:setup返回 Promise,不符合“返回对象/渲染函数”的契约。修法:去掉async,异步请求放到onMounted或独立 async 函数中执行。 - 现象:点击按钮后
ref数据不更新,或模板显示异常。原因:脚本中改ref忘记.value,模板中又误写.value。修法:脚本中count.value++,模板中直接{{ count }}。 - 现象:Vue2 的
beforeDestroy/destroyed写了但销毁逻辑不执行。原因:Vue3 对应生命周期已改为beforeUnmount/unmounted。修法:选项式改生命周期名;组合式用onBeforeUnmount/onUnmounted。 - 现象:
setup里访问this得到undefined,拿不到data/methods。原因:setup执行时机早于组件实例可用阶段,且组合式不依赖this。修法:用ref/reactive声明状态,需要实例时用getCurrentInstance(慎用)或改到生命周期中处理。 - 现象:
data与setup返回同名属性,修改后视图表现和预期不一致。原因:同名时以setup返回为准,且setup返回普通变量不会自动响应。修法:避免同名;需要响应式就用ref/reactive,不要依赖普通变量驱动视图。 - 现象:
ref接收对象后,脚本里直接拿它和原对象比较不相等。原因:ref是包装对象,对象值在.value中且会进入 Proxy 体系。修法:比较、传参、调用对象方法时明确使用.value,并区分“包装对象”和“内部值”。
练习
- 脚手架与入口配置:用
vue create创建带 TypeScript 的 Vue3 项目,在vue.config.js配置devServer.open/port/proxy,启动后验证自动打开、端口生效、代理生效;记录main.ts与 Vue2 入口的差异。 - Vue2 语法兼容验证:在
App.vue中同时写data/methods和setup,分别声明同名name、不同名count,用按钮修改并记录模板显示、同名优先级、响应式差异。 - ref 边界练习:用
ref管理数字、数组、对象,模板直接输出,脚本函数中修改;验证脚本需.value、模板不需.value;打印对象.value并判断是否为 Proxy。 - defineProperty 拦截练习:用
Object.defineProperty拦截user.name的get/set,实现“设置值后读取自动加书名号”;再说明ref接收对象时为何改用 Proxy。 - 故障排查练习:给出一段含
new Vue、async setup、beforeDestroy、模板{{ count.value }}的代码,列出全部问题并改到可运行,附控制台验证结果。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | vue3介绍 | 15:57 |
| 2 | 脚手架说明 | 19:47 |
| 3 | 脚手架中的书写方式与vue2中的差异 | 17:59 |
| 4 | 在Vue3中使用Vue2的语法 | 16:14 |
| 5 | 配置钩子函数setup | 12:46 |
| 6 | 响应组合式API-ref的基本使用 | 25:15 |
| 7 | defineProperty | 20:32 |
小结
- Vue3 学习主线是组合式 API,选项式 API 作为兼容与对照,不要混用到失控。
- Vue3 能跑多数 Vue2 语法,但入口、生命周期、全局 API 不能照搬。
- 脚手架 preset 决定后续体验,Vue3 + TypeScript 要在创建阶段确认。
setup是组合逻辑入口,返回对象供模板使用;async setup会破坏返回契约。ref的核心边界是:脚本中.value,模板中自动解包;对象值进入 Proxy 体系。- 响应式不是“改普通变量就更新”,必须使用
ref/reactive等响应式载体。 Object.defineProperty的get/set适合理解拦截,但对象深层与新增属性场景要理解 Proxy 的必要性。- 工程问题先看控制台警告与编译错误;ESLint/TS 报错不等于运行错误,先定位再改。