Vue实例详解:从new Vue到createApp,掌握生命周期与响应式核心
2026/9/10 5:21:02 网站建设 项目流程

搞 Vue 这么久,我发现一个很有意思的现象:很多人一上来就写组件、配路由、接接口,项目跑起来了,但你要是问他new Vue()到底干了什么、createApp()返回的又是什么、实例上的$refs$nextTick什么时候能用,他反而会愣住。Vue 实例这个东西,看起来简单,实际上是整个框架的地基。你把它吃透了,后面看源码、调性能、排查诡异 bug 都会顺手很多。

这篇博文不打算讲那些云里雾里的设计哲学,就按我平时带团队、写项目的真实经验,把 Vue 实例从创建到销毁、从选项到方法、从调试到排错,一条线给你捋清楚。无论你是刚入门想弄懂new Vue()createApp()区别的新手,还是用了两年 Vue 但某些细节一直模棱两可的同学,这篇文章都值得你花十分钟认真过一遍。

1. 实例创建前的准备与环境理解

1.1 从 CDN 到脚手架:两种方式背后的取舍

很多人学 Vue 是从 CDN 引入开始的,就像我最早接触 Vue 2 的时候,直接在 HTML 里写:

<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

然后:

new Vue({ el: '#app', data: { msg: 'Hello Vue' } });

这种方式的优点是零配置、打开就能跑,非常适合理解"实例"这个概念。你只需要记住:new Vue(options)会创建一个根实例,框架会拿着options里的各种配置去初始化数据响应式系统、编译模板、挂载 DOM。但它的缺点也很明显——没有模块化、没有单文件组件、没有热更新,项目一大就崩。

所以实际开发中,我们几乎都用脚手架(Vue CLI 或 Vite)来创建项目。这时候你可能发现,代码长这样:

import { createApp } from 'vue'; import App from './App.vue'; createApp(App).mount('#app');

createApp()是 Vue 3 的入口,它返回的"应用实例"和我们常说的"组件实例"是两个层面的东西。很多初学者容易在这里犯迷糊:createApp(App)的返回值到底是不是根组件实例?严格说不是,它是一个应用上下文,你后续注册路由、Pinia、全局组件,都是在这个应用实例上操作。真正和 Vue 2new Vue()对应的,是createApp(App)内部根据 App 组件创建出来的那个根组件实例

用生活类比的话,应用实例像是整栋楼的物业,组件实例像楼里的每一户。物业负责统一管理水电燃气,但真正住人的是每一户。Vue 2 的new Vue()既是物业又是第一户,比较"混着来";Vue 3 把这两个角色拆开了,逻辑更清楚。

1.2 new Vue 与 createApp 的双版本对照

如果你还在维护 Vue 2 老项目,或者刚把新项目切到 Vue 3,我建议对照着看两个版本的关键差异,这样记忆特别牢:

对比项Vue 2Vue 3
创建方式new Vue(options)createApp(options)createApp(组件)
根实例 data可以是对象或函数必须是函数
挂载el选项或$mount()mount('#app')
全局 APIVue.use()Vue.component()app.use()app.component()
实例上的全局配置挂在Vue.prototype挂在app.config.globalProperties
销毁vm.$destroy()app.unmount()

Vue 2 里你写new Vue({ data: { a: 1 } })是没问题的,因为根实例只有一个,不存在数据复用问题。但到了 Vue 3,data统一要求是函数,这其实是在强制你养成好习惯——在复用场景下,如果 data 是对象,那么所有组件实例会共享同一个引用,改一个全变,这是连环 bug 的源头。

1.3 挂载点与模板编译:为什么 el 不能是 html 或 body

Vue 2 用el指定挂载点,Vue 3 用mount。不管哪种,都有个硬性规定:挂载点不能是<html><body>标签。原因是 Vue 挂载时会用组件的渲染结果替换掉挂载点内部的内容。如果你把 body 替换了,那<head>里的东西怎么办?整个页面的脚本、样式引用全乱套。所以 HTML 规范也明确规定,<body>的内容是不能随意被整体替换的。

实操中我见过有人这么写:

<body id="app"> ... </body>

然后 Vue 实例绑定了#app,结果页面白屏,控制台还报编译警告。因为 Vue 在替换内容时会把 body 原有的属性、子节点整体换掉,浏览器就开始闹脾气了。正确做法是老老实实在 body 里放一个<div id="app"></div>

