乐趣区

关于vue3:Vue3x-新特性总结

Vue3.x 官网

新版 vue3 的 setup 语法糖更为简洁,详见 Vue3 script setup 语法糖,超爽体验


一、Composition API 简介

Vue2 时的形式在代码很少的时候,逻辑构造还是蛮清晰的,然而随着组件性能越来越多,代码量越来越大,整体内容全副放在其中必定会显得臃肿。因为每个功能模块的代码会散落散布在各个地位,让整个我的项目的内容难以浏览和保护。如下图:

而到了 Vue3,它会依据逻辑性能来进行组织,把同一个性能的不同代码都放在一起,或者把它们独自拿进去放在一个函数中,所以 Composition API 又被称为基于 函数组合的 API


1. setup 函数

setup 函数 是 Vue3 中新增的函数,它是咱们在编写组件时,应用 Composition API 的入口。
同时它也是 Vue3 中新增的一个生命周期函数,会在 beforeCreate 之前调用。因为 此时组件的 datamethods 还没有初始化,因而在 setup 中是不能应用 this。所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的 this 批改成了undefined。并且,咱们只能同步应用 setup 函数,不能用 async 将其设为异步。

setup 函数接管两个参数 propscontext,语法为:setup(props,context){}

props

props 外面蕴含 父组件传递给子组件的所有数据。在子组件中应用 props 进行接管。

props 是响应式的 ,当传入新的 props 时,会及时被更新。
因为是响应式的,所以不能够应用 ES6 解构,解构会打消它的响应式。

父组件:

<template>
    <!-- 父组件向子组件传递数据 -->
    <Sub :name="name" :age="age" />
</template>

<script>
import {ref} from 'vue'
import Sub from './Sub.vue'
export default {setup () {const name = ref('张三');
        const age = ref(20)

        return {name, age}
    },
    components: {Sub},
}
</script>

子组件(Sub.vue):

<template>
    <div>{{name}}{{age}}</div>
</template>

<script>
export default {
    props: {
        name: String,
        age: Number
    },
    mounted () {
        // vue2.x 的写法
        console.log(this.name); // 张三
        console.log(this.age); // 20
    },
    setup (props) {
        // vue3.x 的写法
        console.log(props.name); // 张三
        console.log(props.age); // 20
        
        // let {name ,age} = props;  // 不能间接解构
        let {name, age} = toRefs(props);
        console.log(name.value, age.value); // 张三 20
    }
}
</script>

context

context 外面蕴含 attrs, slots, emit 等数据办法:

  • attrs:获取组件上的属性
  • slots:获取 slot 插槽的节点
  • emit:emit 办法(子组件向父组件传递数据

父组件:

<template>
    <Sub subData="some other data" @subClick='subClick'>parent</Sub>
</template>

<script>
import Sub from './Sub.vue'
export default {setup () {function subClick (e) {console.log(e); // 接管子组件传递过去的数据
        }

        return {subClick}
    },
    components: {Sub},
}
</script>

子组件(Sub.vue):

<template>
    <!-- 父组件向子组件传递数据 -->
    <div @click="handleClick">Child</div>
</template>

<script>
export default {mounted () {
        // vue2.x 获取组件上的属性
        console.log(this.$attrs.subData);  // 'some other data'
        
        // vue2.x 获取 slot 插槽的节点
        console.log(this.$slots);

    },
    methods: {// vue2.x emit 办法(子组件向父组件传递数据)
        handleClick () {this.$emit('subClick', 'vue2.x - this is subData')
        },
    },
    setup (props, context) {let { attrs, slots, emit} = context;

        // vue3.x 获取组件上的属性
        console.log(attrs.subData);  // 'some other data'

        // vue3.x 获取 slot 插槽的节点
        console.log(slots.default());

        // vue3.x emit 办法(子组件向父组件传递数据)
        function handleClick () {emit('subClick', 'vue3.x - this is subData');
        }

        return {handleClick}
    }
}
</script>

2. 生命周期

Vue3.x 生命周期

setup 函数 是 Vue3 中新增的一个生命周期函数

  • setup 函数会在 beforeCreate 之前调用,因为 此时组件的 datamethods 还没有初始化,因而在 setup 中是不能应用 this
  • 所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的 this 批改成了undefined
  • setup 函数,只能是同步的不能是异步
Vue2.x(Option API) Vue3.x(Composition API)
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured
– – onRenderTracked
– – onRenderTriggered

初始化加载程序:

setup => beforeCreate => created => onBeforeMount => onMounted

<script>
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered} from 'vue'
export default {setup () {console.log('setup');
        
        // 生命周期钩子(没有 beforeCreate 和 created)
        onBeforeMount(() => { console.log('onBeforeMount'); })
        onMounted(() => { console.log('onMounted'); })
        onBeforeUpdate(() => { console.log('onBeforeUpdate'); })
        onUpdated(() => { console.log('onUpdated'); })
        onBeforeUnmount(() => { console.log('onBeforeUnmount'); })
        onUnmounted(() => { console.log('onUnmounted'); })

        // 新增的 debug 钩子  生产环境中会被疏忽
        onRenderTracked(() => { console.log('onRenderTracked'); }) // 每次渲染后从新收集响应式依赖,在 onMounted 前触发,页面更新后也会触发
        onRenderTriggered(() => { console.log('onRenderTriggered'); }) // 每次触发页面从新渲染时的主动执行,在 onBeforeUpdate 之前触发
    },
    beforeCreate () {console.log('beforeCreate');
    },
    created () {console.log('created');
    }
}
</script>

