Vue3中setup语法糖使用总结

999 阅读1分钟

Vue3.2 中引入了setup语法糖, 只需要在 script 标签上加上 setup 属性,无需 return,可以在template模板中直接使用。

data使用

<template>
  <h2>{{msg}}</h2>
  <h2>{{data.age}}</h2>
  <h2>{{age}}</h2>
</template>
<script setup lang="ts">
// 按需引用
import { reactive, ref, toRefs } from "vue";

// ref:定义基本数据类型
const msg = ref('欢迎学习vue3')
// 修改值
msg.value = 'vue3真香'

// reactive:定义引用数据类型
const person = reactive({
  name: '花颜',
  age: 18
})
// 修改值
person.name = '水门'

// 结构赋值后,模板中可以直接使用
const { age, name } = toRefs(person)
</script>

methods使用

定义函数,直接使用

<template>
  <h1>{{msg}}</h1>
  <button @click="changeMsg">改变值</button>
</template>
<script setup lang="ts">
import { ref } from "vue";

const msg = ref('欢迎学习vue3')

//  使用箭头函数定义方法(普通函数也可以)
const changeMsg = () => {
  msg.value = 'Vue3真香'
}

</script>

ref

vue2中直接使用this.$refs获取真实Dom,vue3中,需要定义一个变量来接受Dom

<template>
  <h2 ref="titleRef">我是一个标题</h2>
  <button @click="getRef">获取ref</button>
</template>

<script setup lang="ts">
import { ref } from 'vue';

// 定义变量来接收dom
let titleRef = ref(null)

const getRef = ()=>{
  // 获取dom节点
  console.log(titleRef.value)
  console.log(titleRef.value.innerHTML)
}
</script>

image.png

computed

定义一个变量来接受computed返回的值

<template>
  <h2>{{countText}}</h2>
</template>

<script setup lang="ts">
import { ref,computed } from 'vue';

let count = ref(100)

// computed函数
let countText = computed(()=>{
  return `${count.value}%`
})
</script>

watch

<template>
  <h2>{{count}}</h2>
  <button @click="change">改变值</button>
</template>

<script setup lang="ts">
import { reactive, ref,watch } from 'vue';

let count = ref(100)
let loading = ref(true)

const person = reactive({
  name:'Tom',
  age:18
})

// 监听基本数据类型
watch(count,(val,oldval)=>{
  console.log(val)
})

// 同时监听多个数据
watch([count,loading],(val,oldval)=>{
  console.log(val)  // [101, false] 返回的值也是数组
})

// 监听引用数据类型
watch(person,(val,oldval)=>{
  console.log(val)
})

// 监听对象的某个值
watch(()=>person.name,(val,oldval)=>{
  console.log(val)
})

// 同时监听对象多个属性
watch([()=>person.name,()=>person.age],(val,oldval)=>{
  console.log(val) // ['Join', 18] 
})

const change = ()=>{
  loading.value = false
  count.value++
  person.name = 'Join'
  person.age = 18
}
</script>

image.png

生命周期

在原来的基础上加上了on来访问组件的生命周期钩子。

vue2vue3
beforeCreate去掉(直接在setup中执行)
created去掉(直接在setup中执行)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
activatedonActivated
deactivatedonDeactivated
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue";

onMounted(()=>{
...
})

onBeforeUnmount(()=>{
...
})
</script>

子组件自动导入

父组件引入子组件后,无需注册,可以直接使用

<template>
  <Child></Child>
</template>
<script setup lang="ts">
// 直接引入
import Child from './Child.vue'

</script>

插槽

子组件中可以使用useSlots获取到传递过来的插槽对象

  • 父组件
<template>
  <Son>
  <!-- 匿名插槽 -->
  <span>默认插槽</span>
  <!-- 具名插槽 -->
  <template #title>
    <h2>具名插槽</h2>
  </template>
  <!-- 作用域插槽 -->
  <template #footer="scope">
    <h2>{{scope.msg}}</h2>
  </template>
 </Son>
</template>

<script setup lang="ts">
import Son from './Son.vue'

</script>
  • 子组件
