【VUE3】组合式API

目录

0前言

1 使用create-vue创建项目

2 项目配置

2.1 更改vscode插件

3 setup选项

3.1 setup写法

3.2 执行时机

4 响应式数据

4.1 reactive函数

4.2 ref函数

4.3 对比

5 computed 计算属性

6 watch 侦听数据变化

6.1 侦听单个数据

6.2 侦听多个数据

6.3 额外配置

6.3.1 immediate

6.3.2 deep

6.4 侦听对象数据的某一个属性

7 生命周期函数

7.1 选项式API与组合式API对比 

7.2 生命周期函数的使用

7.3 执行多次

8 组件通信 - 父子

8.1 父传子

8.2 子传父

8.3 v-model

8.4 v-model:自定义属性名 

9 模板引用

9.1 获取元素

9.2 获取组件

10 跨层级通信

10.1 向后代传值

10.2 后代修改值(向后代传函数方法)

11 新特性

11.1 defineOptions

11.2 defineModel


0前言

vue3官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js

请多阅读官方文档 

黑马程序员视频地址:140-vue3-为什么要学vue3_哔哩哔哩_bilibili

8.3、8.4以及11.2若有错误请指正,感谢!!!


1 使用create-vue创建项目

1.1 确认node版本

node -v

版本要在v18.3.0及以上(随着create-vue版本迭代,可能需要更高版本的node)

官网:Node.js — Download Node.js®,更多细节见 node.js安装-CSDN博客

1.2 执行如下命令,这一指令将会安装并执行 create-vue

npm init vue@latest

2 项目配置

2.1 更改vscode插件

禁用 vetur ,改为 volar(即Vue - Official)


3 setup选项

3.1 setup写法

完整写法:(不推荐) 

<script>
  export default {
    setup(){
      const message = 'this is message'
      const logMessage = ()=>{
        console.log(message)
      }
      // 必须return才可以
      return {
        message,
        logMessage
      }
    }
  }
</script>

语法糖:(推荐)

<script setup>
  const message = 'this is message'
  const logMessage = ()=>{
    console.log(message)
  }
</script>

3.2 执行时机

在beforeCreate钩子之前执行,非常早,以至于没有this指向组件实例


4 响应式数据

setup内声明的数据不是响应式的,可以使用reactive与ref函数来改成响应式的

4.1 reactive函数

注意:只能讲对象类型的数据转成响应式的

机理:接受对象类型数据的参数传入并返回一个响应式的对象

<script setup>
 // 导入
 import { reactive } from 'vue'
 // 执行函数 传入参数 变量接收
 const state = reactive({
   msg:'this is msg'
 })
 const setSate = ()=>{
   // 修改数据更新视图
   state.msg = 'this is new msg'
 }
</script>

<template>
  {{ state.msg }}
  <button @click="setState">change msg</button>
</template>

4.2 ref函数

注意:既支持对象类型的数据,也支持简单类型的数据

机理:接收简单类型或者对象类型的数据传入并返回一个响应式的对象

<script setup>
 // 导入
 import { ref } from 'vue'
 // 执行函数 传入参数 变量接收
 const count = ref(0)
 const setCount = ()=>{
   // 修改数据更新视图必须加上.value
   count.value++
 }
</script>

<template>
  <button @click="setCount">{{count}}</button>
</template>

注意点:

1. 在 script 中,要通过 变量名.value 访问数据(简单类型与对象类型同理,简单类型是把数据传给响应式对象的value属性中,而对象数据类型会把整个对象传入响应式对象的value属性中)

2. 在 template 中,可以直接直接使用变量名访问数据


4.3 对比

1. 都是用来生成响应式数据
2. 不同点
   1. reactive不能处理简单类型的数据
   2. ref参数类型支持更好,但是必须通过.value做访问修改
   3. ref函数内部的实现依赖于reactive函数
3. 在实际工作中的推荐
   1. 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref


5 computed 计算属性

示例:

<script setup>
// 导入
import {ref, computed } from 'vue'
// 原始数据
const count = ref(0)
// 计算属性
const doubleCount = computed(()=>count.value + 2)
console.log(doubleCount.value)

