初探 Vue3笔记

448 阅读16分钟

前言

上一篇文字里面主要写了Vue2和Vue3的响应式原理及区别

这一篇主要对vue3新特性进行进一步了解与记录,同时也希望给其他小伙伴提供学习借鉴。

☆废话不多说,直接上正题

一、vue3安装与项目创建

参考地址:www.vue3js.cn/docs/zh/gui…

准备工作:脚手架全局安装

对于 Vue 3,你应该使用 npm 上可用的 Vue CLI v4.5 作为 @vue/cli@next。要升级,你应该需要全局重新安装最新版本的 @vue/cli

yarn global add @vue/cli@next
# or
npm install -g @vue/cli@next

然后在 Vue 项目运行:

vue upgrade --next

方式1:以原有vueCli脚手架创建

创建项目:

vue create demo

直接选择 Vue 3 Preview ,并回车,不考虑eslint问题。

Snipaste_2.png 然后执行:

# 进入目录
cd demo
# 用vscode打开当前目录
code .
# 运行项目
npm run serve
or
yarn serve

方式2:vite创建

npm init vite-app demo
# 相当于
npx create-vite-app demo
​
# 安装后执行
npm install
# or
yarn
​
# 运行项目
npm run dev
# or
yarn dev

Snipaste_24.png

创建项目并运行,会发现我们 npm run dev or yarn dev 后是秒开项目的,运行速度极快。

Snipaste_25.png

二、Composition API

相当于 React Hooks

我们先使用以前vue2的方式实现一个累加:

<template>
  <h2>{{count}}</h2>
  <button @click="btnClick">累加</button>
</template><script>
export default {
    data(){
        return {
            count: 0
        }
    },
    methods: {
        btnClick(){
            this.count++;
        }
    }
}
</script>

这套代码可以实现一个累加的效果,但如果以后我们想把这个组件中的 count 字段与 btnClick 单独拎出来管理,那就比较麻烦了,因为 countbtnClick 不在同一个方法内,很难抽离。

1、setup

setup有以下特性:

1、setup函数是处于 生命周期函数 beforeCreateCreated 两个钩子函数之间的函数,在 BeforeMountMounted 之前

我们先下面验证一把

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

<script>
import { reactive, toRefs, onBeforeMount, onMounted } from "vue";
export default {
    name: "",
    beforeCreate() {
        console.log("beforeCreate执行了");
    },
    created() {
        console.log("created执行了");
    },
    setup() {
        console.log("1-开始创建组件-setup");
        const data = reactive({});
        onBeforeMount(() => {
            console.log("2.组件挂载页面之前执行----onBeforeMount");
        });
        onMounted(() => {
            console.log("3.-组件挂载到页面之后执行-------onMounted");
        });
        return {
            ...toRefs(data),
        };
    },
};
</script>

看下面控制图输出的顺序就明白了 image.png

也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的

2、setup函数是 Composition API(组合API)的入口,相比vue2 Options API,两者区别,如图:

223.gif

数据和业务逻辑分散在同一个文件的 N 个地方,随着业务复杂度的上升,可能会出现动图左侧的代码组织方式,不利于管理和维护。

为了解决这个问题,Composition API由此产生。可以把同一功能的数据业务逻辑组织到一起,方便复用和维护。

3、在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用

4、由于我们不能在 setup函数中使用 data 和 methods,所以Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined

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

<script>
import {
    reactive,
    toRefs,
    onMounted,
} from "vue";
export default {
    data() {
        return {
            b: "data数据",
        };
    },
    setup() {
        console.log("this是否有值呢?", this);
        return {
        };
    },
};
</script>

image.png

5、setup函数只能是同步的不能是异步的,但是一定要想变成异步,同步要转异步要特殊处理

在Vue3中,如果当前组件的setup使用了async/await,那么其调用组件的父组件的外层需要嵌套一个suspense标签,例如:

// 子组件
<template> 
    <div>···</div> 
</template>
export default {
    async setup () {
        let ···
        // 接口A
        await getA().then(() => {
            ···
        }).catch(() => {
            ···
        })
        // 接口B
        await getB().then(() => {
            ···
        }).catch(() => {
            ···
        })

        return {
            ···
        }
    }
}
// 父组件
<suspense>
    <async-component/>
</suspense>

2、ref