另外要注意模板编译的两种方式:一种是template选项,Vue 会把模板字符串编译成render函数;另一种是直接写 render 函数。很多构建工具(比如单文件组件 .vue)在编译阶段就直接生成 render 了,运行时不需要编译器。这就是为什么有些 CDN 版本要区分"完整版"和"运行时版",区别就在于带不带模板编译器。如果用了运行时版又想传template,就会报错。这一点我刚踩过时还挺懵的,后来才明白是版本的问题。

2. 实例的选项与核心配置拆解

2.1 data、methods、computed、watch:各自职责与使用时机

Vue 实例的options对象里,最核心、最容易混淆的四个选项就是:datamethodscomputedwatch。我面试人的时候特别喜欢问这个,因为能看出来是真用过还是只会抄代码。

  • data:定义响应式数据。Vue 会把 data 里的属性全部变成响应式的,访问和修改都会触发视图更新。注意一点:data里的属性是实例创建时就代理到this上的。所以不要在created之后往data里动态加新属性,Vue 2 里这么做响应式会失效(虽然有$set补救);Vue 3 的 Proxy 虽然能拦截新增属性,但如果你一开始没声明,后面加进去的字段在类型推导和代码可读性上都会变差。

  • methods:定义实例方法。每次访问都会重新执行函数,不会被缓存。它最适合放事件处理函数、需要主动调用的业务方法。

  • computed:计算属性,基于 data 中的数据计算得出新值。核心特征是有缓存:依赖的数据没变,多次访问计算属性不会重新执行函数,直接返回上次结果。

  • watch:侦听器,用来侦听数据变化并执行异步或开销较大的操作。

这四个东西用错,最常见的现象就是 computed 写成 methods。比如你要显示一个格式化后的价格:

computed: { formattedPrice() { return '¥' + this.price.toFixed(2); } }

如果你把它放到 methods 里,模板中每次渲染都会重新执行一次函数。如果这个函数里有个大数组的循环,性能问题就来了。而 computed 因为有缓存,只要price不变,函数只执行一次。这不仅仅是"少一次运算"的问题,关键是配合响应式依赖追踪,Vue 能知道这个 computed 依赖了哪些数据,从而精确地更新视图。

2.2 计算属性 vs 方法:缓存的差异在实际项目中的影响

我们来把这个问题聊透。假设页面里有个列表,需要根据搜索关键词过滤出数据:

<ul> <li v-for="item in filteredList" :key="item.id">{{ item.name }}</li> </ul>

如果你用 methods:

methods: { getFilteredList() { return this.list.filter(item => item.name.includes(this.keyword)); } }

模板里写v-for="item in getFilteredList()",那么只要组件重新渲染,不管listkeyword有没有变,这个 filter 都会重新执行一遍。如果list有几千条,而组件因为别的原因触发了重新渲染(比如页面某个无关的 data 变了),那你就是在做无用功。

换成 computed:

computed: { filteredList() { return this.list.filter(item => item.name.includes(this.keyword)); } }

Vue 会追踪filteredList依赖了this.listthis.keyword。只要这两个没变,filteredList就始终返回上一次缓存的结果。这在高频更新、大数据量场景下,性能差距是肉眼可见的。

但 computed 不是万能的。如果你需要传参数,比如根据某个 id 获取详情,那你就得用 methods,或者用闭包的方式返回一个函数:

computed: { getItemById() { return (id) => this.list.find(item => item.id === id); } }

这种"计算属性返回函数"的写法,可以让你在模板里调用getItemById(5),同时也利用了 computed 的缓存:this.list不变,返回的函数引用不会变。有点绕,但实战很好用。

2.3 生命周期钩子:从 beforeCreate 到 unmounted

Vue 实例的一生会经历很多阶段,每个阶段都提供了钩子函数。这是很多人背过但不会用的东西。我结合实操经验给你整理一份"每个阶段能做什么、不能做什么"的速查:

