从VUE2到VUE3学习过程遇到的一些困惑

目录

  1. 普通 setup 和 <script setup>(重点普通 setup + 官网链接)
  2. import {} from '' 讲解
  3. 组合式:业务代码抽离复用,demo + 解释
  4. ref /reactive 详细用法(对象数组怎么改,是否需要 import)
  5. ref & reactive 底层原理、什么时候选哪个
  6. hooks 是什么,怎么写怎么用(demo)
  7. 选项式 + setup 混合写法:setup(){} 详解 + 官网链接
  8. <script setup> props、emit demo
  9. defineExpose 讲解 + demo
  10. 什么是宏(macro) ,defineProps/defineEmits 为什么叫宏
  11. 学习路线:先学普通 setup,再学 script setup,该怎么学

1. 普通 setup 和 <script setup>

一句话: 普通 setup :Vue3 组件配置项里的一个函数,属于组合式 API 原始形态 ; <script setup> :是普通 setup 的编译期语法糖,简化写法,uniapp、vue 项目最常用。

① 普通 setup(Options 配置里的 setup 函数)

官网文档:cn.vuejs.org/api/composi...

setup 是 Vue3 组件的一个选项 ,和 data()、methods、mounted 同级。 执行时机:在组件创建前,props 解析之后,beforeCreate 之前执行。 👉 setup 里面没有 this! 这是重点,setup 函数内部this是undefined。

规则:

  1. 所有响应式变量、函数,最后必须 return 暴露出去,模板才能访问;

  2. 生命周期、ref/reactive 都要 import;

  3. 接收两个参数:setup(props, context)

    • props:父传过来的属性(只读,不能直接改)
    • context:上下文,包含 emit, attrs, slots, expose

普通 setup 完整示例

xml 复制代码
<template>
  <view>count:{{count}} <button @click="add">+1</button></view>
</template>

<script>
import {ref, onMounted} from 'vue'
export default {
  // 普通setup,是组件的一个选项
  setup(props, context) {
    const count = ref(0)
    const add = () => {
      count.value++
    }
    onMounted(()=>{
      console.log('挂载了')
    })
    // ⚠️ 必须return!模板才能拿到count、add
    return {
      count,
      add
    }
  }
}
</script>

② <script setup> 语法糖

官网:cn.vuejs.org/api/sfc-scr... 本质:编译阶段自动帮你生成上面的普通 setup 代码,自动 return。 不需要手动 return;顶层变量、函数,模板直接可用。

xml 复制代码
<template>
  <view>count:{{count}} <button @click="add">+1</button></view>
</template>
<script setup>
import {ref,onMounted} from 'vue'
const count = ref(0)
const add = ()=> count.value++
onMounted(()=>console.log('挂载'))
// ✅ 不用return
</script>

两者对比

表格

普通 setup
在export default里面,作为选项 单独标签,sfc 语法糖
必须手动 return 变量 / 方法 自动暴露,不用 return
可以写在选项式组件里,支持和 data/methods 共存 不能和普通<script>export default 直接混用在同一个 script 块
需要手动 context.emit() 用defineEmits宏

为什么很少见到普通 setup?因为写起来麻烦,每个变量都要 return,业务项目都直接上 script setup。但是理解普通 setup,你才能看懂组合式底层。


2. import {} from ''

csharp 复制代码
import {ref, reactive, onMounted} from 'vue'

ES6 模块语法,解构导入。

  • vue:包名,从 vue 这个库里面导入;
  • {ref, reactive}:代表只导入 vue 里面ref、reactive 这几个函数,不是全部导入。

等价理解:vue 内部导出了一堆函数

javascript 复制代码
// vue源码简化理解
export const ref = ()=>{}
export const reactive = ()=>{}
export const onMounted = ()=>{}

你用大括号,只拿你需要的。

两种导入对比:

csharp 复制代码
// 1. 解构导入(最常用,你一直看到的)
import {ref} from 'vue'

// 2. 全部导入(不推荐,体积大,写起来麻烦)
import * as Vue from 'vue'
const count = Vue.ref(0)

简单记:组合式 API 的所有工具函数,都需要手动 import 导入;选项式不用,因为选项式是 Vue 自动挂到 this。


3. 组合式:业务抽离复用(demo + 解释)

核心思想:把一组关联的变量、函数、监听打包成一个函数,导出,在多个组件导入使用。

普通 setup 和 script setup 都可以使用抽离的逻辑。

新建文件 useCounter.js(hooks 文件)

javascript 复制代码
// useCounter.js 抽离复用逻辑
import {ref, computed, watch} from 'vue'

export function useCounter(initVal = 0) {
  // 这一组变量、方法,全部是【同一个业务】
  const count = ref(initVal)
  const doubleCount = computed(()=> count.value *2)

  const add = ()=> count.value++
  const reduce = ()=> count.value--

  watch(count, (newVal)=>{
    console.log('count变化', newVal)
  })

  // 把需要对外使用的变量函数return出去
  return {
    count,
    doubleCount,
    add,
    reduce
  }
}

