随便写写之Vue 3.2setup语法糖

2,514 阅读6分钟

学习前提:

最初 Vue3.0 暴露出来变量是必须要 return 出来,才能在template 中使用。在Vue3.2 之后,只需要在script 标签加上 setup 属性,组件在编译的过程中代码运行的上下文是在 setup()函数中的。无需return , 在template 中可以直接使用。本文通过Vue 2.x 的角度展开对Vue3.2 语法的学习和了解。

1. 实现data数据的修改

<script setup>
  import { reactive, ref, toRefs } from 'vue'

  // ref声明响应式数据,用于声明基本数据类型; 修改value属性实现变量修改
  const name = ref('小明')
  name.value = '小华'

  // reactive声明响应式数据,用于声明引用数据类型;直接修改对应属性值
  const state = reactive({
    name: '老王',
    sex: '男'
  })
  state.name = '老张'
  
  // 使用toRefs解构;template可直接使用{{name}}、{{sex}}
  const {name, sex} = toRefs(state)
</script>

2. method方法和computed计算方法的用法

<template>
  <button @click='changeInfo'>点我触发</button>  
</template>

<script setup>
  import { reactive, computed, ref } from 'vue'

  const num = ref(1)
  const state = reactive({
    message: '未改变'
  })

  // 声明method方法 changeInfo,触发数据修改
  const changeInfo = () => {
    state.message = '改变了'
  } 
  
   // 通过computed获得新的数据 newNum
  const newNum = computed(() => {
    return num.value * 2
  })
</script>

3.watch 监听方法

watch 的第三个参数:

  1. deep: 深度监听
  2. immediate: 作用就是设置是否立即执行监控,当值设置为 true 时,那么被监控的对象在初始化时就会触发一次 watch 方法。
<script setup>
  import { watch, reactive, ref } from 'vue'
  const name = ref('jack')
  const age = ref(21)  
  const person = reactive({
    name: 'kobe',
    infos: {
      age: 28,
      address: '上海'
    }
  })
  

// 监听单个普通类型
watch(name, (newVal, oldVal) => { 
  console.log(newVal)
})

// 监听多个普通类型,返回数组
watch([name, age], (newVal, oldVal) => { 
  console.log(newVal)
})

// 监听对象person时,vue3将强制开启deep深度监听
watch(person, (newVal, oldVal) => { 
  console.log(newVal)
})
watch(() => person, (newVal, oldVal) => { 
  console.log(newVal)
}, { deep: true })

// 只有当person对象中的name属性发生变化才会触发watch方法
watch(() => person.name, (newVal, oldVal) => { 
  console.log(newVal)
})

// 注意:监听对象的属性为复杂数据类型时,需要开启deep深度监听
watch(() => person.infos, (newVal, oldVal) => { 
  console.log(newVal)
}, { deep: true })
</script>

4.父组件传参给子组件 - props; 子组件传参给父组件 - emit

父组件:

<template>
  <child :name='state.name' @sendMsg='sendMsg' />  
</template>

<script setup>
  import { reactive } from 'vue'
  import child from './child.vue'
  
  const state = reactive({
    name: '父亲'
  })
  
  // 接收子组件触发的方法
  const sendMsg = (name) => {
    state.name = name
  }
</script>

子组件:

<template>
  <span>{{props.name}}</span>
  // 可省略【props.】
  <span>{{name}}</span>
  <button @click='sendToFather'>点我传值给父组件</button>
</template>

<script setup>
  import { defineEmits, defineProps } from 'vue'
  
  // 声明props,接受父组件传值
  const props = defineProps({
    name: {
      type: String,
      default: ''
    }
  }) 
  
  // 声明事件
  const emit = defineEmits(['sendMsg'])
  // 触发传值的方法,传值给父组件
  const sendToFather = () => {
    emit('sendMsg', '儿子')
  }
</script>

5.v-model 双向绑定

  • 支持绑定多个v-modelv-model 是 v-model:modelValue 的简写
  • 绑定其他字段,如:v-model:name

父组件:

<template>
  // v-model:modelValue简写为v-model
  // 可绑定多个v-model
  <child
    v-model="state.name"
    v-model:age="state.age"
  />
</template>

<script setup>
  import { reactive } from 'vue'
  // 引入子组件
  import child from './child.vue'

  const state = reactive({
    name: '老王',
    age: 30
  })
</script>

子组件:

<template>
  <span @click="changeInfo">我叫{{ modelValue }},今年{{ age }}岁</span>
</template>

<script setup>
  import { defineEmits, defineProps } from 'vue'
  defineProps({
    modelValue: String,
    age: Number
  })

  const emit = defineEmits(['update:modelValue', 'update:age'])
  const changeInfo = () => {
    // 触发父组件值更新
    emit('update:modelValue', '小明')
    emit('update:age', 10)
  }
</script>

6.ref子组件实例和defineExpose

  • 在 script-setup 模式下,所有数据只是默认 return 给 template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose 来完成

子组件:

<template>
  <span>{{state.name}}</span>
</template>

<script setup>
  import { reactive, toRefs } from 'vue'
  import { defineExpose, reactive, toRefs } from 'vue'

  // 声明state
  const state = reactive({
    name: '小明'
  }) 
	
  // 将方法、变量暴露给父组件使用,父组件才可通过ref API拿到子组件暴露的数据
  defineExpose({
    // 解构state
    ...toRefs(state),
    // 声明方法
    changeName () {
      state.name = '老王'
    }
  })
</script>
  1. 获取一个子组件的实例:
<template>
  <child ref='childRef'/>
</template>