<template>
<!-- 默认插槽 -->
<slot></slot>
 <!-- 具名插槽 -->
  <slot name="title" ></slot>
  <!-- 作用域插槽 -->
  <slot name="footer" :msg="msg" ></slot>
  <h2>{{$attrs.data}}</h2>
</template>

<script setup lang="ts">
import { ref,useSlots } from 'vue';

let msg = ref('我是子组件中的数据')

// 获取插槽对象
const slots = useSlots()
console.log(slots.default()); //获取到默认插槽的虚拟dom对象
console.log(slots.title());   //获取到具名title插槽的虚拟dom对象
</script>

image.png

父子组件通信

需要引入definePropsdefineEmits来声明传递的数据和事件,如果父组件向子组件传递数据,子组件没有使用defineProps接受的话,那么数据就会存放在attrs对象中,模板中使用$attrs接受,script中使用useAttrs接收。

  • 父组件
<template>
  <Child :msg="msg"
         :person="person"
         data="非porps传值子组件用$attrs接收"
         @changeMsg="changeMsg"></Child>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import Child from "./Child.vue";

const msg = ref('欢迎学习vue3')

const person = reactive({
  name: '花颜',
  age: 18
})

// 子组件传递过来的事件
const changeMsg = (value) => {
  msg.value = value
}

</script>
  • 子组件
<template>
  <h1>{{msg}}</h1>
  <h1>{{person.age}}</h1>
  <!-- 没有用props接受的数据放在attrs中 -->
  <h1>{{$attrs.age}}</h1>
  <button @click="changeMsg">改变msg值</button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits,attrs } from 'vue'
// defineProps:声明props
const props = defineProps({
  msg: {
    type: String,
    default: ""
  },
  person: {
    type: Object,
    default: () => { }
  },
})

// defineEmits:声明emit事件
const emit = defineEmits(['changeMsg']);

const changeMsg = () => {
  emit('changeMsg', 'vue3真不错!')
}

// 使用useAttrs来接受没有用props接受的数据
const attrs = useAttrs()
console.log(attrs.data)  // '非porps传值子组件用attrs接收'
</script>

父组件直接获取子组件数据或方法

使用ref获取子组件实例,从而获取属性和方法,需要注意的是,要在子组件中使用defineExpose将对应数据和方法暴露出去,否则父组件无法获取

  • 父组件
<template>
  <button @click="getSonData">Primary</button>
  <Son ref="sonRef"/>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Son from './Son.vue'

// 子组件 ref
let sonRef = ref(null)

const getSonData = ()=>{
  // 通过ref获取子组件数据
  console.log(sonRef.value.msg)
  // 通过ref调用子组件方法
  sonRef.value.getData()
}
</script>
  • 子组件
<script setup lang="ts">
import { ref,defineExpose } from 'vue';
// 数据
let msg = ref('我是子组件中的数据')
// 方法
const getData = ()=>{
  console.log('我是子组件中的方法')
}

// 将组件中的属性暴露出去,这样父组件可以获取
defineExpose({
   msg,
   getData
})
</script>

nextTick

<template>
  <h1 ref="titleRef">我是一个标题</h1>
</template>

<script setup lang="ts">
import { ref,nextTick } from 'vue';

let titleRef = ref(null)

nextTick(()=>{
  // 获取h1的真实DOM节点
  console.log(titleRef.value)
})
</script>

挂载全局属性

main.js中

import { createApp } from 'vue'
import App from './App.vue'

// 创建vue实例
const app = createApp(App)
app.mount('#app')
// 挂载全局属性
app.config.globalProperties.$title = '我是一个全局属性'

文件中使用

<template>
  <h1 class="title">{{proxy.$title}}</h1>
</template>

<script setup lang="ts">
 import { getCurrentInstance } from 'vue'
  // 使用getCurrentInstance获取原型对象
  const { proxy } = getCurrentInstance()
  
  // 输出属性
  console.log(proxy.$title)
</script>

style v-bind

可以直接在style中书写表达式,使用变量

<template>
  <h1 class="title">我是一个标题</h1>
</template>

<script setup lang="ts">
import { ref,nextTick } from 'vue';

let loading = ref(false)