3. 返回值

setup 函数中返回一个对象,能够在模板中间接拜访该对象中的属性和办法。

<template>
    <div @click="handleClick">{{name1}} - {{name2}}</div>
</template>

<script>
import {ref, provide} from 'vue'
export default {setup () {const name1 = ref('张三');
        
        return {
            name1,
            name2: 'zhangsan',
            handleClick () {console.log(name1.value); // 张三
                console.log(this.name2); // zhangsan
            }
        }
    }
}
</script>

4. ref 与 reactive

创立一个 响应式数据

  • ref:任意类型(倡议根本类型)数据的响应式援用(设置、获取值时须要加.value)。
    ref 的实质是拷贝,批改数据是不会影响到原始数据。
  • reactive:只能是简单类型数据的响应式援用
<template>
    <ul>
        <li>ref 根本类型:{{name1}}</li>
        <li>ref 简单类型:{{name2.name}}</li>
        <li>reactive 简单类型:{{name3.name}}</li>
    </ul>
</template>

<script>
import {ref, reactive} from 'vue'
export default {setup () {
        let nameStr = '张三';
        let name1 = ref(nameStr); // ref 为根本数据类型增加响应式状态
        setTimeout(() => {
            name1.value = 'zhangsan';

            console.log(name1.value); // 'zhangsan'
            console.log(nameStr); // '张三'  =>  不会影响到原始数据
        }, 1000)


        let nameObj2 = {name: '张三'};
        let name2 = ref(nameObj2); // ref 为简单数据类型增加响应式状态(不倡议)setTimeout(() => {
            // 设置值须要加.value
            name2.value.name = 'zhangsan';

            console.log(name2.value.name); // 'zhangsan'  =>  获取值须要加.value
            console.log(nameObj2); // {name: "zhangsan"}  =>  会影响到原始数据
        }, 2000)


        let nameObj3 = {name: '张三'};
        const name3 = reactive(nameObj3);// reactive 只能为简单数据类型增加响应式状态
        setTimeout(() => {
            name3.name = 'zhangsan';

            console.log(name3.name); // 'zhangsan'
            console.log(nameObj3); // {name: "zhangsan"}  =>  会影响到原始数据
        }, 3000)


        return {name1, name2, name3}
    }
}
</script>

5. toRef 与 toRefs

也能够创立一个响应式数据

  • toRef:用来给抽离响应式对象中的某一个属性,并把该属性包裹成 ref 对象,使其和原对象产生链接。
    toRef 的实质是援用,批改响应式数据会影响原始数据。
  • toRefs:用来把响应式对象转换成一般对象,把对象中的每一个属性,包裹成 ref 对象。
    toRefs 就是 toRef 的升级版,只是 toRefs 是把响应式对象进行转换,其余的个性和 toRef 无二
<template>
    <ul>
        <li>{{name1}}</li>
        <li>{{name2.name.value}}</li>
        <li>{{name}}</li>
    </ul>
</template>