// 原始数据
const list = ref([1,2,3,4,5,6,7,8])

// 计算属性list
const filterList = computed(() => {
  return list.value.filter(item => item > 2)
})

</script>

<template>
  <div>原数组:{{ list }}</div>
  <div>新数组:{{ filterList }}</div>
</template>

注意:

1.避免直接修改计算属性中的值,如果真要修改,请见文档使用方法:响应式 API:核心 | Vue.js

2. 避免在计算属性中使用dom操作或异步请求

示例:

创建一个只读的计算属性 ref: 

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

 创建一个可写的计算属性 ref:

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

6 watch 侦听数据变化

6.1 侦听单个数据

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  // 2. 调用watch 侦听变化
  watch(count, (newValue, oldValue)=>{
    console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
  })
</script>

6.2 侦听多个数据

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  const name = ref('cp')
  // 2. 调用watch 侦听变化
  watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
    console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName])
  })
</script>

6.3 额外配置

6.3.1 immediate

在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  // 2. 调用watch 侦听变化
  watch(count, (newValue, oldValue)=>{
    console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
  },{
    immediate: true
  })
</script>

6.3.2 deep

通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const state = ref({ count: 0 })
  // 2. 监听对象state
  watch(state, ()=>{
    console.log('数据变化了')
  })
  const changeStateByCount = ()=>{
    // 直接修改不会引发回调执行
    state.value.count++
  }
</script>

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const state = ref({ count: 0 })
  // 2. 监听对象state 并开启deep
  watch(state, ()=>{
    console.log('数据变化了')
  },{deep:true})
  const changeStateByCount = ()=>{
    // 此时修改可以触发回调
    state.value.count++
  }
</script>

也可以不使用deep,只要把state改写成getters的形式 () => state.value,即可

  • watch(state, callback):这种方式直接将 ref 对象作为监听目标。Vue 会对 ref 对象本身的引用变化进行监听。只有当整个 ref 对象被替换成一个新的 ref 时,监听器才会触发。
  • watch(() => state, callback):这里传入的是一个返回 ref 对象的箭头函数。本质上和直接传入 state 类似,同样是监听 ref 对象本身的引用变化。
  • watch(() => state.value, callback):此方式传入的箭头函数返回 ref 对象的 value 属性值。所以它监听的是 ref 对象所存储的实际数据的变化,只要 value 属性值发生改变,监听器就会触发。

示例见 6.4(6.4本质上是把state换成了 () => state.value.count的getters写法)


6.4 侦听对象数据的某一个属性

开启deep,会侦查整个对象数据,任意属性值都会触发回调函数,如果只想要侦听某个属性,则需要下面的写法: 

const state = ref({ count: 0 })
watch(
  () => state.value.count,
  (newvalue, oldvalue) => {
    /* ... */
  }
)

如果要侦听某两个或多个属性,则可以通过一下两种方法:

1.使用数组

const state = ref({ count: 0, name: 'initial' })

watch(
  [
    () => state.value.count, // 第一个属性
    () => state.value.name  // 第二个属性
  ],
  ([newCount, newName], [oldCount, oldName]) => {
    // 新值数组 [newCount, newName]
    // 旧值数组 [oldCount, oldName]
    console.log('count 变化:', newCount, 'old:', oldCount)
    console.log('name 变化:', newName, 'old:', oldName)
  }
)

2. 分别编写独立的侦听器

const state = ref({
     count1: 0,
     count2: 1,
     count3: 2
})
watch(
  () => state.value.count1,
  (newvalue, oldvalue) => {
    /* ... */
  }
)
watch(
  () => state.value.count2,
  (newvalue, oldvalue) => {
    /* ... */
  }
)

7 生命周期函数

7.1 选项式API与组合式API对比 

vue3中的选项式api由distoryed变成了unmounted


7.2 生命周期函数的使用

  1. 导入生命周期函数

  2. 执行生命周期函数,传入回调

<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
  // 自定义逻辑
})
</script>

7.3 执行多次