钩子Vue 2Vue 3能做什么注意什么
创建前beforeCreatebeforeCreate很少用此时 data、methods 都不可用
创建后createdcreated请求接口、初始化非响应式数据、赋值 data可以访问 data,但 DOM 还没生成,不能操作 DOM
挂载前beforeMountbeforeMount最后一次修改 data 且不触发额外渲染模板已编译,但未挂载
挂载后mountedmounted操作 DOM、获取宽高、初始化第三方库子组件不一定全部挂载完,需要等的话用$nextTick
更新前beforeUpdatebeforeUpdate获取更新前的 DOM 状态不要在这里改数据,可能死循环
更新后updatedupdated避免使用,性能敏感所有子组件更新完成,做 DOM 依赖更新后操作要小心
销毁前beforeDestroybeforeUnmount清除定时器、解绑事件、销毁第三方实例此时实例还能正常工作
销毁后destroyedunmounted很少用实例上的指令解绑、事件监听移除,子实例也销毁

一个最常见的坑是:有人把接口请求写在mounted里,认为这样才"靠谱"。其实created/mounted都可以,但服务端渲染(SSR)场景下你不能用 mounted,因为服务端没有 DOM。另一坑是在beforeDestroy(Vue 3 的beforeUnmount)里忘记清除setInterval,导致组件销毁后定时器还在跑,控制台每秒报一次错。这个问题在单页应用里特别常见,切走了页面还在请求。

2.4 组件注册与实例关系:根实例与子组件实例的区别

你可能听过"每个组件都是一个 Vue 实例"这个说法。在 Vue 2 里,这句话基本成立——每个组件实例都是通过类似new Vue()的方式创建的。在 Vue 3 里,更准确的说法是"每个组件都是一个实例",由应用实例统一管理。

根实例和子组件实例在能力上是类似的:都有 data、computed、methods、生命周期钩子,都能通过this访问实例属性。但它们有几个关键区别:

  • 根实例是应用入口,负责挂载到 DOM;子组件实例则由父组件树管理。
  • 根实例可以通过$children(Vue 2)或$refs访问子组件,但官方不推荐依赖这种强耦合方式。
  • 根实例通常不接收 props,子组件通过 props 接收外部数据。

在实际项目中,我更喜欢用$refs来处理"父组件要调用子组件方法"的场景。比如表单组件里有个validate方法,父组件在点击提交时调用它:

