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

想学Vue3从哪入手?新手友好的核心知识问答手册

terry 16小时前 阅读数 100 #Vue
文章标签 Vue3入门

Vue3 基础认知:和 Vue2 有啥不一样?

问题1:Vue3 相比 Vue2 升级了哪些核心点?
Vue3 升级主要体现在 性能、开发体验、生态工具 这三方面。
性能上:一是编译阶段做了「静态标记」,把模板里不变的元素(比如纯文本、静态 class)打上标记,diff 时直接跳过,减少比对次数;二是响应式系统换用 Proxy 代替 Object.defineProperty,能监听对象新增/删除属性、数组下标修改,还能懒代理(对象没访问时不监听,性能更优)。
开发体验上:推出 组合式 API(Composition API),把零散在 datamethodscomputed 里的逻辑按功能聚合,复用起来更方便;还有 setup 语法糖,写组件时不用再写 return,代码更简洁。
生态工具上:配套工具全升级——打包用 Vite 代替 Webpack(启动快到秒级),状态管理用 Pinia 代替 Vuex(语法更轻量),路由用 Vue Router 4(API 更简洁,支持组合式 API)。

问题2:项目里该选 Vue2 还是 Vue3?
看场景:

  • 新项目/团队技术栈新:直接冲 Vue3!组合式 API 写复杂逻辑更顺手,Vite 开发效率高,TypeScript 支持更丝滑,生态工具也在持续迭代。
  • 维护老项目:如果项目稳定、迭代少,Vue2 能继续用;但如果要加新功能,且团队愿意学习,升级 Vue3 更省心(Vue3 对 Vue2 语法兼容度很高,大部分代码能直接跑)。
  • 兼容性需求:比如要兼容 IE 浏览器,只能选 Vue2(Vue3 放弃了 IE 支持,因为 Proxy 没 polyfill)。

问题3:Vue3 对 TypeScript 的支持好在哪?
Vue3 源码本身用 TypeScript 重写,对类型推导的支持「从根上就更友好」。

  • 定义组件时,用 defineComponent 包裹后,TS 能自动推导 propsemits 的类型;
  • 组合式 API 里,refreactive 这些 API 本身带泛型,写 const count = ref<number>(0) 时,类型错误能及时被 IDE 揪出来;
  • 对比 Vue2,以前要手动写一堆 interfacePropType 来声明类型,现在很多场景 TS 能自动推断,写代码时少踩「类型不匹配」的坑。

组合式 API 实战:写代码更顺手的秘密?

问题1:什么是组合式 API(Composition API)?和选项式 API 有啥区别?
选项式 API(Vue2 常用的 datamethodscomputed 那种写法)的问题是:一个功能的逻辑会分散在不同选项里(比如处理表单验证,data 放变量,methods 放验证函数,watch 监听变化),代码多了就像「拼图」,找逻辑得跳来跳去。

组合式 API 把同一功能的逻辑聚合,用 setup 函数(或语法糖)把变量、方法、逻辑封装成「逻辑块」,比如做用户登录功能:

<script setup>
import { ref, onMounted } from 'vue'
// 变量
const user = ref('')
const pwd = ref('')
// 方法
const handleLogin = () => { ... }
// 生命周期
onMounted(() => { 
  // 页面加载后自动聚焦输入框
  user.value.focus() 
})
// 还能把逻辑抽成自定义 Hook,useLogin(),复用起来更方便!
</script>

这样所有登录相关逻辑都在 setup 里,维护时「找逻辑不用跨选项」,复用也更灵活(把这段逻辑复制到另一个组件,或者抽成独立函数就行)。

问题2:setup 函数和 setup 语法糖怎么选?
setup 函数是组合式 API 的基础写法,长这样:

<script>
export default {
  setup() {
    const count = ref(0)
    return { count } // 必须 return 才能在模板用
  }
}
</script>

setup 语法糖(<script setup>)是 Vue3.2+ 推出的简化写法,不用写 export default,也不用 return,代码更简洁:

<script setup>
const count = ref(0) // 直接在模板用,不用 return
</script>

新项目优先选语法糖!它减少重复代码,还能和 TS 完美配合(自动推导类型),如果要写「需要配置项」的组件(比如自定义 props 验证、emits 声明),语法糖也支持,用 definePropsdefineEmits 就行。

问题3:ref 和 reactive 怎么选?什么时候用 toRefs?

  • ref:用来包裹基本类型(字符串、数字、布尔) 或者单值对象{ count: 0 }),访问时要通过 .value(模板里不用,JS 里要写),适合存单个值,比如输入框内容、开关状态。
  • reactive:用来包裹复杂对象/数组(比如用户信息对象、商品列表),直接操作属性就能触发响应式更新,但注意:reactive 包裹的对象不能直接解构,否则会失去响应式!