生命周期函数执行多次的时候,会按照顺序依次执行

<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
  // 自定义逻辑
})

onMounted(()=>{
  // 自定义逻辑
})
</script>

8 组件通信 - 父子

8.1 父传子

第一步:在父组件中给子组件绑定值

<script setup>
// 父组件
// 导入子组件
import sonCom from './components/son-com.vue';
// 导入 ref 函数
import { ref } from 'vue'
// 定义 money 变量
const money = ref(100)
</script>

<template>
  <div>
    我是父组件
    <sonCom :money="money"></sonCom>
  </div>
</template>

<style scoped>

</style>

第二步:在子组件中编译器宏生成 props 并接收数据

<script setup>
// 子组件
// 编译器宏生成 props 并接收数据
const props = defineProps({
  money: Number
})
// 打印money
console.log(props.money)
</script>

<template>
  <div class="son">
    我是子组件 - {{ money }}
  </div>
</template>

<style scoped>
  .son{
    border: 1px solid black;
    width: 300px;
    height: 300px;
  }
</style>

8.2 子传父

第一步:子组件编译器宏生成 emit 函数,并触发事件,传值

<script setup>
// 子组件
const props = defineProps({
  money: Number
})
console.log(props)
// ————————————————————————————————————————
// 1、编译器宏生成 emit
const emit = defineEmits(['spendMoney'])
// 2、触发事件,传数据
const buy = () => {
  emit('spendMoney', 10)
}
</script>

<template>
  <div class="son">
    我是子组件 - {{ money }}
    <button @click="buy">花钱</button>
  </div>
</template>

<style scoped>
  .son{
    border: 1px solid black;
    width: 300px;
    height: 300px;
  }
</style>

第二步:父组件监听事件,触发函数

<script setup>
// 父组件
import sonCom from './components/son-com.vue';
import { ref } from 'vue'
const money = ref(100)
// ——————————————————————————————————————
// 定义方法
const spendMoney = (num) => {
  money.value -= num
}
</script>

<template>
  <div>
    我是父组件
    <!--监听 spendMoney 事件,并触发相应方法-->
    <sonCom :money="money" @spendMoney="spendMoney"></sonCom>
  </div>
</template>

<style scoped>

</style>

8.3 v-model

用 v-model 简化父组件中的写法

缺点:属性值与监听事件固定,解决方法见8.4 

 vue2中的v-model本质上是:value和@input

而vue3变成了:modelValue和@update:modelValue

写法如下: 

<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
</script>

<template>
  <div>
    我是父组件
  </div>
  <model v-model="num"></model>
</template>
<!--model.vue-->
<script setup>
defineOptions({
  name: 'modelPage'
})
defineProps({
  modelValue: Number
})
const emit = defineEmits(['@update:modelVale'])
</script>

<template>
  <div>
    从父组件获取的数据 - {{ modelValue }}
    <button @click="emit('update:modelValue', modelValue + 1)">
      子组件按钮 - 加1
    </button>
  </div>
</template>

在父组件中,v-model等效于

<model :modelValue="num" @update:modelValue="num = $event" />
<!--$event为子组件传过来的值-->

可以发现,默认监听事件会执行赋值操作

如果不想要只做赋值操作,可以再在父组件中单独写监听update:modelValue事件,如

<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {    // newNum即子组件传过来的修改过的值
    // 可以先执行其他操作
    num.value = newNum    // 这里才真正把父组件中的值进行修改
}
</script>

<template>
  <div>
    我是父组件
  </div>
  <model v-model="num" @update:modelValue="changeNum"></model>
</template>

8.4 v-model:自定义属性名 

在vue3中,可以直接通过写v-model:属性名进行设置自定义属性名,如

<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {
    num = newNum
}
</script>

<template>
  <div>
    我是父组件
  </div>
  <model v-model:number="num" @update:number="changeNum"></model>
</template>
<!--model.vue-->
<script setup>
defineOptions({
  name: 'modelPage'
})
defineProps({
  number: Number
})
const emit = defineEmits(['@update:number'])
</script>