ref 函数,可以把简单数据类型包裹为响应式数据(复杂类型也可以),注意 JS 中操作值的时候,需要加 .value 属性,模板中正常使用即可。

  • 作用: 定义一个响应式的数据

  • 语法: const xxx = ref(initValue)

    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

我们先来体验一下vue3怎么处理这个累加效果:

<template>
  <h2>{{count}}</h2>
  <button @click="btnClick">累加</button>
</template><script>
import {ref} from 'vue'
export default {
    data(){
        return {
            count: 0
        }
    },
    setup(){
        const count = ref(1);   // 此时我们使用ref指定count的默认值为1,因此上面data中的count会失效
        let btnClick = () => {
            count.value++;      // 修改ref中的值要用xxx.value
        }
        return {count, btnClick}
    }
}

此时如果我想单独管理这个累加效果,我就可以这么操作:

<template>
  <h2>{{count}}</h2>
  <button @click="btnClick">累加</button>
</template><script>
import {ref} from 'vue'
export default {
    data(){
        return {
            // count: 0     // 一旦把setup中的代码抽离,return中对应的值要去掉,否则ref无效
        }
    },
    setup(){
        // 函数调用后就会返回一个对象,因此我们直接return
        return clickCountFn()
        // 如果后期还想同时返回其他数据,可以将clickCountFn()的返回结果展开
        // return {...clickCountFn(), 其他数据}
    }
}
​
// 封装一个函数,这样这块功能我们就能单独管理了
function clickCountFn(){
    const count = ref(1);
    let btnClick = () => {
        count.value++;
    }
    return {count, btnClick}
}
</script>

3、reactive

再来了解另一个API :

reactive函数和ref作用非常接近,但是它的参数是一个对象,我们可以在对象中定义其方法,而通过这个形式,就不需要再对其进行进行 .value 调用了。

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
<template>
  <h2>{{count}}</h2>
  <button @click="btnClick">累加</button>
  <p>姓名:{{obj.username}}</p>
  <button @click="btnClick1">修改姓名</button>
</template><script>
import {ref, reactive} from 'vue'
export default {
    setup(){
        // 使用reactive
        let obj = reactive({
            username: "Jack"
        })
        let btnClick1 = () => {
            obj.username = "Mary"
        }
                return {...clickCountFn(), obj, btnClick1}
    }
}
​
function clickCountFn(){
    const count = ref(1);
    let btnClick = () => {
        count.value++;
    }
    return {count, btnClick}
}
</script>

使用 reactive 生成的对象与 ref 生成的值都是响应式的。

这里可以看到我们在 html 中调用数据时,使用的是 obj.username ,那我们是否可以直接写 username 呢?答案是可以的,但这里需要注意:

由于reactive返回的对象本质上已经是一个Proxy对象,所以通过…扩展符号展开的属性,是无法进行响应式的

也就是说,如果这么写:

return {...clickCountFn(), ...obj, btnClick1}

那么是无法实现的。

4、toRef和toRefs

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
  • 语法:const name = toRef(person,'name')
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person) eg:
<template>
  <h2>{{count}}</h2>
  <button @click="btnClick">累加</button>
    <!-- 无需obj.username,直接username即可 -->
  <p>姓名:{{username}}</p>
  <button @click="btnClick1">修改姓名</button>
</template><script>
// 新增toRefs方法
import {ref, reactive, toRefs} from 'vue'
export default {
    setup(){
        let person = reactive({
            username: "Jack"
        })
        let btnClick1 = () => {
            person.username = "Mary"
        }
        const name2 = toRef(person, "username");
        console.log("####", name2);

        // 通过toRefs方法
        let refObj = toRefs(person);
        // 通过...refObj将数据扩展
        return {...clickCountFn(), ...refObj, btnClick1,name: toRef(person, "username"),}
    }
}
//#region
// 其他代码...
//#endregion
</script>

5、Vue3.0中的响应式原理

1、vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