举个🌰:

const user = reactive({ name: '张三', age: 18 })
// 错误示范:解构后 name、age 变成普通变量,修改不触发更新
const { name, age } = user 
name = '李四' // 页面不更新!
// 正确做法:用 toRefs 解构
import { toRefs } from 'vue'
const { name, age } = toRefs(user)
name.value = '李四' // 页面会更新~

存单个值用 ref,存复杂对象用 reactive;如果要解构 reactive 包裹的对象,先包一层 toRefs

响应式原理:Vue3 怎么实现数据“自动更新”?

问题1:Vue3 的响应式和 Vue2 有啥本质区别?
Vue2 用 Object.defineProperty 遍历对象的每个属性,给每个属性单独加 getter/setter 拦截;但它有短板:

  • 无法监听对象新增/删除属性user.newProp = 'xxx' 没法检测);
  • 无法监听数组下标修改arr[0] = 1 没法检测);
  • 初始化时要递归遍历所有属性,对象层级深时性能差。

Vue3 换成 Proxy 代理整个对象,拦截的是对象的「读取、修改、新增、删除」等操作(对应 getsetdeleteProperty 等钩子),优势很明显:

  • 能监听对象所有属性的变化(包括新增、删除);
  • 数组下标修改、push/pop 等方法也能监听;
  • 懒代理:对象没被访问时不监听,访问时再代理,性能更优(尤其对象层级深时)。

问题2:Proxy 相比 Object.defineProperty 好在哪?
除了上面说的「监听范围更广」,Proxy 还有这些优势:

  • 性能Object.defineProperty 要遍历对象所有属性,给每个属性加拦截;Proxy 只代理整个对象,初始化更快,也不用递归遍历嵌套对象(访问嵌套对象时再代理)。
  • 代码简洁Proxy 用一个代理对象包裹原对象,拦截逻辑写在 handler 里,代码更集中;Object.defineProperty 要对每个属性单独处理,代码冗余。

Proxy 也有个小缺点:无法兼容 IE 浏览器(因为 IE 根本不支持 Proxy),Vue3 放弃了 IE 支持,这也是选择 Vue2/Vue3 时要考虑的点。

问题3:为什么有时候数据变了页面没更新?
碰到这种情况,先检查这几点:

  • 是不是用了非响应式数据? 比如直接声明 let count = 0,然后修改 count —— 这种普通变量 Vue 监听不到,得用 refreactive 包裹。
  • 是不是修改了 reactive 对象的深层属性,但没触发代理? const user = reactive({ info: { name: 'a' } }),然后直接替换整个 infouser.info = { name: 'b' } —— 这时候要确保 info 本身是响应式的(如果是从接口拿的纯对象,赋值前用 reactive 包一下)。
  • 是不是在 setup 外操作了响应式数据? 比如把 ref 变量暴露到全局,在非 Vue 上下文里修改(比如定时器、第三方库回调),这时候要确保用 .value 正确修改,且操作在 Vue 响应式上下文里。

组件通信:父子、跨组件怎么传值?

问题1:父子组件通信,Vue3 有哪些新方式?
核心还是 props + emits,但 Vue3 做了优化:

  • props 声明更灵活:用 defineProps 可以直接写类型或验证,
    <script setup>
    // 简单声明类型
    const props = defineProps(['title', 'count']) 
    // 带验证的声明
    const props = defineProps({ String,
      count: {
        type: Number,
        required: true,
        default: 0
      }
    })
    </script>
  • emits 要显式声明:用 defineEmits 声明触发的事件,避免和原生事件冲突,
    <script setup>
    const emit = defineEmits(['change', 'submit'])
    const handleClick = () => {
      emit('change', newVal) // 触发自定义事件
    }
    </script>

    父子通信还能通过 useAttrsuseSlots 拿父组件传的属性/插槽,但更推荐 props + emits 走明路~

问题2:跨多层级组件通信,用 Provide/Inject 要注意什么?
Provide/Inject 是「祖先组件给后代组件传值」的方式,不用层层透传 props,但要注意 响应式

  • 如果传的是普通变量,后代组件修改后不会同步到祖先组件;
  • 要让数据「双向响应」,得用 refreactive 包裹值。

举个正确示范:

<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme) // 用 ref 包裹,后代能响应变化
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
theme.value = 'light' // 祖先组件的 theme 也会跟着变~
</script>

如果只传普通值(provide('theme', 'dark')),后代修改后祖先不会更新,这时候适合「只读」场景。

