Code前端首页关于Code前端联系我们

Vue3实例怎么玩?从基础到实战这些关键要点得搞懂!

terry 1天前 阅读数 19 #SEO
文章标签 Vue3实例

想学Vue3但一涉及“实例”就懵?别慌!不管是刚入门想搞懂基础创建,还是想在实战里玩转组件通信、状态管理,这篇问答把关键知识点拆明白,看完动手写项目更顺~下面从基础区别到实战技巧,一个个问题唠清楚!

Vue3里的“实例”和Vue2有啥不一样?

Vue2里我们写 new Vue({ el: '#app', ... }) 创建根实例,整个应用和根实例强绑定,但Vue3变了——用 createApp 先创建应用实例,再把根组件“挂”上去。

举个简单对比:
Vue2写法:

new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

Vue3写法:

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

核心区别有俩:

  • 架构更“轻”更灵活:Vue3的createApp返回的app是“应用实例”,能独立管理组件、指令、插件,比如做微前端时,不同应用实例互不干扰;
  • 根组件更纯粹:Vue2里根实例又当应用又当组件,Vue3把“应用”和“根组件”拆开,App.vue就是普通根组件,由应用实例去挂载,职责更清晰。

怎么用createApp创建Vue3实例?

分三步,像搭积木一样:

引入createApp

先从vue包导入createApp这个工具函数:

import { createApp } from 'vue'

关联根组件

把你的根组件(比如App.vue)传给createApp,生成应用实例:

import App from './App.vue'
const app = createApp(App)

挂载到DOM

app.mount(选择器)把应用渲染到页面里,比如页面有<div id="app"></div>,就写:

app.mount('#app')

额外技巧:应用实例还能搞这些操作——

  • 注册全局组件:app.component('MyButton', MyButton)
  • 注册全局指令:app.directive('focus', { mounted(el) { el.focus() } })
  • 使用插件:app.use(MyPlugin)

举个完整小例子,页面要显示“Hello Vue3”:

App.vue内容:

<template>
  <h1>Hello Vue3</h1>
</template>

main.js(入口文件):

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

浏览器打开后,#app位置就会渲染出<h1>Hello Vue3</h1>,这就是最基础的Vue3实例创建流程~

组合式API怎么和Vue3实例配合?

Vue3的组合式API(比如refreactivesetup),是在组件内部和实例联动的,核心是让逻辑更聚合。

用setup做“逻辑入口”

在组件里写<script setup>(语法糖),它会自动关联到Vue实例的渲染逻辑,比如做个计数器:

<template>
  <button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0) // ref让数字变成响应式
</script>

这里ref(0)创建响应式数据,setup里的变量/方法会自动暴露给模板用,不用像Vue2那样在datamethods里拆分。

生命周期钩子咋用?

组合式API里的钩子要导入用,比如组件挂载后执行逻辑:

<script setup>
import { onMounted } from 'vue'
onMounted(() => {
  console.log('组件挂载到页面啦!')
})
</script>

这些钩子是和当前组件的Vue实例生命周期绑定的,组件挂载/更新/卸载时,钩子会自动触发。

对比选项式API,组合式好在哪?

假设做一个“用户信息卡片”,选项式API得把datamethodscomputed拆到不同配置项,逻辑分散;组合式API可以把“获取用户信息”“格式化时间”这些逻辑包在一个函数里,复用性更强。

<script setup>
import { ref, computed } from 'vue'
import { formatTime } from './utils' // 假设外部工具函数
const user = ref({ name: '小明', createTime: 1699999999 })
const formattedTime = computed(() => formatTime(user.value.createTime))
function updateName() {
  user.value.name = '大明'
}
</script>

所有和“用户信息”相关的逻辑都在setup里扎堆,维护起来更顺,这就是组合式API和Vue3实例配合的优势~

做个TodoList,Vue3实例怎么处理数据和交互?

实战最能练手!做个“添加任务、标记完成、删除任务”的TodoList,拆解步骤:

需求 & 结构设计

页面要有:输入框+添加按钮、任务列表(每项显示标题+完成按钮+删除按钮)。

用响应式数据存任务

在根组件(比如App.vue)的setup里,用reactive存任务列表:

<script setup>
import { reactive } from 'vue'
const todos = reactive([
  { id: 1, title: '学习Vue3实例', done: false },
  { id: 2, title: '写个Demo', done: false }
])
</script>

reactive让对象/数组变成响应式,数据变了页面自动更。

实现“添加任务”

加个输入框和按钮,绑定v-model和点击事件:

<template>
  <input v-model="newTask" placeholder="输入新任务" />
  <button @click="addTodo">添加</button>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.title }}
      <button @click="toggleDone(todo.id)">完成</button>
      <button @click="deleteTodo(todo.id)">删除</button>
    </li>
  </ul>
</template>
<script setup>
import { reactive, ref } from 'vue'
const newTask = ref('') // 输入框的响应式数据
const todos = reactive([...]) // 同上
function addTodo() {
  if (!newTask.value) return
  todos.push({ 
    id: Date.now(),  newTask.value, 
    done: false 
  })
  newTask.value = '' // 清空输入框
}
</script>

标记完成 & 删除任务

写两个方法修改todos

<script setup>
// ...前面的代码
function toggleDone(id) {
  const target = todos.find(todo => todo.id === id)
  if (target) {
    target.done = !target.done
  }
}
function deleteTodo(id) {
  todos.splice(todos.findIndex(todo => todo.id === id), 1)
}
</script>

样式优化(可选)

给完成的任务加删除线,用v-bind:class

<li v-for="todo in todos" :key="todo.id" :class="{ done: todo.done }">
  {{ todo.title }}
  <!-- 按钮 -->