<template>
  <div>
    从父组件获取的数据 - {{ number }}
    <button @click="emit('update:number', number + 1)">
      子组件按钮 - 加1
    </button>
  </div>
</template>

9 模板引用

9.1 获取元素

第一步:创建一个空 ref 对象

import { ref } from 'vue'
const getEle = ref(null)

第二步:给元素绑定 ref 属性

<span ref="getEle">{{ num }}</span>

注意:ref 的值要与 ref 对象变量名同名


9.2 获取组件

与获取元素一样

第一步:创建一个空 ref 对象

import { ref } from 'vue'
const getCom = ref(null)

第二步:给组件绑定 ref 属性

<son ref="getCom"></son>

注意: <script setup>语法糖下组件内部的属性与方法是不开放给父组件访问的,需要在组件内使用defineExpose编译宏,如:

<script setup>
import {ref} from 'vue'
const num = ref(1)
defineExpose({
  num
})
</script>

10 跨层级通信

10.1 向后代传值

第一步:父组件通过 provide 提供数据

<script setup>
  import { provide } from 'vue'
  import sonOne from '@/components/sonOne.vue'
  const a = 1
  provide('num', a)
</script>

传响应式数据也一样 

 第二步:后代组件通过 inject 获取数据

<script setup>
import { inject } from 'vue'

const num = inject('num')
</script>

10.2 后代修改值(向后代传函数方法)

遵循谁的数据谁维护,所以传方法下去,让其调用父组件的方法来修改父组件的数据 

步骤一:父组件通过 provide传函数方法

<script setup>
  import { provide } from 'vue'
  import sonOne from '@/components/sonOne.vue'
  const sayHi = () => {
    console.log('你好')
  }
  provide('key', sayHi)
</script>

 第二步:后代组件通过 inject 获取函数方法

// sonTwo.vue
<script setup>
import { inject } from 'vue'

const sayHi = inject('key')
</script>

 步骤三:调用函数方法

<!--sonTwo.vue-->
<template>
  <div> 我是子组件2 </div>
  <button @click="sayHi">sayHi</button>
</template>

11 新特性

11.1 defineOptions

由于用了setup语法糖,没有地方配置name等属性,所以官方提供了这个方法

可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)

<script setup>
  defineOptions({
    name: 'indexPage'
    // ...
  })
</script>

11.2 defineModel

8.3、8.4中的方法还是过于麻烦,可以使用 defineModel 简化子组件中的代码

8.3 默认写法 (:modelValue 与 @update:modelValue):

父组件不用改内容:

<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {    // newNum即子组件传过来的修改过的值
    num.value = newNum    // 这里才真正把父组件中的值进行修改
}
</script>

<template>
  <div>
    我是父组件
  </div>
  <model v-model="num" @update:modelValue="changeNum"></model>
</template>

子组件代码如下: 

发现不用写defineProps与defineEmits,因为defineModel都会帮我们处理

<!--model.vue-->
<script setup>
defineOptions({
  name: 'modelPage'
})
const num = defineModel() // 默认会找modelValue,这里的num是随便命名的
console.log(num.value)
</script>

<template>
  <div>
    从父组件获取的数据 - {{ num }}
    <button @click="num++">    <!--这里并不会真正修改父组件中的值,而是将修改后的值传给父组件-->
      子组件按钮 - 加1
    </button>
  </div>
</template>

定义的num只要被修改,就会触发父组件中的监听函数

8.4 自定义写法:同理,只需要在defineModel括号中额外传属性名称即可

更多参数见官网:<script setup> | Vue.js 

<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {
    num = newNum
}
</script>

<template>
  <div>
    我是父组件
  </div>
  <model v-model:number="num" @update:number="changeNum"></model>
</template>
<!--model.vue-->
<script setup>
const num1 = defineModel("number")    // 与num绑定
</script>

<template>
  <div>
    从父组件获取的数据 - {{ num1 }}
    <button @click="num1++">    <!--也可以在script中修改,但是修改值要写成num1.value-->
      子组件按钮 - 加1
    </button>
  </div>
</template>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值