vue3常用api盘点及现有vue2组件迁移示例

vue3.png

前言:

vue3项目是在两年前开始的,正式版3.0于2020年9月发布;
目前vue生态支持情况还不完善,如vuex4还处于rc版本,并存在一定的bug,暂不推荐大家用在标准版项目里;
不过那些无关紧要的项目必须可以试试;
再者vue3的官方生态包为了让打包后的文件尽可能的小,打包文件都输出了ECMASCRIPT新语法,这就是网上吹牛逼说的vue3更快、更小;

一、生命周期

  1. beforeCreate -> setup()
  2. created -> setup()
  3. beforeMount -> onBeforeMount // 在挂载前执行某些代码
  4. mounted -> onMounted // 在挂载后执行某些代码
  5. beforeUpdate -> onBeforeUpdate // 在更新前前执行某些代码
  6. updated -> onUpdated // 在更新后执行某些代码
  7. beforeDestroy -> onBeforeUnmount // 在组件销毁前执行某些代码
  8. destroyed -> onUnmounted // 在组件销毁后执行某些代码
  9. errorCaptured -> onErrorCaptured // 错误捕获

二、常用api

!中文文档(https://www.vue3js.cn/docs/zh/api/global-api.html#createapp)

  1. ref
  2. reactive
  3. watch
  4. computed
  5. nextTick
  6. props

三、vite创建vue3项目

文档地址(https://vitejs.dev/guide/#scaffolding-your-first-vite-project)

  • npm init @vitejs/app my-vue-app --template vue
  • cd .\my-vue-app\
  • npm install
  1. ref演示


    ref.gif
<template>
  <button @click="state++">count is: {{ state }}</button>
</template>

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

const state = ref(0)
</script>
  • 提示:获取dom也需要ref
  • 思考1:怎么用ref获取dom
  1. reactive演示


    reactive.gif
<template>
  <button @click="state.count++">count is: {{ state.count }}</button>
</template>

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

const state = reactive({ count: 0 })
</script>
  • 思考2:以上可以看出,如果用reactive声明n个变量,在dom中使用都要加上state.xxx,岂不是很麻烦,
    现在我要这样的效果 <div>count is: {{ count }}</div>,您会怎么做?
  1. watch演示


    watch.gif
<template>
    <div>reactive</div>
  <button @click="state.count ++">count is: {{ state.count }}</button>
    <div>{{ state.text }}</div>
</template>

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

const state = reactive({
    count: 0,
    text: 'ready'
})

watch(() => state.count, (n, o) => {
    if (n > 5) state.text = '大哥,别点了,count大于5了';
    else state.text = 'count小于5';
});
</script>
  • 思考3:
    1. 我想同时监听多个变量,该怎么做
    2. 深度监听,初始化执行,该怎么配置参数

4.computed演示


computed.gif
<template>
    <div>computed</div>
  <button @click="state.count ++">count is: {{ state.count }}</button>
    <div>{{ doubleCount }}</div>
</template>

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

const state = reactive({
    count: 0
})

const doubleCount = computed(() => (state.count * 2))
</script>
  1. nextTick演示


    nextTick.gif
<template>
    <div>nextTick</div>
  <button @click="countClick">count is: {{ state.count }}</button>
    <div v-if="state.count" id="divEle">{{ state.doubleCount }}</div>
</template>

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

const state = reactive({
    count: 0,
    doubleCount: 0
})

const countClick = () => {
    state.count ++

    nextTick(() => {
        state.doubleCount = state.count * 2
        const divEle2 = document.getElementById('divEle')
        console.log('divEle2::', divEle2);
    });

    const divEle1 = document.getElementById('divEle')
    console.log('divEle1::', divEle1);
}
</script>
  1. props演示


    props.png
<template>
    <div>defineProps</div>
    <div>{{ msg }}</div>
</template>

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

defineProps({
    msg: String
})

const state = reactive({
    count: 0,
})

</script>
  1. 以上看着不习惯,setup还可以这样写
<template>
    <div>defineProps</div>
    <div>{{ msg }}</div>
</template>

<script>
import { reactive, defineProps } from 'vue'
export default {
    props: ['msg'],
    setup() {
       const state = reactive({
          count: 0,
      })
      
      return { state }
    }
}
</script>

四、vite打包信息

vite-build.png
  • vite打包是杠杠的大拇指
  • 但是,vite打包虽然小,单并不支持ie,目前在咱们项目中无法使用

五、Breadcrumb 面包屑迁移

完全迁移(composition api jsx篇)

  1. 迁移前
<template>
    <div class="p-breadcrumb">
        <section class="p-breadcrumb-item" v-for="(item, i) in data" :key="i+'-'+item.id">
            <article
                    :class="[
                        'p-breadcrumb-item-text',
                        (value?value===item.id:i===data.length-1)&&'p-breadcrumb-item-active',
                        (i>0&&i<data.length-1)&&'p-breadcrumb-item-width',
                        (i===data.length-1)&&'p-breadcrumb-item-max-width'
                    ]"
                    v-ptitle:isText:true="item.name"
                    @click="breadcrumbClick(item.id)"
                    @mouseenter="TextEllipsis"
            >{{item.name}}</article>
            <article class="p-breadcrumb-arrow" v-if="i<data.length-1">
                <ArrowRight />
            </article>
        </section>
    </div>
</template>

<script>
import ArrowRight from '../static/iconSvg/arrow_right.svg';
import TextEllipsis from '../static/utils/TextEllipsis';

export default {
    name: 'Breadcrumb',
    components: { ArrowRight },
    props: {
        // 数据列表
        data: {
            type: Array,
            default: () => []
        },
        // 当前高亮显示的id
        value: {
            type: String,
            default: ''
        }
    },
    data() {
        return {
            titleShow: false // 是否显示title
        };
    },
    methods: {
        TextEllipsis,
        /**
         * 点击某项执行的钩子
         * @param id
         */
        breadcrumbClick(id) {
            if (this.value) this.$emit('input', id);
        }
    }
};
</script>
  1. 迁移后
import { defineComponent } from 'vue';
import TextEllipsis from '../static/utils/TextEllipsis';

import ArrowRight from '../static/iconSvg/arrow_right.svg';

const ArrowRightDom = (
    <article className="p-breadcrumb-arrow">
        <ArrowRight/>
    </article>
);

const Breadcrumb = defineComponent({
    name: 'Breadcrumb',
    props: {
        // 数据列表
        data: {
            type: Array,
            default: () => []
        },
        // 当前高亮显示的id
        modelValue: {
            type: String,
            default: ''
        }
    },
    emits: ['change', 'update:modelValue'],
    setup(props, { emit }) {
        const breadcrumbClick = (id) => {
            if (props.modelValue) emit('update:modelValue', id);
            else emit('change', id);
        };
        return () => {
            const { data, modelValue } = props;
            return (
                <div class="p-breadcrumb">
                    {
                        data.map((item, i) => (
                            <section class="p-breadcrumb-item" key={`${i}-${item.id}`}>
                                <article class={{
                                    'p-breadcrumb-item-text': true,
                                    'p-breadcrumb-item-active': (modelValue ? modelValue === item.id : i === data.length - 1),
                                    'p-breadcrumb-item-width': (i > 0 && i < props.data.length - 1),
                                    'p-breadcrumb-item-max-width': (i === data.length - 1)
                                }}
                                onClick={() => breadcrumbClick(item.id)}
                                onMouseEnter={TextEllipsis}
                                >{item.name}</article>
                                {(i < data.length - 1) && <ArrowRightDom/>}
                            </section>
                        ))
                    }
                </div>
            );
        };
    }
});
  1. 注意
    • class类名的绑定 - 与react中不一样的是,vue中支持对象、数组
    • props - 必须接收
    • emits - 需申明提交方式
    • 事件 - 事件绑定需要on...开头

保守迁移(option api)

<template>
    <div class="p-breadcrumb">
        <section class="p-breadcrumb-item" v-for="(item, i) in data" :key="i+'-'+item.id">
            <article
                    :class="[
                        'p-breadcrumb-item-text',
                        (modelValue?modelValue===item.id:i===data.length-1)&&'p-breadcrumb-item-active',
                        (i>0&&i<data.length-1)&&'p-breadcrumb-item-width',
                        (i===data.length-1)&&'p-breadcrumb-item-max-width'
                    ]"
                    @click="breadcrumbClick(item.id)"
                    @mouseenter="TextEllipsis"
            >{{item.name}}</article>
            <article class="p-breadcrumb-arrow" v-if="i<data.length-1">
                <ArrowRight />
            </article>
        </section>
    </div>