</script>
<style scoped>
  .title {
    /* 使用v-bind书写表达式 */
    color: v-bind("loading?'red':'green'");
  }  
</style>

image.png

mitt.js

Vue2中使用EventBus来进行任意组件之前通信,而Vue3.x 中更推荐使用mitt.js,首先它足够小,仅有200bytes,其次支持全部事件的监听和批量移除,它还不依赖 Vue 实例,所以可以跨框架使用,使用方式也很简单,和EventBus类似。

方式一:在main.js中注册挂载到全局变量上

import { createApp } from 'vue'
import App from './App.vue'
// 导入mitt
import mitt from 'mitt'

// 创建vue实例
const app = createApp(App)
// 挂载mitt到vue原型上
app.config.globalProperties.$mitt = new mitt()
  • 父组件
<template>
  <button @click="sendMsg">mitt传值</button>
  <Son></Son>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import { getCurrentInstance } from 'vue'
// 获取原型对象
let { proxy } = getCurrentInstance()

const sendMsg = () => {
  // emit来发送数据
  proxy.$mitt.emit('Send', '使用mitt传递过来的值')
}
</script>
  • 子组件
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import { getCurrentInstance } from 'vue'
// 获取原型对象
let { proxy } = getCurrentInstance()

onMounted(() => {
  // on来接受数据
  proxy.$mitt.on('Send', (msg) => {
    console.log(msg)
  })
})

onBeforeUnmount(() => {
  proxy.$mitt.off('Send')
})
</script>

方式二:创建mitt.js文件

  • 新建mitt.js文件
import mitt from 'mitt'
export default new mitt()
  • 父组件
<template>
  <button @click="sendMsg">mitt传值</button>
  <Son></Son>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import mitt from './mitt.js'

const sendMsg = () => {
  // emit来发送数据
  mitt.emit('Send', '使用mitt传递过来的值')
}
</script>
  • 子组件
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import mitt from './mitt.js'

onMounted(() => {
  // on来接受数据
  mitt.on('Send', (msg) => {
    console.log(msg)
  })
})

onBeforeUnmount(() => {
  mitt.off('Send')
})
</script>

路由跳转 router

  • home页面
<template>
  <button @click="goDetail">跳转至detail页面</button>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'

// 必须先声明调用
const router = useRouter()

const goDetail = () => {
  router.push({
    name: 'Detail',
    query: {
      id: '001'
    }
  })
}
</script>
  • detail页面
<script setup lang="ts">
import { onActivated } from "vue";
import { useRoute } from 'vue-router'
// 必须先声明调用
const route = useRoute()

onActivated(() => {
  // 获取路由传递过来的参数
  console.log(route.query)
})
</script>

组合式函数——hooks

自定义Hooks,将代码通过功能分块写,响应变量和方法在一起定义和调用,这样后期我们改功能A只需要关注功能A块下的代码,不会像Vue2在Option Api需要同时关注methos和data。

  • 新建hook.js文件
import { ref } from 'vue'

const getUser = () => {
  let count = ref(0)

  const addCount = () => {
    count.value++
  }

  return {
    count,
    addCount,
  }
}
export default getUser

vue文件中使用

<template>
  <h1>{{count}}</h1>
  <button @click="goDetail">count+1</button>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
import getUser from './hook.js'
const { count, addCount } = getUser()
const goDetail = () => {
  addCount()
}
</script>

provide、inject

provide和inject用于爷孙组件传值

爷组件

<script setup>
import { provide, ref, readonly } from 'vue'

let info = ref('数据在孙组件中不可改变')
let text = ref('数据在孙组件中可以改变')

provide('info', readonly(info))
provide('text', text)

<script>

孙组件

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

const info = inject('info', '我是info')
const text = inject('text', '我是msg')
</script>

arrtsarrts 和 listeners

子组件

<template>
  <div>
    <el-input v-bind="$attrs"
              v-on="$listeners"></el-input>   
  </div>
</template>

<script setup>
</script>

父组件

<template>
  <div>
    <myinput v-model="name"
             @change="change"></myinput>
  </div>
</template>
<script setup>
import { onMounted, provide, ref, readonly } from 'vue'

let name = ref('mark')

const change = ()=>{
}
</script>