<script>
import {toRef, toRefs, reactive} from 'vue'
export default {setup () {let nameObj1 = reactive({ name: '张三'});
        let name1 = toRef(nameObj1, 'name');
        let age1 = toRef(nameObj1, 'age'); // age 不存在
        setTimeout(() => {
            name1.value = 'zhangsan';
            age1.value = 20; // 即使 age 不存在也能失常响应式赋值
        }, 1000)


        let nameObj2 = reactive({name: '张三'});
        let age2 = toRef(nameObj3, 'age'); // age 不存在
        let name2 = toRefs(nameObj2);
        setTimeout(() => {
            name2.name.value = 'zhangsan';
            age2.value = 20; // age 不存在,赋值无反馈
        }, 2000)


        let nameObj3 = reactive({name: '张三'});
        setTimeout(() => {nameObj3.name = 'zhangsan';}, 3000)
        let {name} = toRefs(nameObj3); // 解构后仍需放弃响应式(重要)

        return {name1, name2, name}
    }
}
</script>

6. readonly 只读属性

示意响应式对象不可批改

<template>
    <ul>
        <li>{{nameObj.name}}</li>
    </ul>
</template>

<script>
import {reactive, readonly} from 'vue'
export default {setup () {let nameObj = reactive({ name: '张三'});
        let readonlyObj = readonly(nameObj); // 对 nameObj 响应式对象设置成只读, 不可批改

        setTimeout(() => {readonlyObj.name = 'zhangsan'; // 无奈设置属性  =>  Set operation on key "name" failed: target is readonly. Proxy {name: "张三"}
        }, 1000)

        return {nameObj}
    }
}
</script>

7. method 办法

<template>
  <!-- 调用办法 -->
  <button @click='changeName'> 按钮 </button>  
</template>

<script setup>
import {reactive} from 'vue'
export default {setup () {const state = reactive({ name: '张三'});
        
        // 申明 method 办法
        const changeName = () => {state.name = 'zhangsan';}  
        
        return {state , changeName}
    }
}
</script>

8. computed 计算属性

<template>
    <div>
        <button @click="add">+</button>
        <p>{{addCount}}</p>
    </div>
</template>

<script>
import {ref, computed} from 'vue'
export default {setup () {const num = ref(0);
        const add = () => {num.value++}
        // 计算属性
        const addCount = computed(() => {return num.value * 2;})
        return {add, addCount}
    }
}
</script>

9. watch 与 watchEffect 监听属性

  • watch 函数:
    用来侦听特定的数据源,并在回调函数中执行副作用。
    默认状况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
  • watchEffect 函数:
    1.立刻执行 、立刻监听(immediate)
    2. 主动会感知代码依赖( 主动收集依赖 ),不须要传递监听的内容(不须要像 watch 一样手动传入依赖)
    3. 无奈取得变动前的值(oldVal)
<template>
    <div>
        <p>{{name}}</p>
        <p>{{nameObj.name}}</p>
    </div>
</template>

<script>
import {ref, reactive, watch, watchEffect} from 'vue'
export default {setup () {

        // 一、监听根本类型
        const name = ref('张三');
        setTimeout(() => {name.value = '李四';}, 1000);
        
        watch(name, (newVal, oldVal) => {console.log(newVal, oldVal);
        }, {immediate: true}) // 立刻执行


        // 二、监听简单类型
        const nameObj = reactive({name: 'zhangsan'})
        setTimeout(() => {nameObj.name = 'lisi';}, 2000);
        
        // 1. 简单数据无奈间接监听、惰性
        watch(() => nameObj, (newVal, oldVal) => {console.log(newVal, oldVal); // 不会触发
        })
        
        // 2. 须要深度监听、不惰性
        watch(() => nameObj, (newVal, oldVal) => {console.log(newVal, oldVal); // newVal、oldVal 具备响应式
        }, { 
            deep: true, // 深度监听
            immediate: true, // 立刻监听
        })
        
        // 3. 也能够间接监听对象的属性
        watch(() => nameObj.name, (newVal, oldVal) => {console.log(newVal, oldVal);
        })


        // 三、同时监听多个对象
        watch([() => nameObj.name, () => nameObj.lastName], ([newName, newLastName], [oldName, oldLastName]) => {console.log(newName, oldName, newLastName, oldLastName);
        })



        const stop = watchEffect(() => {console.log(name);
            console.log(nameObj.name);
        })

        // 3 秒后进行监听
        setTimeout(()=>{stop();   
        },3000)
        
        
        return {name, nameObj}
    }
}
</script>

二、获取 DOM 节点

