从0开始手把手带你入门Vue3-全网最全(1.1w字)

vue

天命不足畏,祖宗不足法。 ——王安石

前言

本文并非标题党,而是实实在在的硬核文章,如果有想要学习Vue3的网友,可以大致的浏览一下本文,总体来说本篇博客涵盖了Vue3中绝大部分内容,包含常用的CompositionAPI(组合式API)、其它CompositionAPI以及一些新的特性:Fragment、Teleport、Suspense、provide和inject。

项目搭建

既然是学习Vue3,那么首先应该需要的是如何初始化项目,在这里提供了两种方式供大家参考

  • 方式一:vue-cli脚手架初始化Vue3项目

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

//	查看@vue/cli版本,确保@vue/cli版本在4.5.0以上

vue --version

// 安装或者升级你的@vue/cli

npm install -g @vue/cli

// 创建

vue create vue_test

// 启动

cd vue_test

npm run serve

  • 方式二:vite初始化Vue3项目

vite官网:https://vitejs.cn/

//	 创建工程

npm init vite-app <project-name>

// 进入工程目录

cd <project-name>

// 安装依赖

npm install

// 运行

npm run dev

项目目录结构分析

这里的项目目录结构分析主要是main.js文件

  • Vue2里面的main.js

new Vue({

el: '#app',

components: {},

template: ''

});

  • Vue3里面的main.js

import { createApp } from 'vue'

import App from './App.vue'

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

在Vue2里面,通过new Vue({})构造函数创建应用实例对象,而Vue3引入的不再是Vue的构造函数,引入的是一个名为createApp的工厂函数创建应用实例对象。

Vue3-devtool获取

devtool:https://chrome.zzzmh.cn/info?token=ljjemllljcmogpfapbkkighbhhppjdbg

Composition API

setup

  • 理解:Vue3.0中一个新的配置项,值为一个函数

  • setup是所有Composition API(组合式API)的入口

  • 组件中所用到的数据、方法等等,均要配置在setup里面

  • setup函数的两种返回值

    • 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用
    • 若返回一个渲染函数,则可以自定义渲染内容

  • setup的执行时机

    • 在beforeCreate之前执行一次,此时this为undefined

  • setup的参数

    props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性

    context:上下文对象

    • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
    • slots:收到的插槽内容,相当于this.$slots
    • emit:分发自定义事件的函数,相当于this.$emit

注意事项:

  • 尽量不要与Vue2x的配置使用

    • Vue2x的配置(data、methods、computed)均可以访问到setup中的属性、方法
    • setup中不能访问Vue2x的配置(data、methods、computed)
    • 如果data里面的属性和setup里面的属性有重名,则setup优先

  • setup不能是一个async函数,因为返回值不再是return的对象,而是Promise,模板看不到return对象中的属性,但是后期也可以返回一个Promise实例,需要Suspense和异步组件的配合

示例一:setup函数的两种返回值

<template>

<h2>练习setup相关内容</h2>

<!--<h2>setup返回一个对象,并使用对象中的属性和方法</h2>-->

<!--<p>姓名:{{student.name}}</p>-->

<!--<p>年龄:{{student.age}}</p>-->

<!--<button @click="hello">点击查看控制台信息</button>-->

<hr>

<h2>setup返回一个函数</h2>

</template>

<script>

import {h} from 'vue'

export default {

name: "setupComponent",

setup(){

// 属性

let student={

name:'张三',

age:18,

}

// 方法

function hello() {

console.log(`大家好,我叫${student.name},今年${student.age}`)

}

return{ // 返回一个对象

student,

hello,

}

// return()=>h('h1','你好') // 返回一个函数

}

}

</script>

<style scoped>

</style>

这里需要注意的是setup里面定义的属性和方法均要return出去,否则无法使用

示例二:setup里面的参数和方法和配置项混合使用

<template>

<h2>setup和配置项混用</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>性别:{{sex}}</h2>

<button @click="sayHello">sayHello(Vue3里面的方法)</button>

<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>

</template>

<script>

export default {

name: "setup01_component",

data(){

return{

sex:'男',

sum:0,

}

},

methods:{

sayWelcome(){

console.log(`sayWelcome`)

},

},

setup(){

let sum=100;

let name='张三';

let age=18;

function sayHello() {

console.log(`我叫${name},今年${age}`)

}

return{

name,

age,

sayHello,

test02,

sum

}

}

}

</script>

<style scoped>

</style>

这段代码是先实现了setup里面的属性和方法,以及Vue2中配置项里面的属性和方法。接下来添加对应的混合方法

<template>

<h2>setup和配置项混用</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>性别:{{sex}}</h2>

<button @click="sayHello">sayHello(Vue3里面的方法)</button>

<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>

<br>

<br>

<button @click="test01">测试Vue2里面调用Vue3里面的属性和方法</button>

<br>

<br>

<button @click="test02">测试Vue3setup里面调用Vue2里面的属性和方法</button>

<br>

<h2>sum的值是:{{sum}}</h2>

</template>

<script>

export default {

name: "setup01_component",

data(){

return{

sex:'男',

sum:0,

}

},

methods:{

sayWelcome(){

console.log(`sayWelcome`)

},

test01(){

console.log(this.sex); // Vue2里面的属性(data里面的属性)

// setup里面的属性

console.log(this.name);

console.log(this.age);

// setup里面的方法

this.sayHello();

}

},

setup(){

let sum=100;

let name='张三';

let age=18;

function sayHello() {

console.log(`我叫${name},今年${age}`)

}

function test02() {

// setup里面的属性

console.log(name);

console.log(age);

// data里面的属性

console.log(this.sex);

console.log(this.sayWelcome);

}

return{

name,

age,

sayHello,

test02,

sum

}

}

}

</script>

<style scoped>

</style>

这里新增了test01和test02方法,分别点击,控制台可以看到,点击配置项里面test01方法时,除了自身的sex属性有值,setup里面定义的属性也有值以及方法也可以调用,点击setup里面定义的test02方法时,控制台只能输出setup里面定义的属性和方法,而配置项里面定义的属性和方法值均为undefined。

  • setup里面定义的属性和方法均可以在配置项里面使用(methods、computed、watch等),而配置项里面定义的属性和方法无法在setup里面调用
  • 如果setup里面的属性和data里面的属性有重名,则setup里面的属性优先

