vue3.2 语法糖 Composition Api应用

715 阅读5分钟

前段时间,因为一些新的语法糖和api弄的有点迷糊,故想整理一下3.0,3.2 相关的、常用的新api,语法糖等。可能会有遗漏,想起来了再补.

  1. ref
  2. reactive
  3. toRef
  4. toRefs
  5. unref,isRef
  6. isReactive
  7. toRaw
  8. markRaw
  9. v-model
  10. computed
  11. watch
  12. onMounted
  13. defineProps
  14. defineEmits
  15. defineExpose
  16. useRoute, useRouter
  17. await
  18. globalProperties
  19. css => v-bind()
  20. volar
  21. provide, inject
  22. solt, useSlots

Vue3.0 变量函数必须 return 出来,template中才能使用;

Vue3.2 在script标签中添加setup属性,替代了setup() { return {} }, 在标签内的变量函数,就可以template中使用

    // 3.2
    <template>
        <div @click="handleClick">{{msg}}</div>
    </template>
    <script lang="ts" setup>
        const msg = "setup语法糖"
        
        const handleClick = (): void => { msg.value = "3.0 => function" }
    
    </script>
    <style>
    </style>
    // 3.0
     <template>
        <div @click="handleClick">{{msg}}</div>
    </template>
    <script lang="ts">
    setup() {
        const msg = "3.0 setup"
        
        const handleClick = () => { msg.value = "3.0 => function" }
        
        return { msg, handleClick }
    }
    </script>
    <style>
    </style>

ref

ref 是创建一个响应式的变量(官方推荐ref定义一个基础类型的变量,当然你也可以引用数据类型),返回的是一个RefImpl实例对象包裹的响应数据.

// 定义一个ref变量
const count = ref(1)
// 改变ref的值
count.value = 2

reactive

reactive 是创建一个响应式的引用类型的数据。

const obj = reactive({})
obj.name = "name"

refreactive的区别

  1. ref基本是用来创建一个基本数据类型,reactive是用来创建一个引用数据类型。
  2. ref的底层其实还是用object.defineProperty()来数据劫持的,reactive是用Proxy来实现劫持的。然后底层通过Reflect的get,set来改变内部数据。
  3. ref改变值需要在值后面加.value,当然在template的dom中显示不需要加。reactive改变值不需要加.value

unref, isRef

unref,isRef,见名知义,就是将ref类型的数据转换成普通的数据,判断是不是ref类型的数据

/* 这个可以根据情况灵活运用 */
const count = ref(0)
const value = computed(() => isRef(count) ? unref(count) : count)

isReactive

isReactive就是判断是不是reactive对象,返回boolean

const state = reactive({ name: "caicai" });
const isReac = computed(() => isReactive(state));

toRaw

toRaw 作用就跟unref一样,只不过是toRaw只将reactive定义的对象转换为普通的对象。使该对象不再是响应式的

const state = reactive({
    name: "caicai"
});
const rawState = toRaw(state);
// 数据会变,但是视图不发生变化了
rawState.name = "chaoshen";

markRaw

markRaw标记一个对象,不能成为代理对象。返回也是一个普通对象

const state = reactive({
    custom: markRaw({
        age: 18
    }),
    name: "caicai"
});
// custom视图不会发生变化,视图显示age还是18.
setTimeout(() => {
  state.custom.age = 22;
  console.log(state.custom); // { age: 22 },数据会发生变化
}, 1000);

我个人感觉吧,有点鸡肋,可能是我目前遇到这样写的需求比较少,但是他有时候确实是会报这个警告。

image.png

image.png

就是这个,如果你不用markraw包一下你传入component,他就说你传入的组件是一个响应式的对象。

v-model

众所周知,vue父子组件通过props传值是单向数据流,在vue2里使用了.anyc异步处理了。在3里是用v-model做处理

// 父组件
<button @click="ok"></button>
<children v-model="visible"></children>

<script lang="ts" setup>
import children from "./children.vue";
const visible = ref(false);
const ok = (): void => visible.value = true
</script>

// 子组件
<div v-show="visible" @cancel="cancel"></div>

<script lang="ts" setup>
const props = defineProps({
    visible: {
        type: boolean,
        default: () => false,
        required: true
    }
})
const emit = defineEmits(["update:visible"])
const cancel = (): void => {
    emit("update:visible", false)
}
</script>

computed

computed 计算属性,功能没变,写法有点变化

<div>{{ sum }}</div>

const count = 2
const sum = computed(() => count * 2 );

watch

watch 监听属性嘛,和vue2一样

const count = ref(0);

watch(
count,
(newval, oldval) => {
    console.log(newval, oldval)
},
{ immediate: true, deep: true })

监听reactive时

const state = reactive({
    name: "caicai"
});

watch(
() => state.name,
(newval, oldval) => {
    console.log(newval, oldval)
},
{ immediate: true, deep: true })

监听多个数据时

const state = reactive({
    name: "caicai",
    age: 18
});