<script setup>
  import { ref, nextTick } from 'vue'
  import child from './child.vue'

  // 子组件ref
  const childRef = ref(null)
  
  // nextTick
  nextTick(() => {
    // 获取子组件name
    console.log(childRef.value.name)
    // 执行子组件方法
    childRef.value.changeName()
  })
</script>

2.获取多个子组件实例:在v-for中获取子组件的实例

  • 这种情况仅适用于 v-for 循环数是固定的情况。如果 v-for 循环数 在初始化之后发生改变,那么就会导致 childRefs 再一次重复添加,childRefs中会出现重复的子组件实例
<template>
  <div v-for="item in 3" :key="item">
    <child :ref='addChildRef'/>
  </div>
</template>

<script setup>
  import { ref } from 'vue'
  import child from './child.vue'
  
  // 子组件实例数组
  const childRefs = ref([])
  
  // 通过 addChildRef 方法向 childRefs 添加子组件实例
  const addChildRef = (el) => {
    childRefs.value.push(el)
  }
</script>

3.获取多个子组件实例:动态 v-for 获取子组件实例:

<template>
  <button @click='childNums++'></button>
  <div v-for="(item, i) in childNums" :key="item">
    // 通过下标向 childRefs 动态添加子组件实例
    <child :ref='(el) => childRefs[i] = el'/>
  </div>
  <button @click='childNums--'></button>
</template>

<script setup>
  import { ref } from 'vue'
  import child from './child.vue'
  
  // 子组件数量
  const childNums = ref(1)
  
  // 子组件实例数组
  const childRefs = ref([])
</script>

7.插槽 slot

在 Vue2 中, 一般中具名插槽和作用域插槽分别使用slotslot-scope来实现,如下为vue2 插槽的使用例子:

// 父组件
<template>
  <div>
    <p style="color:red">父组件</p>
    <Child>
      <template slot="content" slot-scope="{ msg }">
        <div>{{ msg }}</div>
      </template>
    </Child>
  </div>
</template>
<script lang="ts" setup>
import Child from './Child.vue'
</script>
// 子组件
<template>
  <div>child</div>
  <slot name="content" msg="我是子组件内容"></slot>
</template>

在 Vue3 中slotslot-scope进行了合并统一使用,使用 v-slotv-slot:slotName简写成#slotName

// 父组件
<template>
  <child>
    // 匿名插槽
    <span>我是默认插槽</span>
    
    // 具名插槽
    <template #title>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
    </template>
    
    // 作用域插槽
    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
    </template>
  </child> 
</template>

<script setup>
  // 引入子组件
  import child from './child.vue'
</script>
// 子组件
<template>
  // 匿名插槽
  <slot/>
  // 具名插槽
  <slot name='title'/>
  // 作用域插槽
  <slot name="footer" :scope="state" />
</template>

<script setup>
  import { useSlots, reactive } from 'vue'
  const state = reactive({
    name: '张三',
    age: '25岁'
  })
  const slots = useSlots()
  
  // 匿名插槽使用情况
  const defaultSlot = reactive(slots.default && slots.default().length)
  console.log(defaultSlot) // 1
  
  // 具名插槽使用情况
  const titleSlot = reactive(slots.title && slots.title().length)
  console.log(titleSlot) // 3
</script>

8.原型绑定和组件内使用

main.js中配置:

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

// 获取原型
const prototype = app.config.globalProperties

// 绑定参数
prototype.name = '小明'

在组件中使用:

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

  // 获取原型
  const { proxy } = getCurrentInstance()
  
  // 输出
  console.log(proxy.name)
</script>

9. provide和inject

父组件:

<template>
  <child/>
</template>

<script setup>
  import { ref, watch, provide } from 'vue'
  import child from './child.vue'
  let name = ref('老王')
  
  // 声明provide
  provide('provideState', {
    name,
    changeName: () => {
      name.value = 'Tom'
    }
  })

  // 监听name改变
  watch(name, () => {
    console.log(`name变成了${name}`)
    setTimeout(() => {
      console.log(name.value) // Tom
    }, 1000)
  })
</script>

子组件:

<script setup>
  import { inject } from 'vue'
  // 注入,第二个参数为默认值
  const provideState = inject('provideState', {})
  
  // 子组件触发name改变
  provideState.changeName()
</script>

10.自定义指令

vue 3.0的自定义指令

const app = createApp({})

// 使 v-demo 在所有组件中都可用
app.directive('demo', {
  // 在绑定元素的 attribute 前或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {},
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
})

自定义指令实现测试生命周期:

app.directive('testLifeCycle', {
   // 在绑定元素的attribute前或事件监听器应用前调用
   created(el, binding, vnode, prevVnode) {},
   // 在元素被插入到 DOM 前调用
   beforeMount(el, binding, vnode, prevVnode) {},
   // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
   mounted(el, binding, vnode, prevVnode) {
       console.log(binding);
       console.log(binding.modifiers);
       if (Object.keys(binding.modifiers).length == 0) {
           alert('说明没有传修饰符');
           return false;
       }
       alert('说明传了修饰符')
   },
   // 绑定元素的父组件更新前调用
   beforeUpdate(el, binding, vnode, prevVnode) {},
   // 在绑定元素的父组件及他自己的所有子节点都更新后调用
   updated(el, binding, vnode, prevVnode) {},
   // 绑定元素的父组件卸载前调用
   beforeUnmount(el, binding, vnode, prevVnode) {},
   // 绑定元素的父组件卸载后调用
   unmounted(el, binding, vnode, prevVnode) {}
});

<div v-testLifeCycle>测试生命周期<div>