问题3:兄弟组件通信除了 EventBus,还有更优雅的方法吗?
以前用 EventBus(自己搞个空 Vue 实例当事件中心),但 Vue3 里没了 $on/$emit,EventBus 不好使了,更推荐这两种方式:

  • 用 Pinia/Vuex 做状态管理:把兄弟组件共享的数据存在 Store 里,一个改、一个读,自动响应式,比如用 Pinia:

    // store/counter.js
    import { defineStore } from 'pinia'
    export const useCounterStore = defineStore('counter', {
      state: () => ({ count: 0 }),
      actions: { increment() { this.count++ } }
    })
    // 组件A
    <script setup>
    const store = useCounterStore()
    store.increment()
    </script>
    // 组件B
    <script setup>
    const store = useCounterStore()
    console.log(store.count) // 能拿到最新值
    </script>
  • 通过父组件中转:兄弟组件的通信需求,让父组件当「中间人」—— 组件A emit 事件给父组件,父组件再 props 传给组件B,适合通信逻辑简单的场景。

生态工具:Vue3 项目怎么搭基建?

问题1:Vue3 配 Vite 比 Webpack 好在哪?
Vite 是专门为 Vue3 打造的下一代构建工具,优势直接「肉眼可见」:

  • 启动速度:Webpack 启动时要打包整个项目(哪怕代码没改),项目大了要等几十秒;Vite 基于浏览器的 ES Module,只编译「当前页面用到的代码」,启动秒级完成。
  • 热更新(HMR):Webpack 改个组件,要重新打包整个模块,热更新慢;Vite 直接更新单个组件,页面瞬间刷新,甚至能保留组件状态。
  • 配置简单:Vite 配置文件比 Webpack 简洁太多,比如配置别名、代理,几行代码搞定,不用记一堆 loader 和 plugin。

现在新项目基本都用 Vite + Vue3 组合,开发体验拉满~

问题2:状态管理用 Pinia 代替 Vuex 有必要吗?
太有必要了!Pinia 可以理解为「Vuex 的升级版」,解决了 Vuex 很多痛点:

  • 语法更简单:不用写 mutation(修改状态直接在 actions 或组件里改 state),少写很多冗余代码。
  • 支持组合式 API:在 setup 里能直接用 useStore,和组合式 API 无缝配合。
  • 体积更小:Pinia 打包后体积比 Vuex 小一半还多,对性能友好。
  • TypeScript 友好:类型推导自动完成,不用手动写一堆声明。

如果是新项目,直接上 Pinia;老项目用 Vuex 也能继续维护,但新功能推荐用 Pinia 重构~

问题3:Vue Router 4 升级后要注意哪些变化?
Vue Router 4 是 Vue3 专属的路由库,和 Vue3 生态(组合式 API、Vite)配合更丝滑,但有这些 Breaking Change:

  • 创建路由的方式:用 createRouter + createWebHistory(或 createWebHashHistory)代替之前的 new VueRouter
    import { createRouter, createWebHistory } from 'vue-router'
    const router = createRouter({
      history: createWebHistory(),
      routes: [...]
    })
  • 动态路由匹配:参数变化时,默认不会触发组件重新渲染,要手动监听 $route 或者用 key="$route.fullPath" 强制刷新。
  • 导航守卫语法:全局守卫用 router.beforeEach,组件内守卫用 onBeforeRouteUpdate 等组合式 API 风格的钩子。

实战避坑:这些细节新手最容易错?

问题1:为什么 setup 里不能用 this?
因为 setup 是「在组件实例创建之前」执行的,这时候组件实例(也就是 this)还没生成,所以拿不到 this,如果要访问组件的属性(attrsslots),得用 setup 的参数或者 useAttrs/useSlots

<script setup>
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
// 或者在 setup 函数里用参数
export default {
  setup(props, ctx) {
    console.log(ctx.attrs, ctx.slots)
  }
}
</script>

问题2:自定义指令在 Vue3 里怎么写?
Vue3 里自定义指令的钩子函数名变了,对应关系:

  • bindbeforeMount(指令绑定到元素,还没挂载)
  • insertedmounted(元素插入 DOM)
  • update → 拆成 beforeUpdate(更新前)和 updated(更新后)
  • unbindunmounted(指令和元素解绑)

举个自定义指令例子(实现输入框自动聚焦):

// main.js 注册全局指令
import { createApp } from 'vue'
const app = createApp(App)
app.directive('focus', {
  beforeMount(el) {
    el.focus() // 元素挂载前,设置聚焦
  }
})
// 组件里用
<input v-focus />

问题3:Vue3 打包后体积太大怎么优化?
这几个方向能有效瘦身:

  • Tree-shaking:Vue3

版权声明

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

热门