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

Posted 一条有梦想的咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0开始手把手带你入门Vue3-全网最全(1.1w字)相关的知识,希望对你有一定的参考价值。

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

前言

本文并非标题党,而是实实在在的硬核文章,如果有想要学习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.salaryK</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

示例

    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生命周期对比
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
activated onActivated
deactivated onDeactivated

因为 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.salaryk</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>薪水:salaryk</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>薪水:salaryk</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>薪水:salaryk</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.salaryk</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.salaryk</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.salaryk</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.salaryk</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选项来开始使用这些数据

快速上手docker-java,示例全网最全,带你踩坑,带你飞

肝魂一晚上总结:全网最全最细手把手教你PyQt5安装与使用☀️《❤️记得收藏❤️》

Vue3.0全家桶最全入门指南 - vue3.0新特性 (2/4)

手把手教你VirtualBox安装Centos,全网最全教程

Python3 视频教程,全网最全的视频教程,爬虫,从入门到实战

2021全网最全Activiti7教程02(Activiti7入门使用-欢迎收藏)