watch(
[() => state.name, () => state.age],
(newval, oldval) => {
    // newval 和 oldval 也是数据
    console.log(newval, oldval)
},
{ immediate: true, deep: true })

onMounted

onMounted,就是mounted在vue3中的生命周期api

import { onMounted } from "vue"

onMonuted(() => console.log("页面加载完成"));

defineProps

在vue3.2中,props需要通过defineProps方法来返回。

<template>
    <div>{{ showCount }}</div>
</template>

<script lang="ts" setup>
const props = defineProps({
    count: {
        type: number,
        required: true,
        defalut: () => 0
    }
})

const showCount = computed(() => props.count * 2)

</script>

defineEmits

在vue3.2中,emit方法,通过defineEmits方法返回

//在vue3的时候还需要注册事件,做配置
emit: ["submit"]
/**************/
//3.2直接就可以同时注册,返回
const emit = defineEmits(["submit", "update:visible"])

const submit = () => {
    emit("submit", formState)
}
const cancel = () => {
    emit("update:visible", false)
}

defineExpose

defineExpose 代替了vue中的expose方法,常用于将子组件中方法,变量暴露出去。

// 父组件
<template>
    <div>
        <Children ref="childRef" />
    </div>
</template>

<script setup>
import Children from "./children.vue";

components: { Children };

const childRef = ref();

console.log(childRef.value.count); // 2

</script>


// 子组件

<script setup>

const count = ref(2);
definedExpose({
    count
})
</script>

useRoute,useRouter

在vue3中,useRoute,useRouter, 需要通过vue-router引入, 相当于vue2的 this.$routethis.$router,然后其他之前vue2的操作都可以进行

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

const route = useRoute();
const router = useRouter();

const handlePath = () => {
    cosole.log(route.query);
    router.push("/home");
}
</script>

await

在script-setup 语法糖之下,await语法可以直接使用。

<script setup lang="ts">
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

它转换成标准组件的写法就是:

<script lang="ts">
import { defineComponent, withAsyncContext } from 'vue'

export default defineComponent({
  async setup() {
    const post = await withAsyncContext(
      fetch(`/api/post/1`).then((r) => r.json())
    )

    return {
      post
    }
  }
})
</script>

globalProperties

app.config.globalProperties 相当于vue2中的 vue.prototype

// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App); // 获取原型 
app.config.globalProperties.name = "caicai"; // 绑定参数

在组件中使用,需要引用getCurrentInstance

<script setup> 
import { getCurrentInstance } from 'vue' // 获取原型 
const { proxy } = getCurrentInstance() // 输出 
console.log(proxy.name) // "caicai"
</script>

Bind

3.2 之后呢,我们之前梦寐以求的,在js里定义变量,计算dom的宽高,移动距离,然后拿变量在css里用。实现了。

<template>
    <div class="wrap">
    
    </div>
</template>
<script lang="ts" setup>
    import { ref, onMounted } from 'vue';
    const bodyHeight = ref<string>("");
    const countHeaderHeight = (): void => {
      const headDom = document.querySelector(".head");
      if (headDom) {
        headerHeight.value = (headDom as any).offsetHeight;
      }
      bodyHeight.value = `calc(100% - ${headerHeight.value}px)`;
      onMounted(() => {
          countHeaderHeight();
      });
    };
</script>
<style scoped>
    .wrap {
        // 使用v-bind绑定state中的变量
        height: v-bind(bodyHeight);
    }
</style>

volar

vetur相同,volar是一个针对vuevscode插件,由于之前vetur对vue3和ts,兼容都不是很好,volar就相当于是vetur二代。集成了高亮,代码提示等等,还新增了一些列的功能,感兴趣的同学可以搜搜其具体的功能。

image.png

provide, inject

provide, inject 在vue3中是我比较常用的一种祖先后代传值的一种,毕竟一直props传值层数多了也挺烦的。比之前vue2的写法也要简单的多。

// vue2的  provide出去
  provide () {
    return {
      tags: this.tags
    }
  }
  inject: [
    'tags'
  ],

在vue3中,用法简单,想要做到数据响应也很简单,只需要传递一个响应的数据就行

const state = reactive({
    name: "caicai"
})
provide("state", state);

/****************/

const state = inject("state");

不管是父组件改变state的值,还是子组件的值,数据都是响应式的。

solt, useSlots

// 子组件
<template>
    <slot name="footer" :scope="state" />
</template> <script setup>
    import { useSlots, reactive } from 'vue'
    const state = reactive({ name: 'caicai', age: 18 })
    const slots = useSlots() // 可以拿到所有的插槽,视具体情况而用
</script>

// 父组件
<template>
    <child>
        <template #footer="{ scope }"> 
            <p>姓名:{{ scope.name }},年龄{{ scope.age }}</p> 
        </template> 
    </child> 
</template> 
<script setup> 
// 引入子组件 
import child from './child.vue' 
</script>