</template>

<script>
import ArrowRight from '../static/iconSvg/arrow_right.svg';
import TextEllipsis from '../static/utils/TextEllipsis';

export default {
    name: 'Breadcrumb',
    components: { ArrowRight },
    props: {
        /**
         * 数据列表
         */
        data: {
            type: Array,
            default: () => []
        },
        /**
         * 当前高亮显示的id
         */
        modelValue: {
            type: String,
            default: ''
        }
    },
    emit: ['update:modelValue'],
    data() {
        return {
            titleShow: false // 是否显示title
        };
    },
    methods: {
        TextEllipsis,
        /**
         * 点击某项执行的钩子
         * @param id
         */
        breadcrumbClick(id) {
            if (this.modelValue) this.$emit('update:modelValue', id);
        }
    }
};
</script>
  • 问题:
    vue3当中很多废弃api,不能保证所有组件迁移成功(研究中)

六、v-model与v-show写法

v-model.png
  • 截图来之某乎,大家可以下去试下,在jsx中并没什么ly
.vue v-show v-model v-model:title v-model:title.func
.jsx v-show v-model v-model={[val, 'title']} v-model={[val, 'title', ['func']]}

七、全局变量

版本 变量 获取
vue2 Vue.prototype.$xxx = xxx this.$xxx
vue3 app.config.globalProperties.$xxx = xxx getCurrentInstance().ctx.$xxx