<child-form ref="form"></child-form>
methods: { submit() { const valid = this.$refs.form.validate(); if (valid) { // 提交 } } }

$refs拿到的是子组件的实例对象,所以你调用的是实例上的真实方法,数据是共享的。但这个方式要慎用,因为它打破了单向数据流,项目大了以后很难追踪是谁调了谁。尽量用事件机制或状态管理。

3. 实例方法与内置属性的实战使用

3.1 $refs、$el、$data、$nextTick——实例身上的"瑞士军刀"

Vue 实例在创建之后,会自动暴露一些以$开头的属性和方法。新手看到这些$符号会觉得神秘,其实它们就是挂在实例上的普通东西,只是命名上做了区分。

  • $data:就是实例的 data 对象,访问vm.$data.msg等价于vm.msg。在调试时可以快速查看所有数据状态。
  • $el:实例挂载后的根 DOM 元素。在mounted之前是 undefined。
  • $refs:一个对象,键是模板里ref属性的值,值是对应的 DOM 元素或组件实例。注意:$refsmounted之前是空的,因为还没渲染。
  • $nextTick(callback):DOM 更新后执行的回调。它的底层原理是异步任务队列,Vue 的 DOM 更新是异步的,数据变了视图不会立刻变,你需要等下一个 tick。

$nextTick是最容易踩坑的地方。比如你写:

this.message = 'Hello'; console.log(this.$refs.text.innerHTML); // 还是旧值

原因就是 Vue 的响应式更新是异步的,赋值后 DOM 还没变。你需要这样:

this.message = 'Hello'; this.$nextTick(() => { console.log(this.$refs.text.innerHTML); // Hello });

在 Vue 3 中,$nextTick可以导入为独立 API:

import { nextTick } from 'vue'; await nextTick();

搞清楚这个,你以后遇到"数据变了 DOM 没变"的诡异问题,第一时间就能想到是不是异步更新没等。

3.2 路由与实例:this.$router 和 this.$route 的差异

项目里几乎必配 vue-router。当你把路由插件注册到实例后,每个组件实例上都会挂上$router$route两个东西。这两个名字长得像,但职责完全不同,我见过太多人搞混。

  • $router:全局路由实例。通过它可以进行路由跳转,比如this.$router.push('/home')this.$router.replace('/login')this.$router.go(-1)
  • $route:当前活跃路由的信息对象。包含pathqueryparamshashmetaname等属性。它是响应式的,路由切换时这个对象会变。

一个非常经典的需求:列表页跳详情页,如何把 id 传给详情页?

// 列表页 this.$router.push({ name: 'Detail', params: { id: item.id } });
// 详情页 created() { const id = this.$route.params.id; this.fetchData(id); }

但有两点要强调。第一,使用params时如果你用path跳转,params会失效。必须用name指定路由。第二,从/detail/1跳转到/detail/2,组件实例会被复用created不会重新触发。这时候你需要侦听$route变化:

watch: { '$route.params.id'(newId) { this.fetchData(newId); } }

或者使用 Vue Router 4 的onBeforeRouteUpdate钩子。这个坑特别隐蔽,不处理的话你会发现详情页数据始终是第一次的。

3.3 事件总线与实例通信:什么时候不该用

组件之间通信,常见方式有 props/emit、provide/inject、状态管理(Vuex/Pinia)、事件总线。在 Vue 2 时代,事件总线很流行:

// 全局总线 Vue.prototype.$bus = new Vue();

发送方:

this.$bus.$emit('refresh', data);

接收方:

this.$bus.$on('refresh', handler);

思路本质是"利用 Vue 实例的 $on/$emit 机制做全局发布订阅"。但 Vue 3 移除了实例上的$on$off,推荐用第三方的 mitt 或者直接用状态管理。为什么移除?因为事件总线在大型项目里特别容易失控——事件名难维护、没有类型提示、无法追踪谁监听谁触发,改起来提心吊胆。

我现在比较推荐的做法是:简单跨级通信用provide/inject,复杂共享状态用 Pinia。比如祖先组件提供一个刷新方法:

provide() { return { refresh: this.refreshData }; }

任意后代组件:

inject: ['refresh']

注意provideinject本身不具备响应式,如果你需要响应式值,建议用一个refreactive对象包一层。这是我踩过坑后才明白的。

3.4 Vue DevTools 在实例调试中的应用

聊实例调试,不聊 Vue DevTools 等于没聊。很多人装了插件但只会看"树形图",太浪费了。我这里分享几个我常用的场景。

第一个是查看实例数据。在 DevTools 的 Vue 面板里选中某个组件,右侧会显示datacomputedpropssetup等。你可以直接修改这些值,页面会实时响应。这在调试交互逻辑时特别好用,不用刷新页面改代码。

第二个是追踪事件。点击组件时,DevTools 会记录该组件所有 emit 的事件日志。你可以通过事件日志快速确认子组件是否正常向外传递了数据。

第三个是性能分析。Vue 3 DevTools 提供了性能标签页,可以查看每次渲染的耗时和触发原因,定位多余渲染非常管用。如果某个组件频繁渲染,它能显示是哪个数据变化导致的。

我一般遇到"页面不更新"的问题,第一反应不是去看代码,而是打开 DevTools 看数据变没变。如果数据变了,视图没变,那是渲染层的问题;如果数据压根没变,那就是逻辑层的问题。二分法排查,能省一半时间。

4. 实操过程:从零配置一个 Vue 实例项目并调试

4.1 安装与项目创建:Vite 和 Vue CLI 的选择

现在新项目一定要用 Vite,这是 Vue 官方在推荐的,构建速度比 Webpack 快了一个量级。如果你还在用 Vue CLI 创建新项目,除非是公司技术栈锁定,否则可以直接跳过。Vite 的安装步骤很简单:

npm create vue@latest

它会问你项目名、是否启用 TypeScript、是否安装 Vue Router、Pinia、ESLint 等。按需选择就行。这个命令生成的是 Vue 3 + Vite 的项目结构。如果你是 Vue 2 老项目维护,那还是用 Vue CLI 或已有的构建配置。

如果你只是想快速验证一个实例行为,不想搭工程,也可以用 Vite 的 CDN 方式:

<script type="module"> import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'; createApp({ setup() { const count = ref(0); return { count }; }, template: `<button @click="count++">{{ count }}</button>` }).mount('#app'); </script>

这种直接把 Vue 模块导入浏览器的方式,特别适合做小 demo。我在写技术文章和排疑问题时经常用。

4.2 编写第一个实例:从 options API 到 Composition API

为了让这篇文章对两种风格的读者都友好,我分别写一遍。

Vue 2 / Vue 3 的 Options API 风格:

import { createApp } from 'vue'; import App from './App.vue'; // 根组件选项 const app = createApp({ data() { return { title: 'Vue 实例详解', count: 0, items: ['A', 'B', 'C'] }; }, computed: { doubleCount() { return this.count * 2; } }, methods: { increment() { this.count++; }, addItem() { this.items.push('D'); } }, mounted() { console.log('实例已挂载', this.$el); } }); app.mount('#app');

Vue 3 的 Composition API 风格(setup函数):

import { createApp, ref, reactive, computed } from 'vue'; const app = createApp({ setup() { const count = ref(0); const items = reactive(['A', 'B', 'C']); const doubleCount = computed(() => count.value * 2); const increment = () => { count.value++; }; const addItem = () => { items.push('D'); }; return { count, items, doubleCount, increment, addItem }; } }); app.mount('#app');

如果你用的是单文件组件,setup通常写成:

<script setup> import { ref } from 'vue'; const count = ref(0); const doubleCount = computed(() => count.value * 2); </script>

注意在setup里访问实例本身很别扭,因为大多数逻辑都拆成了独立函数。你只需要知道:setup返回的数据和函数会暴露给模板,这就是实例的"脸面"。

4.3 通过 DevTools 观察实例状态变化

项目跑起来后,打开浏览器开发者工具,切到 Vue 面板。你会看到一个组件树。选中根组件,右侧的 data 面板里会显示countitems等。现在你在页面上点按钮,观察 DevTools 中数据的变化。

这里有个小技巧:如果想验证 computed 的缓存特性,你可以给count加一个很耗时的计算过程,比如循环一千万次。每次点击 count 时,doubleCount 会重新计算,页面会卡一下。然后你点击一个无关的按钮,比如addItem,如果 doubleCount 没有重新计算,页面不会卡,这就直观看到了缓存的作用。

DevTools 还能让你手动改值。比如把count改成 10,页面上的数字会立刻变 20。这个能力在调试复杂交互时非常有用,不用去源码里找变量赋值。

4.4 常见配置:环境变量与实例全局属性

实例级配置里,有两个东西我在实战中经常用到。

第一个是app.config.globalProperties,用来挂载全局方法或属性。比如在很多组件里都要拿用户 token:

const app = createApp(App); app.config.globalProperties.$token = 'your-token';

然后任意组件里通过this.$token访问。但注意,这种方式定义的属性不是响应式的,它只是个常量。如果 token 会变,还是建议用 Pinia 或 localStorage 管理。

第二个是环境变量。Vite 项目根目录下可以建.env.development.env.production文件:

VITE_API_BASE_URL=https://api.example.com

在组件里通过import.meta.env.VITE_API_BASE_URL访问。注意变量名必须是以VITE_开头,这样 Vite 才会把它暴露到前端代码中。这个不属于实例选项,但和实例的数据初始化强相关。比如你可以在created里用环境变量去拼接口地址。

5. 常见问题与排查技巧实录

5.1 为什么组件 data 必须是函数,根实例却不是?

这是 Vue 面试高频题,也是实际报错的重灾区。在 Vue 2 里,如果你写组件时用了data: { ... },会直接报错:The "data" option should be a function that returns a per-instance value in component definitions.原因是组件会被复用,如果 data 是一个对象,那么每个组件实例都引用同一个对象。一个实例改了数据,所有实例的数据都跟着变。而函数形式会在每次创建实例时调用,返回全新的对象,实例间互不干扰。

Vue 3 更进一步,连根实例的 data 都要求必须是函数。这个改动很聪明,因为根实例很可能在测试里被多次创建,用函数可以避免测试间的状态污染。所以你在 Vue 3 里写:

createApp({ data() { return { count: 0 }; } });

而不是data: { count: 0 }

5.2 mounted 中获取 DOM 尺寸为 0 怎么办?

这个问题我帮人调过很多次。现象是:在mounted里用this.$refs.container.offsetWidth获取宽度,结果返回 0。原因可能有两种。

第一种:图片或字体导致布局不稳定。mounted只保证 DOM 已经被插入,但不保证图片加载完成。如果容器的宽度依赖图片,图片还没加载完,宽度就是 0。解决方案是等图片onload或使用ResizeObserver监听尺寸变化。

第二种:异步数据尚未渲染。mounted触发的时间早于异步请求返回,这时候列表还没渲染出来,拿不到 li 的宽度。解决方案是在数据更新后再获取,比如:

watch: { list: { immediate: true, handler() { this.$nextTick(() => { console.log(this.$refs.container.offsetWidth); }); } } }

如果等一次$nextTick还不够,那可能是子组件还没渲染完,可以再加一个setTimeout或者用双层$nextTick。但要注意,过度依赖这种"玄学"写法往往说明架构有问题,更优雅的方案是数据驱动样式,尽量避免手动操作 DOM。

5.3 修改了数据但视图不更新:响应式原理的边界

Vue 2 的响应式是基于Object.defineProperty,它有一个限制:无法检测对象属性的新增删除,也无法检测数组通过下标赋值和length变化。所以经典 bug 是:

this.obj.newKey = 'value'; // 视图不更新 this.arr[0] = 'new'; // 视图不更新

Vue 2 的解决方案是:

this.$set(this.obj, 'newKey', 'value'); this.$set(this.arr, 0, 'new');

Vue 3 用 Proxy 重写了响应式,上面直接赋值是可以的。但 Vue 3 也有自己的坑:如果用reactive对对象做了响应式代理,你解构出来用就丢了响应式:

const state = reactive({ count: 0 }); const { count } = state; // count 是普通数字,不是响应式

要用toRefs才能解构后保持响应式:

const state = reactive({ count: 0 }); const { count } = toRefs(state);

这些边界问题,在排查"视图不更新"时一定要优先考虑。

5.4 实例销毁后仍触发更新:清理不及时

组件销毁后,实例上的 DOM 已经被移除,但如果有异步回调或定时器还在引用实例,就会产生内存泄漏或控制台报错。最常见的就是在mounted里启动轮询:

mounted() { this.timer = setInterval(() => { this.getData(); }, 5000); }, beforeUnmount() { clearInterval(this.timer); }

还有全局事件监听:

mounted() { window.addEventListener('scroll', this.handleScroll); }, beforeUnmount() { window.removeEventListener('scroll', this.handleScroll); }

在 Vue 3 的<script setup>里,可以用onUnmounted钩子。很多年轻人容易忘的是 useEventListener 这类组合式函数,其实在内部已经帮你清理了,但你自己封装的时候一定要记得。

把生命周期钩子里的清理工作写成 checklist,每次写组件都检查一遍,能避免大多数线上"页面卡死"的问题。

5.5 报错信息速查表

最后整理一份我在日常和社区里常见的 Vue 实例报错,方便你对照排查。

报错信息原因解决方案
The "data" option should be a function组件 data 写成了对象改为函数返回对象
Cannot read property 'xxx' of undefined访问了未初始化的数据,或上游异步数据未返回初始化时给完整结构,或使用可选链?.
Property or method "xxx" is not defined模板中引用了未定义的变量/方法检查 data/computed/methods 中是否声明
You may have an infinite update loop in a component在 computed 或 render 里修改了依赖数据检查计算属性是否有副作用,不要在 computed 中改数据
Invalid value for option "el"el指向了一个不存在的 DOM 或 body/html确保挂载点是合法的普通元素
Object is possible prototype pollution动态设置__proto__相关字段(Vue 3 安全警告)避免把不可信数据直接设置到响应式对象上

排查步骤我一般遵循三板斧:

  1. 看控制台完整报错栈,定位是哪个组件、哪个方法。
  2. 打开 Vue DevTools,查看实例数据是否符合预期。
  3. 在关键生命周期里打console.log,确认时序。

这一套流程走完,百分之九十的实例问题都能解决。

最后再分享一个小技巧:如果你在控制台调试实例,可以在元素面板选中根 DOM 节点,然后输入$0.__vue__(Vue 2)或$0.__vue_app__(Vue 3)直接拿到实例对象。你甚至可以在控制台里直接改数据、调用方法,调试效率极高。这个技巧是我在工作中用了很多年的,第一次见到的人都觉得像发现了新大陆。写 Vue 这么多年,我始终觉得,对实例的理解程度决定了你能在这个框架上走多远。希望这篇详解能帮你把这块短板补上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询