2、Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    new Proxy(data, {
      // 拦截读取属性值
     get (target, prop) {
          return Reflect.get(target, prop)
   },
      // 拦截设置属性值或添加新属性
      set (target, prop, value) {
        return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
       deleteProperty (target, prop) {
       return Reflect.deleteProperty(target, prop)
      }
   })
            ​
   proxy.name = 'tom'   

6、computed计算属性与watch监视

1、computed函数

  • 用法

    import {computed} from 'vue'setup(){
        ...
        //计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    

2、watch函数

  • 六大使用场景

    • 情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
        console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    • 情况二:监视ref所定义的多个响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    • 情况三:监视reactive所定义的一个响应式数据的全部属性
    watch(person,(newValue,oldValue)=>{
        console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) 
    
    • 情况四:监视reactive所定义的一个响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    • 情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    • 情况六:监视reactive定义的对象中某个属性,deep配置有效
     watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true})
    
    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

所有代码

<template>
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="sum++">点我+1</button>
    <hr />
    <h2>当前的信息为:{{ msg }}</h2>
    <button @click="msg += '!'">修改信息</button>
    <hr />
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>薪资:{{ person.job.j1.salary }}K</h2>
    <button @click="person.name += '~'">修改姓名</button>
    <button @click="person.age++">增长年龄</button>
    <button @click="person.job.j1.salary++">涨薪</button>
</template>

<script>
import { reactive, toRefs, watchEffect, onMounted, ref, watch } from "vue";
export default {
    name: "demo",
    setup() {
        let sum = ref(0);
        let msg = ref("你好啊");
        let person = reactive({
            name: "张三",
            age: 18,
            job: {
                j1: {
                    salary: 20,
                },
            },
        });
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
        console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    ​
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变化了',newValue,oldValue)
    }) 
    ​
    /* 情况三:监视reactive定义的响应式数据
       1.注意:此处无法正确的获取oldValue
       2.注意:强制开启了深度监视(deep配置无效)
    */
    watch(person,(newValue,oldValue)=>{
        console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效//情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    ​
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    ​
    //情况六:监视reactive定义的对象中某个属性,deep配置有效
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true})
    
    

3、watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    
    

7、生命周期

对比生命周期升级到vue3,跟vue2区别,确实本质上没什么区别的

vue2.x的生命周期lifecycle_2

vue3.0的生命周期244a61f139f92d768ccc114bcf9faae6__url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0426%2F768f82b0j00qs65gw002vd200u001jog00ht00x1.jpg&thumbnail=660x2147483647&quality=80&type=jpg.jpg

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:

    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

    • beforeCreate————>setup()
    • created————>setup()
    • beforeMount ————>onBeforeMount
    • mounted————>onMounted
    • beforeUpdate————>onBeforeUpdate
    • updated ————>onUpdated
    • beforeUnmount ————>onBeforeUnmount
    • unmounted ————>onUnmounted

8、自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于vue2.x中的mixin。
  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

三、Provide与Inject跨组件通信

1、Vue2写法

以往我们的父传子是通过props传的:

<!-- Father.vue父组件 -->
<template>
  <Child :num="num" />
</template><script>
import Child from './Child.vue'
export default {
    data(){
        return {
            num: 123
        }
    },
    components: {
        Child
    }
}
</script><!-- Child.vue子组件 -->
<template>
  <h2>父组件传过来的值:{{num}}</h2>
</template><script>
export default {
    props: ['num']
}
</script>

这个时候限制死了数据必须来自父组件,我们其实还有 ProvideInject

<!-- Father.vue父组件 -->
<template>
  <Child />
</template><script>
import Child from './Child.vue'
export default {
    components: {
        Child
    },
    provide: {
        num: 456
    }
}
</script><!-- Child.vue子组件 -->
<template>
  <h2>父组件传过来的值:{{num}}</h2>
</template><script>
export default {
    inject: ['num']
}
</script>

Provide/Inject 相比于 props 的好处在于:

如果组件嵌套较多,那么 props 需要一级一级往下传递,后期很难维护。Provide+Inject 相当于是跨级组件传值,比如孙子组件也想用上面这个 num 的值,就不用一级一级往下传,直接在孙子组件使用即可:

<!-- Sun.vue孙子组件 -->
<template>
  <h4>孙子组件:{{num}}</h4>
</template><script>
export default {
      // 将Sun组件在Child组件中引入,即可实现跨级组件传值
    inject: ['num']
}
</script>

2、Vue3写法

vue3中的 provide/inject。两者都只能在当前活动实例的 setup() 期间调用。

格式为:

// provide
import {provide} from 'vue' // 显式导入
export default {
  setup() {
    // 此处name必须是String类型,value则不限制
    provide(name, value)
  }
}
​
// inject
import {inject} from 'vue' // 显式导入
export default {
    setup(){
        // name即为传过来的字段,第二个参数可选,可填写默认值
        const val = inject(name, defaultValue);
        return {val}
    }
}

我们修改以上案例的代码:

<!-- Father.vue父组件 -->
<template>
  <Child />
</template><script>
import {provide} from 'vue' // 显式导入
import Child from './Child.vue'
export default {
    components: {
        Child
    },
    setup(){
        provide('num', 789)
    }
}
</script><!-- Sun.vue孙子组件 -->
<template>
  <h4>孙子组件:{{mynum}}</h4>
</template><script>
import {inject} from 'vue' // 显式导入
export default {
    setup(){
        const mynum = inject('num');
        return {mynum}
    }
}
</script>

3、响应性

所谓的 Provide/Inject 响应性,其实就是把传递的值结合上文提及的 refreactive 一起使用:

<!-- Father.vue父组件 -->
<template>
  <Child />
  <button @click="changeNumFn">修改num</button>
</template><script>
import {provide, ref} from 'vue' // 显式导入
import Child from './Child.vue'
export default {
    components: {
        Child
    },
    setup(){
        // 使用ref来定义num的值
        const num = ref(123);
​
        // 声明一个函数,专门用于修改num
        let changeNumFn = () => {
            num.value = 456;
        }
​
        provide('num', num)
​
        // 返回这个函数
        return {changeNumFn}
    }
}
</script>

此时,当你点击按钮时,孙子组件接收到的 num 就会被修改了。在父子通信一般都用props,虽然Provide/Inject 子组件也可以取到值,但不推荐。

四、Teleport(传送门)

在vue2中,想要将子节点渲染到存在于父组件以外的 DOM 节点时,需要通过第三方库 portal-vue 去实现。而vue3中,Teleport 是一种能够将我们的模板移动到 DOMVue app 之外的其他位置的技术。

举个最简单的例子:

我们在 index.html#app 同级的地方新增一个 #test 元素:

<div id="app"></div>
<div id="test"></div>

由于vue的 main.js 中规定了打包出来的代码都放入 #app 中:

createApp(App).mount('#app')

因此,你现在没有办法将代码放入 #test 中。此时,我们可以使用传送门:

App.vue 中:

<template>
  <Home />
</template><script>
import Home from './components/Home.vue'
export default {
  name: 'App',
  components: {
    Home
  }
}
</script>

Home.vue 中:

<template>
  <p>这段话是渲染在#app中的</p>
  <teleport to="#test">
      <p>这段话是渲染在#test中的--1</p>
  </teleport>
    <teleport to="#test">
      <p>这段话是渲染在#test中的--2</p>
  </teleport>
</template>

此时,你打开浏览器控制台,就可以看到第2、3个p标签已经被渲染到 #test 中。

备注

  • 1、标签身上都to属性,填写的是css选择器。
  • 2、多个传送门书写时,会按照自上而下的顺序传送至另一个DOM元素。

五、Suspense(等待)

Suspense组件用于等待异步组件时渲染一些额外内容,让应用有更好的用户体验。

那我们什么时候需要使用异步组件呢?多了去了,比如:

  • 在页面加载之前显示加载动画
  • 显示占位符内容
  • 处理延迟加载的图像

那么,让我们看看 Suspense 怎么使用,我们先来提一个需求:

在等待组件获取数据并解析时显示“玩命加载中…”之类的内容

OK,我们来写一个 Child.vue 组件:

使用步骤:

        // 异步引入组件
        import {defineAsyncComponent} from 'vue'
        const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
        
        // 使用`Suspense`包裹组件,并配置好`default` 与 `fallback`
        <template>
            <div class="app">
                <h3>我是App组件</h3>
                <Suspense>
                    <template v-slot:default>
                        <Child/>
                    </template>
                    <template v-slot:fallback>
                        <h3>加载中.....</h3>
                    </template>
                </Suspense>
            </div>
        </template>

六、Fragment(碎片)

  • 在Vue2中: 组件必须有一个根标签:
<template>
    <div>你好欢迎我的世界</div>
</template>
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
<template>
    <div>你好</div>
    <div>欢迎</div>
    <div>我的世界</div>
</template>

原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。

这情况同样存在于react,但react可以使用空标签 <></> 来包裹,或者是使用一个名为Fragment的虚拟元素:

class Columns extends React.Component {
  render() {
    return (
        <React.Fragment>
            <td>你好</td>
            <td>世界</td>
        </React.Fragment>
        <>
            <td>你好</td>
            <td>世界</td>
        </>
    )
  }
}

尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。目前你可以在Vue 2中使用vue-fragments库来使用Fragments,而在Vue 3中,你直接使用就行了,无需引入任何库。

七、TreeShaking(摇树)

TreeShaking 是一个术语,指的是在打包构建过程中移除没有被引用到的代码,这些代码可以成为 dead code。这个概念最早在基于 ES6 的打包工具 Rollup 中提出,后来被引入到 webpack 中。TreeShaking 比较依赖于 ES6 模块系统的静态结构特性,比如 importexport

举个例子:

vue2中我们常使用 Vue.nextTick(()=>{}) 来预操作DOM,但有时候我们不用这个 nextTick ,比如改用别的方式来代替(如setTimeout),那么项目打包时,vue 全局的 nextTick 就成为一个多余的代码,从而使你的项目打包体积变大。

在vue3中,官方团队重构了所有全局 API 的组织方式,让所有的 API 都支持了 TreeShaking。所以vue3中如果还想使用全局的 nextTick ,就需要引入:

import { nextTick } from 'vue';
 
nextTick(() => {
  // 和 DOM 有关的一些操作
});

如果你在 Vue 3 中不引入而直接调用 Vue.nextTick() ,就会得到一个报错:undefined is not a function

官方也给出了Vue 2.x 中的受此更改影响的全局 API:

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile (仅全构建)
  • Vue.set (仅兼容构建)
  • Vue.delete (仅兼容构建)

八、Performance(性能)

vue3.0相对于vue2.0来说性能快1.2到1.5倍,主要原因如下:

1、diff方法优化

  • Vue2 中的虚拟dom是进行全量的对比
  • Vue3 新增了静态标记(PatchFlag),只比对带有 PF 的节点,并且通过 Flag 的信息得知 当前节点要比对的具体内容。

2、静态提升

  • Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
  • Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可

3、cacheHandlers 事件侦听器缓存

  • 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化
  • 但是因为是同一个函数,所以没有追踪变化, 直接缓存起来复用即可

4、SSR渲染

  • 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面, 即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟DOM来渲染的快上很多很多。
  • 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node, 这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

九、Setup的生命周期

组合式API需要在setup中使用,setup中含有的生命钩子与vue的大体一致:

具体参考:《setup生命周期钩子》

十、TypeScript支持

vue3新增了对TS语法的支持。

//script标签上 **lang="ts"**
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
//定义一个类型type或者接口interface来约束data
type Todo = {
  id: number,
  name: string,
  completed: boolean
}

export default defineComponent({
	//使用reactive时,可以用toRefs解构导出,在template就可以直接使用了
	const data = reactive({
	  todoList: [] as Todo[]
	})
	//可以使用ref或者toRefs来定义响应式数据
	const count = ref(0);
	//使用ref在setup读取的时候需要获取xxx.value,但在template中不需要
	console.log(count.value)
	return {
	  ...toRefs(data)
	}
})
</script>

十一、其它 Composition API

跟响应式数据有关的方法、跨组件通信、自定义ref

1、shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 啥时候使用合适?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
        // let person = shallowReactive({
         let person = reactive({
            name: "张三",
            age: 18,
            job: {
                j1: {
                    salary: 20,
                },
            },
        });
        // 只考虑基本类型的响应式,不考虑对象类型的响应式
        let x = shallowRef({
            y: 0,
        });
        console.log("******", x);
        //返回一个对象(常用)
        return {
            x,
            person,
            ...toRefs(person),
        }; 

2、readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。
<template>
    <h4>当前求和为:{{ sum }}</h4>
    <button @click="sum++">点我++</button>
    <hr />
    <h4>{{ person }}</h4>
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>薪资:{{ job.j1.salary }}K</h2>
</template>

<script>
import { reactive, toRefs, onMounted, ref } from "vue";
export default {
    setup() {
  //数据
        let sum = ref(0);
        let person = reactive({
            name: "张三",
            age: 18,
            job: {
                j1: {
                    salary: 20,
                },
            },
        });

        // person = readonly(person);
        person = shallowReadonly(person);
        // sum = readonly(sum);
        // sum = shallowReadonly(sum);

        //返回一个对象(常用)
        return {
            sum,
            ...toRefs(person),
        };
   },
};
</script>

3、toRaw 与 markRaw

  • toRaw

    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw

    • 作用:标记一个对象,使其永远不会再成为响应式对象。

    • 应用场景:

      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
<template>
    <h4>当前求和为:{{ sum }}</h4>
    <button @click="sum++">点我++</button>
    <hr />
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>薪资:{{ job.j1.salary }}K</h2>
    <h3 v-show="person.car">座驾信息:{{ person.car }}</h3>
    <button @click="name += '~'">修改姓名</button>
    <button @click="age++">增长年龄</button>
    <button @click="job.j1.salary++">涨薪</button>
    <button @click="showRawPerson">输出最原始的person</button>
    <button @click="addCar">给人添加一台车</button>
    <button @click="person.car.name += '!'">换车名</button>
    <button @click="changePrice">换价格</button>
</template>
<script >
import { ref, reactive, toRefs, toRaw, markRaw } from "vue";
export default {
    name: "Demo",
    setup() {
        //数据
        let sum = ref(0);
        let person = reactive({
            name: "张三",
            age: 18,
            job: {
                j1: {
                    salary: 20,
                },
            },
        });
        /* - 
        作用:将一个由```reactive```生成的----响应式对象--->转为---普通对象---。
        - 使用场景:用于读取响应式对象对应的普通对象,
        对这个普通对象的所有操作,不会引起页面更新。 */
        const showRawPerson = () => {
            const p = toRaw(person);
            p.age++;
            console.log(p);
        };

        const addCar = () => {
            let car = { name: "奔驰", price: 40 };
            person.car = markRaw(car);
        };
        /* markRaw:

- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
  1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
  2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。 */
        const changePrice = () => {
            person.car.price++;
            console.log(person.car.price);
        };

        //返回一个对象(常用)
        return {
            sum,
            person,
            ...toRefs(person),
            showRawPerson,
            addCar,
            changePrice,
        };
    },
};
</script>

4、customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    <template>
        <input type="text" v-model="keyword">
        <h3>{{keyword}}</h3>
    </template><script>
        import {ref,customRef} from 'vue'
        export default {
            name:'Demo',
            setup(){
                // let keyword = ref('hello') //使用Vue准备好的内置ref
                //自定义一个myRef
                function myRef(value,delay){
                    let timer
                    //通过customRef去实现自定义
                    return customRef((track,trigger)=>{
                        return{
                            get(){
                                track() //告诉Vue这个value值是需要被“追踪”的
                                return value
                            },
                            set(newValue){
                                clearTimeout(timer)
                                timer = setTimeout(()=>{
                                    value = newValue
                                    trigger() //告诉Vue去更新界面
                                },delay)
                            }
                        }
                    })
                }
                let keyword = myRef('hello',500) //使用程序员自定义的ref
                return {
                    keyword
                }
            }
        }
    </script>
    

5、provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
          ......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      setup(props,context){
          ......
          const car = inject('car')
          return {car}
          ......
      }
      

6、响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

十二、其他变动

1、全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      ​
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue3.x 实例 API (app)
      Vue.config.xxxxapp.config.xxxx
      Vue.config.productionTip移除
      Vue.componentapp.component
      Vue.directiveapp.directive
      Vue.mixinapp.mixin
      Vue.useapp.use
      Vue.prototypeapp.config.globalProperties

2、其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
      
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      ​
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
      
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
      
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

十三、Composition API 的优势

1、Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

2、Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

结语

如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下阿绵哈哈。

宝贝们,都看到这里了,要不点个赞呗 👍

写作不易,希望可以获得你的一个「赞」。如果文章对你有用,可以选择「关注 + 收藏」。 如有文章有错误或建议,欢迎评论指正,谢谢你。❤️

参考网址

Vue官网文档:v3.cn.vuejs.org/

Vue中文文档:www.vue3js.cn/docs/zh/gui…

Vite中文网:vitejs.cn/

在线编辑器:codesandbox.io/