示例三:setup的执行时机

setup会在beforeCreate之前执行一次

<template>

<h2>setup的执行机制</h2>

</template>

<script>

export default {

name: "setup_component03",

setup(){

console.log('setup')

},

beforeCreate(){

console.log('beforeCreate')

}

}

</script>

<style scoped>

</style>

查看控制台的话我们看到的顺序是setup>beforeCreate

setup里面context和props的使用

Vue2里面props和slot的使用

讲解setup这里面的两个参数之前,先回顾一下Vue2里面的相关知识

  • props和自定义事件的使用
  • attrs
  • slot(插槽)

示例一:Vue2props和自定义事件的使用

准备两个组件,分别为parent.vue组件和child.vue组件

parent.vue

<template>

   <div class="parent">

    我是父组件

     <child msg="传递信息" name="张三" @sendParentMsg="getMsg"/>

   </div>

</template>

<script>

   import Child from "./Child";

   export default {

       name: "Parent",

     components: {Child},

     methods:{

       getMsg(msg){

         console.log(msg)

      }

    }

  }

</script>

<style scoped>

 .parent{

   padding: 10px;

   background-color: red;

}

</style>

child.vue

<template>

   <div class="child">

     <h2>我是子组件</h2>

     <p>父组件传递过来的消息是:{{msg}}</p>

     <p>父组件传递过来的消息是:{{name}}</p>

     <button @click="sendMsg">向父组件的传递信息</button>

   </div>

</template>

<script>

   export default {

       name: "Child",

       props:{

         msg:{

           type:String,

           default:''

        },

         name:{

           type:String,

           default:''

        }

      },

       mounted(){

         console.log(this);

      },

       methods:{

         sendMsg(){

           this.$emit("sendParentMsg",'通知父组件更新')

        }

      }

  }

</script>

<style scoped>

 .child{

   padding: 10px;

   background-color: orange;

}

</style>

child组件对应的代码如下:

<template>

   <div class="child">

     <h2>我是子组件</h2>

     <!--<p>父组件传递过来的消息是:{{msg}}</p>-->

     <!--<p>父组件传递过来的消息是:{{name}}</p>-->

     <p>父组件传递过来的消息是:{{$attrs.msg}}</p>

     <p>父组件传递过来的消息是:{{$attrs.name}}</p>

     <button @click="sendMsg">向父组件的传递信息</button>

   </div>

</template>

<script>

   export default {

       name: "Child",

       // props:{

       //   msg:{

       //     type:String,

       //     default:''

       //   },

       //   name:{

       //     type:String,

       //     default:''

       //   }

       // },

       mounted(){

         console.log(this);

      },

       methods:{

         sendMsg(){

           this.$emit("sendParentMsg",'通知父组件更新')

        }

      }

  }

</script>

<style scoped>

 .child{

   padding: 10px;

   background-color: orange;

}

</style>


子组件通过props接收父组件传递的信息,通过this.$emit()自定义事件向父组件传递信息。当使用props接收数据的时候,attrs里面的数据为空,如果没有使用props接收数据的话,那么props里面就有值。

示例二:Vue2里面slot的使用

同理准备两个组件,一个Index.vue组件,另一个为MySlot.vue组件

Index.vue

<template>

   <div class="index">

     <h2>我是Index组件</h2>

     <!--写法一-->

     <my-slot>

       <!--插槽里面的内容-->

       <h2>传入的slot参数</h2>

       <h2>传入的slot参数</h2>

       <h2>传入的slot参数</h2>

       <h2>传入的slot参数</h2>

     </my-slot>

     <!--写法二-->

     <my-slot>

       <template slot="header">

         <h2>我是header组件</h2>

       </template>

       <template slot="footer">

         <h2>我是footer附件</h2>

       </template>

     </my-slot>

   </div>

</template>

<script>

   import MySlot from "./MySlot";

   export default {

       name: "Index",

     components: {MySlot}

  }

</script>

<style scoped>

 .index{

   padding: 10px;

   background: red;

}

</style>

MySlot.vue

<template>

   <div class="slot">

     <h2>我是MySlot组件</h2>

     <slot></slot>

     <br>

     <slot name="header"></slot>

     <br>

     <slot name="footer"></slot>

   </div>

</template>

<script>

   export default {

       name: "MySlot",

       mounted(){

         console.log(this);

      }

  }

</script>

<style scoped>

 .slot{

   padding: 10px;

   background: orange;

}

</style>

ref

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

  • 语法:const xxx=ref(initValue)

  • 创建一个包含响应式数据的引用对象(reference对象);

  • JS中操作数据:xxx.value=xxx;

  • 模板中读取数据:不需要.value,直接:

    {{xxx}}

    备注:

    • 接收的数据可以是:基本类型,也可以是对象类型
    • 基本类型的数据:响应式依然是靠Object.defineProperty()的get和set完成的
    • 对象类型的数据:内部求助了Vue3.0中的一个新函数-reactive函数

      示例

<template>

<h1>ref</h1>

<h2>ref定义基本数据类型</h2>

<p>姓名:{{name}}</p>

<p>年龄:{{age}}</p>

<p>婚否:{{isMarry}}</p>

<h2>ref定义对象类型</h2>

<p>爱好:{{hobby}}</p>

<p>证件类型:{{user.idCard}}</p>

<p>国籍:{{user.nation}}</p>

<button @click="changeName">修改信息</button>

</template>

<script>

import {ref} from 'vue'

export default {

name: "refComponent",

setup(){

// 使用基本数据类型 number,string,boolean,

let name=ref('张三');

let age=ref(18);

let isMarry=ref(false);

// 使用ref定义数组

let hobby=ref(['吃饭','睡觉','打豆豆']);

// 使用ref定义对象

let user=ref({

idCard:'身份证',

nation:['中国','美国','英国','俄罗斯']

})

function changeName() {

// 修改基本数据数据类型

name.value='李四'; // ref定义的响应式数据修改数据时必需要.value

age.value=20;

isMarry.value=true;

// 修改对象数据类型

hobby.value[0]='玩游戏';

user.value.idCard='港澳台居民身份证';

user.value.nation[0]='挪威';

}

return{

name,

age,

isMarry,

changeName,

user,

hobby

}

}

}