八、两个好玩的组件

  1. Teleport 传送门

    • 有这样的场景,我们在做业务的时候,经常会遇到层级(z-index)问题、或者我们项把组件挂在到body下
      示例:
         <Teleport to="body">
             <div>xxx</div>
         </Teleport>
      

    其中to参数值可以指向任何一个容器(容器建议具有唯一性)

  2. Suspense 用作处理异步占位

    • 最常见的当数据没有回来时我们需要一个占位内容,通常是loading或骨架屏
      示例:
            <Suspense>
                <template #default>
                    <div>主内容(异步加载内容)</div>
                </template>
                <template #fallback>
                    <div>loading</div>
                </template>
            </Suspense>
      

    此处为固定写法,目前官网文档还不完善,且Suspense api可能会改变,别问为什么我知道,网上抄的

九、以上思考问题回复

  1. 思考1:怎么用ref获取dom
    回复:
       const dom = ref(null);
       <div ref={dom}></div>
    
  2. 思考2:以上可以看出,如果用reactive声明n个变量,在dom中使用都要加上state.xxx,岂不是很麻烦,
    现在我要这样的效果 <div>count is: {{ count }}</div>,您会怎么做?
    回复:
       const state = reactive({
           params1: '111',
           params2: '222',
           params3: '333'
       })
       return {
           ...toRefs(state)
       }
    
  3. 思考3:
    • 我想同时监听多个变量,该怎么做
      回复:
         watch([() => state.params1,() => state.params2,() => state.params3], (n, o) => {
             console.log(n, o);
         });
         const clickHandler = () => {
             state.params1 = '1111+1'
         }
      
    • 深度监听,初始化执行,该怎么配置参数
      回复:
       watch([() => state.params1,() => state.params2,() => state.params3], (n, o) => {
             console.log(n, o);
       }, { deep: true, immediate: true });
    

总结

  • vue3支持大部分在vue2中的option api,就好比在react17中既可以使用class component也可以使用hooks
  • vue3 api变化大,其中所提供的api远不止本文这些,本文只做了一个简单的入门介绍
  • vue3目前暂不支持ie11-,尤老师本计划在20年第四季度完成这事的,现在来看,不知道啥时候能得到尤老师的好消息
  • vue3的生态是一个浩大的工程,官方正在奋力解决,小伙伴们不要慌

祝各位工作愉快

end~

推荐阅读更多精彩内容