组件 A 使用:

xml 复制代码
<!-- A.vue 普通setup版本 -->
<script>
import { useCounter } from './useCounter.js'
export default {
  setup(){
    // 直接拿到一整套逻辑
    const {count,doubleCount,add,reduce} = useCounter(10)
    return {count,doubleCount,add,reduce}
  }
}
</script>

组件 B 使用:

xml 复制代码
<!-- B.vue script setup -->
<script setup>
import { useCounter } from './useCounter.js'
const {count,doubleCount,add,reduce} = useCounter(0)
</script>

✅ 复用特点:

  • 每个组件调用useCounter(),都会生成独立的响应式数据,组件之间互不干扰;
  • 选项式的 mixins 最大坑:命名冲突,不知道变量来自哪个 mixin;hooks 没有这个问题,变量显式导入。

4. ref /reactive 详细用法

✅ 使用前,都必须 import

csharp 复制代码
import {ref, reactive} from 'vue'

ref

ref 接收任意值,返回一个包装对象,.value存真实数据。

  • 基础类型:数字、字符串、布尔
dart 复制代码
const num = ref(10)
num.value = 20  // js修改要.value
console.log(num.value)
//模板直接 {{num}},不用.value

ref也可以放对象、数组(只是一般对象推荐 reactive)

ini 复制代码
const user = ref({name:'张三'})
user.value.name = '李四' // ref存对象,修改必须 .value.xxx

reactive

专门用来包装对象、数组 ,返回响应式代理,不需要.value

ini 复制代码
// 对象
const user = reactive({
  name:'张三',
  age:20
})
user.age = 21
user.name = '李四'

// 数组
const list = reactive([1,2,3])
list.push(4)
list[0] = 100

⚠️ reactive 限制:

  1. 不能直接整体替换!会丢失响应式
ini 复制代码
const user = reactive({name:'张三'})
// ❌ 错误,直接覆盖,丢失响应式
user = reactive({name:'李四'}) 

// ✅ 正确,修改属性
user.name = '李四'

如果需要整体替换对象,推荐用 ref 存对象。

选型口诀

  • 简单基础类型(数字字符串布尔)👉 ref
  • 对象 / 数组,不需要整体替换 👉 reactive
  • 对象,未来可能直接替换整个对象 👉 ref

5. ref & reactive 核心原理(入门理解)

Vue3 响应式底层基于 Proxy。

  1. reactive:直接对传入对象做 Proxy 代理。返回代理对象。所以直接操作属性。
  2. ref:因为 Proxy不能代理基础类型 (数字、字符串不是对象),所以把基础值包装成一个{ value:xxx }对象,再给这个对象做 proxy。 所以读取修改必须.value。

模板里面 Vue 自动做 ref 自动解包,模板写 {{count}},不用 count.value。
小知识点:ref 里面如果传入对象,内部底层会自动调用 reactive 包装这个对象。


6. hooks 是什么,怎么写怎么用

hooks(vue 组合式 hooks):以 use 开头的函数,封装一组响应式状态 + 相关逻辑 就是上面useCounter例子。 命名约定:useXxx,约定俗成,方便识别。

特点:

  1. 普通 js 函数;
  2. 内部使用 ref/reactive/ 生命周期等组合式 api;
  3. 返回响应式变量和方法;
  4. 组件导入调用,实现逻辑复用。

注意:hooks只能在 setup 函数内部调用(普通 setup 或者 script setup 都可以,不能放在普通函数、定时器顶层全局)

例子:useList 列表加载 hooks

javascript 复制代码
// useList.js
import {ref} from 'vue'
export function useList(){
  const list = ref([])
  const loading = ref(false)
  async function fetchData(){
    loading.value=true
    //模拟请求
    const res = await new Promise(res=> setTimeout(()=>res([1,2,3]),500))
    list.value = res
    loading.value=false
  }
  return {list,loading,fetchData}
}

组件使用:

xml 复制代码
<script setup>
import {useList} from './useList'
const {list,loading,fetchData} = useList()
fetchData()
</script>

7. setup(){} 混合写法详解(选项式里面写 setup)

官网链接:cn.vuejs.org/api/composi...

xml 复制代码
<script>
import {ref} from 'vue'
export default {
  data(){
    return {
      b:2
    }
  },
  setup(){
    const a = ref(1)
    return {a}
  }
}
</script>