</script>

<style scoped>

</style>

注意:

  • ref定义的响应式数据修改数据时必需要.value
  • ref定义的对象数据类型,内部求助了Vue3.0中的一个新函数-reactive函数
  • 模板中使用数据时不需要.value

reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型别用它,用ref函数)
  • 语法:const 代理对象=reactive(被代理的对象)接收一个对象(或数组),返回一个代理器对象(Proxy的实例对象,简称Proxy对象)
  • reactive定义的响应式数据是深层次的
  • 内部基于ES6的Proxy实现,通过代理对象的操作源对象的内部数据都是响应式的

<template>

<h2>reactive响应式数据</h2>

<p>姓名:{{student.name}}</p>

<p>年龄:{{student.age}}</p>

<p>爱好:{{student.hobbies}}</p>

<button @click="changeStuInfo">改变学生信息</button>

</template>

<script>

import {reactive} from 'vue'

export default {

name: "reactiveComponent",

setup(){

// 数据

let student=reactive({

name:'张三',

age:19,

hobbies:['吃饭','睡觉','打豆豆']

});

console.log(student)

// 方法

function changeStuInfo() {

student.name='李四';

student.age=20;

student.hobbies[0]='做家务'

}

return{

student,

changeStuInfo,

}

}

}

</script>

<style scoped>

</style>

reactive对比ref

  • 从定义数据的角度对比

    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型的数据,它内部会自动通过reactive转为代理对象

  • 从原理角度对比

    • ref通过Object.defineProperty()的get和set实现(响应式)数据劫持
    • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据

  • 从使用角度

    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据均不需要.value

watch和watchEffect

    //	attr表示需要监视的属性

// 情况一:监视单个ref定义的响应式数据

watch(attr,(newValue,oldValue)=>{

console.log('attr变化了',newValue,oldValue);

})

// 情况二; 监视多个ref定义的响应式数据

watch([attr1,attr2,....,attrn],(newValue,oldValue)=>{

console.log('attr1或attrn变化了',newValue,oldValue);

})

// obj表示需要监听的对象

// 情况三:监视reactive定义的响应式数据

// 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue

// 若watch监视的是reactive定义的响应式数据,则强制打开开启了深度监视

watch(obj,(newValue,oldValue)=>{

console.log('obj变化了',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.name,()=>person.age],(newValue,oldValue)=>{

console.log('person的job变化了',newValue,oldValue)

})

// 特殊情况

watch(()=>person.job,(newValue,oldValue)=>{

console.log('person的job变化了',newValue,oldValue)

},{deep:false});// 此处由于是监视reactive所定义的对象中的某个属性,所以deep配置有效

  • watch

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
    • 监视reactive定义的响应式数据中某个属性时deep配置有效

      示例一:wath监听ref定义的响应式数据

<template>

<h2>watch监听ref定义的响应式数据</h2>

<h2>姓名:{{userName}}</h2>

<h2>年龄:{{age}}</h2>

<button @click="userName+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<hr>

<h2>姓名:{{user.name}}</h2>

<h2>年龄:{{user.age}}</h2>

<button @click="user.name+='!'">修改姓名</button>

<button @click="user.age++">修改年龄</button>

</template>

<script>

import {ref,watch} from 'vue';

export default {

name: "watch_component01",

setup(){

let userName=ref('张三');

let age=ref(18);

let user=ref({

name:'张三',

age:21,

})

// watch监听ref定义的单个响应式数据

watch(userName,(newValue,oldValue)=>{

console.log(`userName发生了变化,新值是:${newValue},旧值是:${oldValue}`)

});

watch(age,(newValue,oldValue)=>{

console.log(`age发生了变化,新值是:${newValue},旧值是:${oldValue}`);

});

// 如果需要监听多个ref定义的响应式数据的话,代码如下

/**

* newValue和oldValue都是数组的形式,其中数组的第n位表示监听位置的第n位

* 例如:此例子中,监听属性的第一位是userName,那位newValue和oldValue对应的第一位也是

* userName。

* 如果有立即执行,那么最开始的值为[],而不是[undefined,undefined]

*/

watch([userName,age],(newValue,oldValue)=>{

console.log('userName或age中的其中一个发生了变化,',newValue,oldValue)

})

// watch监视ref定义的响应式对象数据

watch(user.value,(newValue,oldValue)=>{

console.log('person发生了变化',newValue,oldValue)

})

watch(user,(newValue,oldValue)=>{

console.log('person发生了变化',newValue,oldValue);

},{deep:false})

return{

userName,

age,

user

}

}

}

</script>

<style scoped>

</style>

示例二:watch监听reactive定义的响应式数据

<template>

<h1>watch监听reactive定义的响应式数据</h1>

<p>姓名:{{user.name}}</p>

<p>年龄:{{user.age}}</p>

<p>薪水:{{user.job.salary}}K</p>

<button @click="user.name+='!'">改变姓名</button>

<button @click="user.age++">改变年龄</button>

<button @click="user.job.salary++">改变薪水</button>

</template>

<script>

import {watch,reactive} from 'vue'

export default {

name: "watch_component02",

setup(){

let user=reactive({

name:'张三',

age:18,

job:{

salary:20

}

});

// 情况一:监听reactive定义的响应式数据,无法正确获取oldValue

/**

* 此时的newValue和oldValue都是最新的数据

* 默认强制开启深度监视,此时深度监视失效

*/

watch(user,(newValue,oldValue)=>{

console.log(newValue,oldValue);

},{deep:false});

// 情况二,监视reactive定义的响应式数据的单个属性

// watch(()=>user.name,(newValue,oldValue)=>{

// console.log('name发生了变化',newValue,oldValue);

// });

// watch(()=>user.age,(newValue,oldValue)=>{

// console.log('age发生了变化',newValue,oldValue);

// })

// 情况三:监视reactive定义的响应式数据的多个属性

/**

* newValue和oldValue都是数组的形式,其中数组的第n位表示监听位置的第n位

* 例如:此例子中,监听属性的第一位是userName,那位newValue和oldValue对应的第一位也是

* userName,

*/

// watch([()=>user.name,()=>user.age],(newValue,oldValue)=>{ // 写法一

// console.log('name或age中的某个属性发生了变化',newValue,oldValue);

// })

// watch(()=>[user.name,user.age],(newValue,oldValue)=>{ // 写法二

// console.log('name或者age中的某个属性发生了变化',newValue,oldValue)

// })

// 情况四:监视reactive定义的响应式数据的对象的某个属性,此时deep有效

/**

* 注意:此时需要区别是reactive定义的对象还是reactive定义的对象里面的某个属性

* 此时deep有效,关闭了监视

*/

// watch(()=>user.job,(newValue,oldValue)=>{

// console.log(newValue,oldValue);

// },{deep:false});

return{

user

}

}

}

</script>

<style scoped>

</style>

  • watchEffect

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

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

    • watchEffect有点像computed

      • 但computed注重的是计算出来的值(回调函数的返回值),所以必需要写返回值
      • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

        示例

<template>

<h1>watchEffect监视ref和reactive定义的响应式数据</h1>

<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}}</h2>