<template>
    <input type="text" value="张三" ref="hello">
</template>

<script>
import {ref, onMounted} from 'vue'
export default {setup () {const hello = ref(null); // 获取组件中 ref="hello" 的实在 dom 元素
        
        onMounted(() => {console.log(hello.value); // <input type="text">
            console.log(hello.value.value); // 张三
        })
        
        return {hello}
    }
}
</script>

三、provide 与 inject

父组件向子组件传递数据与办法

父组件:

<template>
    <Sub />
</template>

<script>
import {ref, provide} from 'vue'
import Sub from './Sub.vue'
export default {setup () {const name = ref('张三');

        // provide(别名, 要传递的数据和办法)
        provide('myName', name)
        provide('handleClick', () => {name.value = 'zhangsan';})

    },
    components: {Sub},
}
</script>

子组件(Sub.vue):

<template>
    <div @click="handleClick">{{name}}</div>
</template>

<script>
import {inject} from 'vue'
export default {setup () {
        // 调用 inject 办法,通过指定的别名,接管到父级共享的数据和办法
        const name = inject('myName');
        const handleClick = inject('handleClick');

        return {name, handleClick}
    }
}
</script>

四、Teleport 传送门

Teleport 翻译过去是 传送 的意思,就像是哆啦 A 梦中的 「任意门」,任意门的作用就是能够将人霎时传送到另一个中央。

举例:
咱们心愿 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在 App.vue 文件中定义一个供挂载的元素:

<template>
    <router-view />
    <div id="model"></div> <!-- 挂载点 -->
</template>

定义一个 Dialog 组件 Dialog.vue , 注意 to 属性,与下面的 id 选择器 统一:

<template>
    <teleport to="#model"> 
        <!-- 挂载内容 -->
        <div>title</div>
        <div>I'am a Dialog.</div>
    </teleport>
</template>

DOM 渲染成果如下:

咱们应用 teleport 组件,通过 to 属性,指定该组件渲染的地位在 App.vue 中,然而 Dialog 又是齐全由 Dialog.vue 组件管制。


五、Suspense

Suspense 组件用于在期待某个异步组件解析时显示 后备内容

在 Vue2.x 中常常遇到这样的场景:

<template>
<div>
    <div v-if="!loading">
        <!-- 异步渲染 -->
        ...  
    </div>
    <div v-if="loading">
        加载中...
    </div>
</div>
</template>

在异步数据没加载前,个别咱们都会提供一个加载中 (loading) 的动画,当数据返回时配合 v-if 来控制数据显示。

当初 Vue3.x 新出的内置组件 Suspense , 它提供两个template slot, 刚开始会渲染一个 fallback 状态下的内容,直到达到某个条件后才会渲染 default 状态的正式内容,通过应用 Suspense 组件进行展现异步渲染就更加的简略。

<Suspense>
      <template #default>
              <!-- 异步渲染 -->
               ...  
      </template>
      <template #fallback>
          <div>
              Loading...
          </div>
      </template>
</Suspense>

Suspense 组件 只是一个带插槽的组件,只是它的插槽指定了 defaultfallback 两种状态。


六、碎片化节点 Fragment

在 Vue2.x 中,template 中只容许有一个根节点:

<template>
    <div>
        <span></span>
        <span></span>
    </div>
</template>

然而在 Vue3.x 中,能够有多个根元素,也能够有把文本作为根元素

<template>
    <span></span>
    <span></span>
</template>

七、插槽与作用域插槽

Vue 之 slot 插槽和作用域插槽

1. 插槽

父组件:

<template>
    <Child>
        <!-- Vue2.x 写法
        <div slot="parent">
            <div> 父组件 </div>
        </div>
         -->
        <template v-slot:parent>
            <div> 父组件 </div>
        </template>
    </Child>
</template>

子组件(Child.vue):

<template>
    <slot name='parent'> 子组件 </slot>
</template>

2. 作用域插槽

父组件:

<template>
    <Child>
        <!-- <template slot="content" slot-scope="scoped">  -->
        <template v-slot:content="scoped">
            <div>{{scoped.myName}}</div>
        </template>
    </Child>
</template>

子组件:

<template>
    <slot name="content" :myName="myName"></slot>
</template>

<script>

import {ref} from 'vue'
export default {setup () {let myName = ref("猫老板的豆")

        return {myName}
    },
}
</script>

退出移动版