解读:

  1. 这是选项式组件,export default 是选项配置对象;
  2. setup只是其中一个选项,和 data 同级;
  3. setup 内部写组合式 API;return 出来的变量,会挂载到组件实例,和 data 里面的变量合并;
  4. 模板里{{a}}、{{b}}都可以访问;
  5. setup 执行时机早于 data;
  6. setup 里没有 this ,setup 内部不能访问this.b;
  7. 业务不推荐这么写:两套写法混在一起,阅读成本翻倍,容易混乱,学习看懂即可。

用途:老 Vue3 选项式项目,想局部使用组合式逻辑,才会这么写。新项目不要混合。


8. <script setup> props、emit demo

官网:cn.vuejs.org/api/sfc-scr...

子组件(接收 props,触发 emit)Child.vue

xml 复制代码
<template>
  <view>
    <p>父传的值:{{title}}</p>
    <button @click="handleClick">通知父组件</button>
  </view>
</template>
<script setup>
// 宏,接收props
const props = defineProps({
  title: {
    type:String,
    required:true
  }
})

// 宏,声明可以触发的事件
const emit = defineEmits(['change'])

const handleClick = ()=>{
  emit('change','子组件传给父的数据')
}
</script>

父组件 Parent.vue

xml 复制代码
<template>
  <Child title="hello" @change="onChildChange"></Child>
</template>
<script setup>
import Child from './Child.vue'
const onChildChange = (val)=>{
  console.log('收到子组件消息',val)
}
</script>

9. defineExpose 讲解 + demo

官网:cn.vuejs.org/api/sfc-scr...

<script setup>默认:组件内部所有变量、方法对外完全私有 。父组件通过 ref 拿到子组件实例,默认访问不到子组件里面的函数 。 defineExpose:把你想暴露给父组件的方法 / 变量显式导出。

子组件 Child.vue

xml 复制代码
<template>
  <view>子组件</view>
</template>
<script setup>
const testFn = ()=>{
  console.log('子组件方法执行')
}
const innerVal = ref(100)

// 暴露给父组件
defineExpose({
  testFn,
  innerVal
})
</script>

父组件(通过 ref 调用子组件方法)

xml 复制代码
<template>
  <Child ref="childRef"></Child>
  <button @click="callChild">调用子组件方法</button>
</template>
<script setup>
import {ref} from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
const callChild = ()=>{
  childRef.value?.testFn()
}
</script>

👉 就是你 uniapp right-window 那段代码用到的!

10. 什么是宏(macro)?defineProps /defineEmits/defineExpose 为什么叫宏?

宏:编译期宏,不是运行时 js 函数 。 在<script setup>里面:defineProps、defineEmits、defineExpose 特点:

  1. 不需要 import 导入,直接使用;
  2. 不是 js 运行时函数,是 Vue 编译器在编译模板阶段做处理;
  3. 编译完成之后,这些defineXxx代码直接消失,不会打包进最终 js;

普通 js 函数是浏览器运行的时候执行;宏是打包编译阶段处理。

只有在 <script setup> 里面可以直接使用这些宏;普通 setup 函数里不能用宏,普通 setup 要用 props 参数、context.emit。

一句话区分:

  • 普通 API:import {ref} from 'vue' 运行时函数;
  • 宏:defineProps,编译期语法,无需 import。

11. 学习路线:先学普通 setup,再学 script setup,怎么学

目标:看懂组合式本质,而不是只会写语法糖。按下面顺序练习,不要跳。

阶段 1:只写普通 setup(不碰 script setup,1~3 天练习)

练习任务,全部使用普通 setup(export default + setup 函数)

  1. 计数器 ref,return 变量
  2. 用 reactive 写对象,修改对象属性
  3. computed、watch
  4. onMounted 生命周期
  5. 学习 setup 参数:props, context.emit
  6. 写一个简单 hooks,在 setup 里面导入使用

重点体会:

  • setup 没有 this
  • 所有变量必须 return模板才能看见
  • 所有 api 都要 import

阶段 2:理解普通 setup 之后,再切换到<script setup>

对比同一个页面,分别写普通 setup 版本 和 script setup 版本,观察差异:

  • 不用手动 return
  • 宏 defineProps/defineEmits/defineExpose
  • 自动暴露顶层变量

阶段 3:练习 ref 获取子组件实例 + defineExpose(就是你 uniapp 场景)

阶段 4:uniapp 项目实战,全部 script setup

避坑

不要一上来直接写 script setup,很多人只会复制代码,不懂底层 ref、实例暴露、setup 执行顺序,遇到问题就懵,就像你最开始看 right-window 代码一样。


相关推荐
deli0071 小时前
1 个汉字算几个 token?实测中英文 prompt 与代码的编码差异
前端
无限聚核1 小时前
抖音 certificate.prepare 之后怎么验券?和美团 msconsume 差在哪,无限聚核
前端·微信小程序
2603_965896622 小时前
Grid 网格布局|CSS 二维页面排版体系
前端·css
有味道的男人2 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft2 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly3 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝3 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦3 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡3 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式