<button @click="person.name+='!'">修改姓名</button>

<button @click="person.age++">修改年龄</button>

<button @click="person.job.j1.salary++">涨薪</button>

</template>

<script>

import {ref,reactive,watchEffect} from 'vue';

export default {

name: "watch_effect_component01",

setup(){

let sum=ref(0);

let msg=ref('你好');

let person=reactive({

name:'张三',

age:18,

job:{

j1:{

salary:100,

}

}

});

/**

* 在watchEffect里面写需要监视的属性,默认会执行一次

* 如果是监视ref定义的响应式书则需要.value

* 如果是监视reactive定义的响应式数据则直接监视

*/

watchEffect(()=>{

let x1=sum.value;

let x2=person.job.j1.salary;

console.log('watchEffect所指定的回调函数执行了');

})

return{

sum,

msg,

person

}

}

}

</script>

<style scoped>

</style>

Vue2响应式原理VSVue3响应式原理

在Vue2中主要是通过数据劫持来实现响应式原理的,也就是基于Object.defineProperty来实现的,而Vue3中是通过Proxy和Reflect来实现的(个人最低层次上的理解,还望各位大佬海涵)。

那么我们来对比一下Vue3实现响应式的原理比Vue2实现响应式的原理好在哪里?

Vue2响应式原理

  • 实现原理

    • 对象类型:通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
    • 数组类型:通过重写更新数组的的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

Object.defineProperty(data,'count',{

get(){}

set(){}

})

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

    先看一个简单的示例

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Title</title>

</head>

<body>

<div id="app">

<h1>学生信息</h1>

<h4>姓名:{{student.name}}</h4>

<h4>年龄:{{student.age}}</h4>

<h4 v-if="student.sex">性别:{{student.sex}}</h4>

<h4>爱好:{{student.hobbies}}</h4>

<button @click="addSex">新增性别</button>

<button @click="deleteAge">删除年龄</button>

<button @click="updateHobbies">修改爱好</button>

</div>

<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>

<script>

let vm=new Vue({

el:'#app',

data(){

return{

student:{

name:'张三',

age:18,

hobbies:['吃饭','睡觉','打豆豆']

}

}

},

methods:{

addSex(){ // 新增性别

this.student.sex='male';

console.log(this.student);

},

deleteAge(){ // 删除年龄

delete this.student.age;

console.log(this.student);

},

updateHobbies(){ // 修改爱好

this.student.hobbies[0]='玩游戏';

console.log(this.student);

}

}

})

</script>

</body>

</html>

分别调用按钮对应的方法,控制台可以看到,新增性别属性时,student里面有性别这个属性,但是并没有实现响应式(视图没有更新),同理,其它两个按钮对应的方法也是一样。

原因:Vue2.0想要实现响应式数据的话,必需先在data里面定义,之后重新添加的数据无法实现响应式。

解决方案:

  • 新增/修改:vue.$set(target,propName/index,value)

    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value

  • 删除:vue.$delete(target,propName/index)

    • {Object | Array} target
    • {string | number} propertyName/index

此时我们修改对应的代码如下

    addSex(){   // 新增性别

// this.student.sex='male';

// vm.$set(this.student,'sex','male');

this.$set(this.student,'sex',male);

console.log(this.student);

},

deleteAge(){ // 删除年龄

// delete this.student.age;

// this.$delete(this.student,'age');

vm.$delete(this.student,'age');

console.log(this.student);

},

updateHobbies(){ // 修改爱好

// this.student.hobbies[0]='玩游戏';

// this.$set(this.student.hobbies,0,'玩游戏');

// vm.$set(this.student.hobbies,0,'玩游戏');

/**

* 或者使用数组变异的方法

* push()

* pop()

* shift()

* unshift()

* splice()

* sort()

* reverse()

*/

this.student.hobbies.splice(0,1,'玩游戏');

console.log(this.student);

}

弊端

  • 必需定义在data里面的数据才能实现响应式
  • 如果后面添加的数据想要实现响应式,那么就需要调用对应的API

Object.defineProperty的简单示例

    let student={

name:'张三',

age:18,

}

let p={}

Object.defineProperty(p,'name',{

get(){ // 读取name时触发

console.log('读取了name属性');

return student.name;

},

set(value){ // 修改name时触发

console.log('name发生了变化,视图发生了变化');

student.name=value;

}

});

console.log(p.name);

p.name='李四';

p.sex='male';

delete p.name;

Vue3响应式原理

关于Proxy和Reflect的用法这里不过多介绍,如果有想要了解的推荐看MDN或者阮一峰老师的ES6

  • https://es6.ruanyifeng.com/
  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

示例

    let user={

name:'张三',

age:18,

}

let p=new Proxy(user,{

get(target,propName){

console.log(`读取了p里面的${propName}属性`);

Reflect.get(target,propName);

// return target[propName];

},

set(target,propName,value){

console.log(`修改了p里面的${propName}属性`);

// target[propName]=value;

Reflect.set(target,propName,value);

},

deleteProperty(target,propName){

console.log(`删除了p里面的${propName}属性`);

// delete target[propName];

Reflect.deleteProperty(target,propName);

}

});

console.log(p.name);

p.name='李四';

p.sex='male';

delete p.age;

查看控制台,当读取name属性的时候触发get()方法,新增或者修改属性的时候触发set()方法,删除的时候触发deleteProperty()方法,这就是Vue3.0对响应式的改进。