</li>
<style scoped>
.done {
  text-decoration: line-through;
  opacity: 0.5;
}
</style>

整个流程里,Vue3实例通过createApp挂载根组件,根组件用组合式API管理响应式数据和方法,实现交互逻辑,只要数据变了,Vue的响应式系统会自动更新DOM,不用手动操作DOM,这就是Vue3实例+响应式的威力~

Vue3实例里的组件通信咋搞?

组件通信是绕不开的坎,Vue3里常用这几种方式:

父传子:props

父组件给子组件传数据,子组件用defineProps接收。

父组件(TodoList.vue)

<template>
  <TodoItem 
    v-for="todo in todos" 
    :key="todo.id" 
    :task="todo" 
  />
</template>
<script setup>
import TodoItem from './TodoItem.vue'
import { reactive } from 'vue'
const todos = reactive([...]) // 任务列表
</script>

子组件(TodoItem.vue)

<template>
  <div>{{ task.title }}</div>
  <button @click="markDone">完成</button>
</template>
<script setup>
const props = defineProps(['task']) // 接收父组件的task
</script>

子传父:emit

子组件触发事件,父组件监听,比如子组件点“完成”,父组件更新任务状态。

子组件(TodoItem.vue)

<script setup>
const props = defineProps(['task'])
const emit = defineEmits(['toggle']) // 声明要触发的事件
function markDone() {
  emit('toggle', props.task.id) // 把任务id传给父组件
}
</script>

父组件(TodoList.vue)

<template>
  <TodoItem 
    ... 
    @toggle="handleToggle" 
  />
</template>
<script setup>
function handleToggle(id) {
  // 找到对应任务,切换done状态
  const target = todos.find(todo => todo.id === id)
  if (target) target.done = !target.done
}
</script>

跨层级通信:provide / inject

如果组件嵌套很深(比如爷爷→爸爸→孙子),props层层传递太麻烦,用provide(祖先组件提供数据)和inject(后代组件注入数据)。

祖先组件(比如App.vue)

<script setup>
import { provide, reactive } from 'vue'
const theme = reactive({ mode: 'light' }) // 全局主题
provide('theme', theme) // 把theme提供出去
</script>

深层子组件

<script setup>
import { inject } from 'vue'
const theme = inject('theme') // 注入theme
console.log(theme.mode) // 能拿到'light'
</script>

这三种方式覆盖了大部分场景,按需选择就行~

Vue3实例性能优化有哪些关键技巧?

Vue3本身性能比Vue2强(比如响应式基于Proxy),但写代码时注意这些细节,能更丝滑:

“自动提升”

Vue3会自动检测不依赖响应式数据的DOM节点,把它们“提升”到渲染函数外,减少重复渲染。

<template>
  <div class="logo">固定Logo</div> <!-- 静态内容,只渲染一次 -->
  <div>{{ dynamicMsg }}</div> <!-- 动态内容,数据变了才更 -->
</template>

不用你手动做啥,Vue3编译时自动优化~

按需导入,减少包体积

Vue3支持树摇(Tree Shaking),没用到的API不会打包,所以导入时要“按需拿”:

// 好的写法:用啥导啥
import { ref, onMounted } from 'vue'
// 不好的写法:导入整个vue(虽然能跑,但包更大)
import Vue from 'vue'

减少响应式追踪开销

如果数据很大(比如长列表、大对象),但只需要“顶层响应式”,用shallowRefshallowReactive

  • shallowRef:只有.value赋值时触发更新,内部属性变化不触发;
  • shallowReactive:只有对象顶层属性变化触发更新,深层属性不管。

举个例子,加载一个很大的图片列表,用shallowRef

import { shallowRef } from 'vue'
const imageList = shallowRef([])
// 赋值时触发更新
imageList.value = await fetchImages()
// 内部push不会触发更新(适合一次性替换数据的场景)
imageList.value.push(newImage) // 没用,不会更DOM

合理用<Teleport>优化渲染 要“跳出”父组件容器(比如弹窗、下拉菜单),用<Teleport>渲染到body下,避免父组件样式干扰,也能减少不必要的渲染层级。

<template>
  <button @click="showModal = true">打开弹窗</button>
  <Teleport to="body">
    <div class="modal" v-if="showModal">
      我是弹窗内容
    </div>
  </Teleport>
</template>

实例挂载后想改根组件,咋操作?

有时候项目运行中,想动态替换根组件(比如AB测试、动态加载主题组件),Vue3也能搞:

先unmount,再mount新组件

应用实例的mount是可以多次调用的,但要先把之前的卸载掉:

import { createApp } from 'vue'
import AppA from './AppA.vue'
import AppB from './AppB.vue'
const app = createApp(AppA)
app.mount('#app')
// 后来想换AppB
app.unmount() // 先卸载原来的
createApp(AppB).mount('#app') // 挂载新的

用动态组件+<component>

在根组件里用<component :is="currentApp" />动态切换:

<template>
  <component :is="currentApp" />
</template>
<script setup>
import { ref } from 'vue'
import AppA from './AppA.vue'
import AppB from './AppB.vue'
const currentApp = ref(AppA)
// 某个事件触发切换
function switchApp() {
  currentApp.value = AppB
}
</script>

这种方式更灵活,适合不需要完全替换应用实例,只是根组件内切换场景~

Vue3的“实例”不再是Vue2那种“大而全”的根实例,而是用createApp生成更灵活的应用实例,配合组合式API让逻辑更聚合,实战里处理数据、通信、性能都有对应的技巧,把这些问题吃透,写项目时思路会更顺,遇到需求也知道从哪下手~ 要是还有具体场景卡壳,评论区随时喊我唠!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门