生命周期和钩子函数

Vue2.0生命周期和钩子函数

vue3.0生命周期和钩子函数

Vue2和Vue3生命周期对比
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
activatedonActivated
deactivatedonDeactivated

因为 setup是围绕 beforeCreate和 created生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup函数中编写。

Vue3x中可以继续使用Vue2x中的生命周期钩子,但有两个被更名

  • beforeDestory改名为beforeUnmout
  • destoryed改名为unmouted

<template>

<!--Vue3x生命周期和钩子函数-->

<h3>Vue3x生命周期和钩子函数</h3>

<h3>数字:{{num}}</h3>

<button @click="num++">点我加加</button>

</template>

<script>

import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'

export default {

name: "lifeCycleComponent",

setup(){

let num=ref(0);

console.log("======setup=========");

onBeforeUnmount(()=>{

console.log("======onBeforeUnmount=========");

});

onMounted(()=>{

console.log("======onMounted=========");

});

onBeforeUpdate(()=>{

console.log("======onBeforeUpdate=========");

});

onUpdated(()=>{

console.log("======onUpdated=========");

});

onBeforeUnmount(()=>{

console.log("======onBeforeUnmount=========");

})

onUnmounted(()=>{

console.log("======onUnmounted=========");

});

return{

num,

}

}

}

</script>

<style scoped>

</style>

自定义hook

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

作为初学hook的我来说,我对于hook并没有了解多少,这里贴一些自己练习中的示例,现阶段的我感觉不出来export 导出函数和hook的区别和优点。示例的话是鼠标点击的时候获取当前坐标

示例

<template>

<h2>自定义hook</h2>

<h2>当前x的坐标:{{x}},当前y的坐标:{{y}}</h2>

</template>

<script>

import {reactive,toRefs,onMounted,onUnmounted} from 'vue'

export default {

name: "hook_component01",

setup(){

// 数据

let point=reactive({

x:0,

y:0,

})

// 方法

function getPoint(event){

console.log(event)

point.x=event.clientX;

point.y=event.clientY;

}

// 生命周期钩子函数

onMounted(()=>{

window.addEventListener('click',getPoint);

})

onUnmounted(()=>{

window.removeEventListener('click',getPoint);

})

return{

...toRefs(point)

}

}

}

</script>

<style scoped>

</style>

抽离单独的hook

  • 新建目录hook
  • hook目录下新建文件usePoint.js

usePoint.js

import {reactive,onMounted,onUnmounted} from 'vue'

export let getPoint=()=>{

// 数据

let point=reactive({

x:0,

y:0,

})

// 方法

function getPoint(event){

console.log(event)

point.x=event.clientX;

point.y=event.clientY;

}

// 生命周期钩子函数

onMounted(()=>{

window.addEventListener('click',getPoint);

})

onUnmounted(()=>{

window.removeEventListener('click',getPoint);

})

return point

}

需要引入hook的.vue文件

    import {reactive,toRefs,onMounted,onUnmounted} from 'vue'

import {getPoint} from "./hook/usePoint";

export default {

name: "hook_component01",

setup(){

let point=getPoint();

return{

...toRefs(point)

}

}

}

这个就是最简单hook的用法,如果有知道export 导出函数和hook区别的大佬,可以在下方评论区留言,感激不敬!!!

其它Composition API

toRef与toRefs

toRef

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
  • 语法:const name=toRef(obj,'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时
  • 扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,toRefs(obj)

示例一

<template>

<h2>toRef与toRefs</h2>

<h2>姓名:{{person.name}}</h2>

<h2>年龄:{{person.age}}</h2>

<h2>薪水:{{person.job.salary}}k</h2>

<button @click="person.name+='!'">修改姓名</button>

<button @click="person.age++">修改年龄</button>

<button @click="person.job.salary++">涨点薪资</button>

</template>

<script>

import {reactive} from 'vue'

export default {

name: "toRef_component",

setup(){

let person=reactive({

name:'二郎神杨杨戬',

age:18,

job:{

salary:20

}

})

return{

person,

}

}

}

</script>

<style scoped>

</style>

示例一里面直接返回person对象,导致每次取值的时候都需要person.xxx,这样既不美观也不优雅,修改一下代码。

示例二

<template>

<h2>toRef与toRefs</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪水:{{salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="salary++">涨点薪资</button>

</template>

<script>

import {reactive,toRef} from 'vue'

export default {

name: "toRef_component",

setup(){

let person=reactive({

name:'二郎神杨杨戬',

age:18,

job:{

salary:20

}

})

return{

name:toRef(person,'name'),

age:toRef(person,'age'),

salary:toRef(person.job,'salary')

}

}

}

</script>

<style scoped>

</style>

错误用法示例一

<template>

<h2>toRef与toRefs</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪水:{{salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="salary++">涨点薪资</button>

</template>

<script>

import {reactive,toRef,toRefs} from 'vue'

export default {

name: "toRef_component",

setup(){

let person=reactive({

name:'二郎神杨杨戬',

age:18,

job:{

salary:20

}

})

return{

name:person.name,

age:person.age,

salary:person.job.salary

}

}

}

</script>

<style scoped>

</style>

错误用法示例二

<template>

<h2>toRef与toRefs</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪水:{{salary}}k</h2>

<h2>peron对象{{person}}</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="salary++">涨点薪资</button>

</template>

<script>

import {reactive,toRef,toRefs,ref} from 'vue'

export default {

name: "toRef_component",

setup(){

let person=reactive({

name:'二郎神杨杨戬',

age:18,

job:{

salary:20

}

})

return{

person,

name:ref(person.name),

age:ref(person.age),

salary:ref(person.job.salary)

}

}

}

</script>

<style scoped>

</style>

toRefs

示例一

<template>

<h2>toRef与toRefs</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪水:{{job.salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="job.salary++">涨点薪资</button>

</template>

<script>

import {reactive,toRef,toRefs} from 'vue'

export default {

name: "toRef_component",

setup(){

let person=reactive({

name:'二郎神杨杨戬',

age:18,

job:{

salary:20

}

})

return{

...toRefs(person)

}

}

}

</script>

<style scoped>

</style>

shallowRef和shallowReactive

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

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

  • 什么时候用

    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化用shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换用shallowRef

shallowRef示例

<template>

<h2>shallowRef示例</h2>

<h2>当前sum的值是:{{sum}}</h2>

<button @click="sum++">点我sum加1</button>

<h2>当前x.y的值是:{{x.y}}</h2>

<button @click="x.y++">点我x.y加1</button>

<button @click="x={y:100}">点我替换y的值</button>

</template>

<script>

import {shallowRef,ref} from 'vue'

export default {

name: "shallowRef_component",

setup(){

let sum=ref(0);

let x=shallowRef({

y:0,

})

return{

sum,

x,

}

}

}

</script>

<style scoped>

</style>

这里我们通过ref和shallowRef进行比对,点击x.y加1按钮的时候,视图不会触发更新,因为y的值对象作为深层次的,而直接点击sum加1的按钮的时候可以触发更新,sum直接是浅层次的,替换y的值的时候替换的是整个x的值(即整个对象),而不是x里面的值进行操作。

shallowReactive示例

<template>

<h2>shallowReactive示例</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪资:{{job.salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="job.salary++">涨点薪资</button>

</template>

<script>

import {shallowReactive,toRefs} from 'vue'

export default {

name: "shallowReactive01_component",

setup(){

let person=shallowReactive({

name:'张三',

age:18,

job:{

salary:20,

}

})

return{

...toRefs(person)

}

}

}

</script>

<style scoped>

</style>

点击修改姓名和修改年龄的按钮时,可以看到视图发生变化,点击涨薪的时候视图不会发生变化,但是数据发生了变化,这个大家可以使用控制台进行测试。

readonly和shallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读)
  • shallowReadonly:让一个响应式变为只读的(浅只读)

    示例一

<template>

<h2>readonly与shallowReadonly</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪资:{{job.salary}}</h2>

<h2>当前sum的值是:{{sum}}</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="job.salary++">涨薪(readonly)</button>

<button @click="job.salary++">涨薪(shallowReadonly)</button>

<button @click="sum++">点我加1</button>

</template>

<script>

import {ref,reactive,readonly,shallowReadonly,toRefs} from 'vue'

export default {

name: "shallowReadonly_component",

setup(){

let sum=ref(0);

let person=reactive({

name:'二郎神杨戬',

age:21,

job:{

salary:200

}

});

person=readonly(person);

sum=readonly(sum);

// person=shallowReadonly(person);

// sum=readonly(sum);

return{

sum,

...toRefs(person)

}

}

}

</script>

<style scoped>

</style>


使用readonly的时候,按钮点击全部失效,我们看下shallowReadonly的效果


使用shallowReadonly的时候,修改姓名,修改年龄都不会发生变化,只有涨薪发生了变化

toRaw和markRaw

  • toRaw

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

  • markRow

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

      应用场景:

    • 有些值不应该被设置为响应式的,例如复杂的第三方类库,

    • 当渲染具有不可变的数据源的大列表时,跳过响应式转换可以提高性能

      示例一

<template>

<div style="width: 800px;margin: 0 auto">

<h2>toRaw与markRow</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪资:{{job.salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="job.salary++">涨点薪资</button>

<button @click="showRawPerson">输出最原始的person对象</button>

</div>

</template>

<script>

import {ref,reactive,toRaw,markRaw,toRefs} from 'vue'

export default {

name: "toRaw01_component",

setup(){

let sum=ref(0);

let person=reactive({

name:'二郎神杨戬',

age:18,

job:{

salary:20

}

})

function showRawPerson() {

let p=toRaw(person)

console.log(p);

let sum=toRaw(sum);

console.log(sum); // 对ref定义的响应式数据无效

}

return{

sum,

...toRefs(person),

showRawPerson

}

}

}

</script>

<style scoped>

</style>

调用showRawPerson方法的时候,控制台可以看到输出最原始的person,sum的话,输出的undefined,toRaw对ref定义的响应式数据无效,接下来看下markRow的效果

示例二

<template>

<div style="width: 800px;margin: 0 auto">

<h2>toRaw与markRow</h2>

<h2>姓名:{{name}}</h2>

<h2>年龄:{{age}}</h2>

<h2>薪资:{{job.salary}}k</h2>

<button @click="name+='!'">修改姓名</button>

<button @click="age++">修改年龄</button>

<button @click="job.salary++">涨点薪资</button>

<button @click="showRawPerson">输出最原始的person对象</button>

<h2>车的信息是:{{person.car}}</h2>

<button @click="addCar">给人添加一辆车</button>

<template v-if="person.car">

<button @click="person.car.name+='!'">修改车名</button>

<button @click="person.car.price++">修改车的价格</button>

<button @click="changeCarPrice">修改车的价格</button>

</template>

</div>

</template>

<script>

import {ref,reactive,toRaw,markRaw,toRefs} from 'vue'

export default {

name: "toRaw01_component",

setup(){

let sum=ref(0);

let person=reactive({

name:'二郎神杨戬',

age:18,

job:{

salary:20

}

})

function showRawPerson() {

let p=toRaw(person)

console.log(p);

let sum=toRaw(sum);

console.log(sum); // 对ref定义的响应式数据无效

}

function addCar() {

let car={name:'宝马',price:40}

person.car=markRaw(car);

}

function changeCarPrice() {

person.car.price++;

console.log(person.car.price)

}

return{

sum,

person,

...toRefs(person),

showRawPerson,

addCar,

changeCarPrice

}

}

}

</script>

<style scoped>

</style>

这里新增了一个车信息的方法和相关属性到person对象里面,正常情况下,直接在reactive里面的追加的数据会实现响应式的,但是这里使用了markRaw方法,所以点击修改车的名字和价格时数据发生了变化,但是视图不会更新。

customRef

  • 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制,它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。

    实现防抖效果:

  • 1、先实现自定义双向绑定

  • 2、实现了双向绑定之后,实现防抖

    自定义双向绑定

<template>

<h2>customRef示例</h2>

<input type="text" v-model="msg">

<h2>{{msg}}</h2>

</template>

<script>

import {customRef,} from 'vue'

export default {

name: "customRef01_component",

setup(){

function myRef(msg){ // 自定义ref函数

return customRef((track,trigger)=>{

return{

get(){

console.log('读取了值')

track();

return msg;

},

set(newValue){

console.log(`修改了值,修改后的值是:${newValue}`);

msg=newValue;

trigger();

}

}

})

}

let msg=myRef('你好');

return{

msg

}

}

}

</script>

<style scoped>

</style>


在这里我们实现了数据的双向绑定,接下来是实现防抖

<template>

<div style="width: 800px;margin: 0 auto">

<h2>customRef示例</h2>

<input type="text" v-model="msg">

<h2>{{msg}}</h2>

</div>

</template>

<script>

import {customRef,} from 'vue'

export default {

name: "customRef01_component",

setup(){

function myRef(msg,delay){ // 自定义ref函数

let timer;

return customRef((track,trigger)=>{

return{

get(){

console.log('读取了值')

track();

return msg;

},

set(newValue){

timer=setTimeout(()=>{

console.log(`修改了值,修改后的值是:${newValue}`);

msg=newValue;

trigger();

},delay)

}

}

})

}

let msg=myRef('你好',500);

return{

msg

}

}

}

</script>

<style scoped>

</style>

响应式数据的判断

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

provide和inject

  • 作用:实现祖与后代组件间通信
  • 套路:父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据

祖组件

<template>

<h2>我是祖组件</h2>

<h3>汽车信息</h3>

<p>名称:{{name}}</p>

<p>价格:{{price}}</p>

<inject_component></inject_component>

</template>

<script>

import {reactive,toRefs,provide} from 'vue'

export default {

name: "provide_component",

setup(){

let car=reactive({

name:'宝马',

price:'40w'

});

provide('car',car); // 提供provide

return{

...toRefs(car)

}

}

}

</script>

<style scoped>

</style>

后代组件

<template>

<h2>我是孙组件</h2>

<h3>汽车信息</h3>

<p>名称:{{name}}</p>

<p>价格:{{price}}</p>

</template>

<script>

import {inject,toRefs,ref} from 'vue'

export default {

name: "inject_component",

setup(){

let car=inject("car"); //使用inject接收

return{

...toRefs(car)

}

}

}

</script>

<style scoped>

</style>

Fragment

  • 在vue2中:组件必需有一个根标签
  • 在vue3中:组件可以没有根标签,内部会将多个根标签包含在一个Fragment虚拟元素中

好处:减少标签层级,减少内存占用

Teleport

Teleport是一种能够将我们的组件html结构移动到指定位置的技术

<teleport to='移动位置'>

<div v-if='isShow' class='mark'>

<div class="dialog">

<h3>我是一个弹窗</h3>

<button @click='isShow=true'>关闭弹窗</button>

</div>

</div>

</teleport>

实现一个弹窗居中显示,有四个组件,分别为:teleport_parent,teleport_child,teleport_son,teleport_dialog,然后需要实现的效果是在teleport_son组件引入teleport_dialog组件,teleport_dialog显示的时候在屏幕正中央

teleport_parent.vue

<template>

<div class="parent">

<h2>我是parent组件</h2>

<teleport_child/>

</div>

</template>

<script>

import Teleport_child from "./teleport_child";

export default {

name: "teleport_parent",

components: {Teleport_child}

}

</script>

<style scoped>

.parent{

background-color: red;

padding: 10px;

}

</style>

teleport_child.vue

<template>

<div class="child">

<h2>我是child组件</h2>

<teleport_son/>

</div>

</template>

<script>

import Teleport_son from "./teleport_son";

export default {

name: "teleport_child",

components: {Teleport_son}

}

</script>

<style scoped>

.child{

background-color: orange;

padding: 10px;

}

</style>

teleport_son.vue

<template>

<div class="son">

<h2>我是son组件</h2>

<teleport_dialog/>

</div>

</template>

<script>

import Teleport_dialog from "./teleport_dialog";

export default {

name: "teleport_son",

components: {Teleport_dialog}

}

</script>

<style scoped>

.son{

background-color: yellow;

padding: 10px;

}

</style>

teleport_dialog.vue

<template>

<div>

<button @click="isShow=true">点我弹窗</button>

<div class="dialog_container" v-if="isShow">

<h2>我是弹窗组件</h2>

<div class="dialog_body">

<h2>我是内容</h2>

<h2>我是内容</h2>

<h2>我是内容</h2>

<h2>我是内容</h2>

</div>

<button @click="isShow=false">关闭按钮</button>

</div>

</div>

</template>

<script>

import {ref} from 'vue'

export default {

name: "teleport_dialog",

setup(){

let isShow=ref(false);

return{

isShow

}

}

}

</script>

<style scoped>

.dialog_container{

width: 500px;

height: 300px;

background: red;

}

</style>

实现的效果如下


当我们点击按钮的时候,效果是这样的


点击按钮的时候,弹窗显示,但是在son组件里面会改变son的高度,这样子不太美观,如果弹窗使用定位的话可以使其脱离文档流,而不会撑开son里面的高度,但是postion:absolute是根据最近的有定位元素的父元素进行定位的,所以此方法不可靠,我们需要实现的效果是根据body来定位

修改dialog的样式

<template>

<div>

<button @click="isShow=true">点我弹窗</button>

<teleport to="body">

<div class="mask" v-if="isShow">

<div class="dialog_container">

<h2>我是弹窗组件</h2>

<div class="dialog_body">

<h2>我是内容</h2>

<h2>我是内容</h2>

<h2>我是内容</h2>

<h2>我是内容</h2>

</div>

<button @click="isShow=false">关闭按钮</button>

</div>

</div>

</teleport>

</div>

</template>

<script>

import {ref} from 'vue'

export default {

name: "teleport_dialog",

setup(){

let isShow=ref(false);

return{

isShow

}

}

}

</script>

<style scoped>

.mask{

position: absolute;

top: 0px;

left: 0px;

right: 0px;

bottom: 0px;

background: rgba(0,0,0,.5);

}

.dialog_container{

width: 500px;

height: 300px;

background: red;

position: absolute;

left: 50%;

top: 50%;

margin-left: -250px;

margin-top: -150px

}

</style>

Suspense

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

使用步骤:

  • 异步引入组件

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>

示例

suspense.vue

<template>

<div class="suspense">

<h2>我是suspense组件</h2>

<child/>

</div>

</template>

<script>

import Child from "./child";

export default {

name: "suspense01_component",

components: {Child}

}

</script>

<style scoped>

.suspense{

background-color: red;

padding: 10px;

}

</style>

child.vue

<template>

<div class="child">

<h2>我是child组件</h2>

</div>

</template>

<script>

export default {

name: "child"

}

</script>

<style scoped>

.child{

background-color: orange;

padding: 10px;

}

</style>

如果使用以上代码引入的话,那么suspense和child组件将会同时加载,如果child里面还有其它子组件的话,子组件里面还有子组件,在网络慢的情况下,用户可能就看不到child组件以及child里面其它的组件。这会影响用户体验,修改对应的代码

suspense.vue

<template>

<div class="suspense">

<h2>我是suspense组件</h2>

<suspense>

<template v-slot:default>

<child/>

</template>

<template v-slot:fallback>

<h3>请稍等,加载中。。。</h3>

</template>

</suspense>

</div>

</template>

<script>

// import Child from "./child"; // 静态引入

import {defineAsyncComponent} from 'vue'

const Child=defineAsyncComponent(()=>import('./child')) // 异步引入

export default {

name: "suspense01_component",

components: {Child}

}

</script>

<style scoped>

.suspense{

background-color: red;

padding: 10px;

}

</style>

child.vue

<template>

<div class="child">

<h2>我是child组件</h2>

<h2>当前sum的值是:{{sum}}</h2>

</div>

</template>

<script>

import {ref} from 'vue'

export default {

name: "child",

setup(){

let sum=ref(0);

return new Promise((resole,reject)=>{

setTimeout(()=>{

resole({

sum

});

},3000)

})

}

}

</script>

<style scoped>

.child{

background-color: orange;

padding: 10px;

}

</style>

为了看到效果,延迟了3秒之后显示组件

Vue2x和Vue3x的其它变化

1.全局API的转移

vue2.x有许多全局API和配置,例如:全局注册组件、注册全局指令等

  • 注册全局组件

Vue.component('MyButton',{

   data:()=>{

       count:0,

  },

       template:'<button @click="count++">clicked {{count}} times</button>'

  });

  • 注册全局指令

Vue.directive('focus',{

   inserted:el=>el.foucus

})

  • Vue3.0中对这些API做出了调整

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

2.x 全局API(Vue)3.x 实例API(app)
Vue.config.xxxapp.config.xxx
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-to,

.v-enter-to{

   opacity:1

}

  • 移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes
  • 移除v-on.navtive修饰符

父组件绑定事件

<my-component>

  v-on:close="handleComponentEvent"

v-on:click="handleNativeClickEvent"

</my-component>

子组件中声明自定义组件

export default{

   emits:['close']

}

  • 移除过滤器(filter)

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

Vue3x相关资料

相关库名称在线地址
Vue 3.0 官方文档(英文)在线地址
Vue 3.0 中文文档在线地址
Composition-API手册在线地址
Vue 3.0 源码学习在线地址
Vue-Router 官方文档在线地址
Vuex 4.0Github
vue-devtoolsGithub
Vite 源码学习线上地址
Vite 2.0 中文文档线上地址
Vue3 新动态线上地址

Vue3xUI组件库

Element-plus

  • 仓库地址:https://github.com/element-plus/element-plus

  • 文档地址:https://element-plus.gitee.io/zh-CN/

  • 开源项目

    • Vue 3.0 + Vite 2.0 + Vue-Router 4.0 + Element-Plus + Echarts 5.0 + Axios 开发的后台管理系统:https://github.com/newbee-ltd/vue3-admin

    • Vue3.0+TypeScript+NodeJS+MySql编写的一套后台管理系统:https://github.com/xiaoxian521/vue-pure-admin

Ant Design of Vue

  • 仓库地址:https://github.com/vueComponent/ant-design-vue/

  • 文档地址:https://antdv.com/docs/vue/introduce-cn/

  • 开源项目

    • AntdV后台管理系统:https://github.com/iczer/vue-antd-admin

    • vue3.x + ant-design-vue(beta 版本,免费商用,支持 PC、平板、手机):https://github.com/chuzhixin/vue-admin-better

    • 基于 Vue3.0 + Vite + Ant Design Vue:https://github.com/lirongtong/miitvip-vue-admin-manager

Vant

  • 仓库地址:https://github.com/youzan/vant

  • 文档地址:https://vant-contrib.gitee.io/vant/#/zh-CN/

  • 开源项目

    • newbee-mall Vue3 版本:https://github.com/newbee-ltd/newbee-mall-vue3-app

    • 高仿微信笔记本:https://github.com/Nick930826/daily-cost

    • 仿京东淘宝电商:https://github.com/geekskai/vue3-jd-h5

NutUI 3

  • 仓库地址:https://github.com/jdf2e/nutui

  • 文档地址:https://nutui.jd.com/#/index

Vue3X相关视频

相关库名称在线地址
Vue 3.0 实战星座物语 H5 项目在线地址
Vue 3.0 UI 组件库开发在线地址
Vue 3.0 + Vite 手册阅读在线地址
Vue 3.0 入门之项目搭建(杨村长)在线地址
Vue 3.0 入门(技术胖)【不太建议推荐】在线地址
Vite 2.0 插件开发指南在线地址
Vue 3.0 + Vite 2.0 快速搭建 Electron 应用在线地址
Vue3.0视频(强烈推荐)在线地址
Vue3.0驾照题库在线地址

参考资料

掘金地址:https://juejin.cn/post/6955129410705948702

Vue.js官网:https://v3.cn.vuejs.org/

bilibili地址:https://www.bilibili.com/video/BV1Zy4y1K7SH

MDN地址:https://developer.mozilla.org/zh-CN/

ES6地址:https://es6.ruanyifeng.com/

总结

总体来说,学完这篇博客基本涵盖了Vue3中的绝大部分内容,写的也特别详细,笔记,配套资料和实战视频都有,主要看你愿不愿意抽出时间去学习,我自己认为学习是一辈子的事情,当然学习也是一件特别孤独的事情。如果本篇文章对您有所帮助的话,记得点赞、收藏和关注,原创不易,三连支持,感谢大家!

以上是 从0开始手把手带你入门Vue3-全网最全(1.1w字) 的全部内容, 来源链接: utcz.com/z/376728.html

回到顶部