关于前端:从0开始手把手带你入门Vue3全网最全11w字

4次阅读

共计 35032 个字符,预计需要花费 88 分钟才能阅读完成。

天命有余畏,祖宗有余法。——王安石

前言

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

我的项目搭建

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

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

官网文档:https://cli.vuejs.org/zh/guid…

//    查看 @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?…

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, 间接:<div>{{xxx}}</div>

    备注:

    • 接管的数据能够是:根本类型,也能够是对象类型
    • 根本类型的数据:响应式仍然是靠 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…

示例

    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.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.xxx app.config.xxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.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.0 Github
vue-devtools Github
Vite 源码学习 线上地址
Vite 2.0 中文文档 线上地址
Vue3 新动静 线上地址

Vue3xUI 组件库

Element-plus

  • 仓库地址:https://github.com/element-pl…
  • 文档地址:https://element-plus.gitee.io…
  • 开源我的项目

    • Vue 3.0 + Vite 2.0 + Vue-Router 4.0 + Element-Plus + Echarts 5.0 + Axios 开发的后盾管理系统:https://github.com/newbee-ltd…
    • Vue3.0+TypeScript+NodeJS+MySql 编写的一套后盾管理系统:https://github.com/xiaoxian52…

Ant Design of Vue

  • 仓库地址:https://github.com/vueCompone…
  • 文档地址:https://antdv.com/docs/vue/in…
  • 开源我的项目

    • AntdV 后盾管理系统:https://github.com/iczer/vue-…
    • vue3.x + ant-design-vue(beta 版本,收费商用,反对 PC、平板、手机):https://github.com/chuzhixin/…
    • 基于 Vue3.0 + Vite + Ant Design Vue:https://github.com/lirongtong…

Vant

  • 仓库地址:https://github.com/youzan/vant
  • 文档地址:https://vant-contrib.gitee.io…
  • 开源我的项目

    • newbee-mall Vue3 版本:https://github.com/newbee-ltd…
    • 高仿微信笔记本:https://github.com/Nick930826…
    • 仿京东淘宝电商:https://github.com/geekskai/v…

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/695512…

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

bilibili 地址:https://www.bilibili.com/vide…

MDN 地址:https://developer.mozilla.org…

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

总结

总体来说,学完这篇博客根本涵盖了 Vue3 中的绝大部分内容,写的也特地具体,笔记,配套材料和实战视频都有,次要看你愿不愿意抽出工夫去学习,我本人认为学习是一辈子的事件,当然学习也是一件特地孤单的事件。如果本篇文章对您有所帮忙的话,记得点赞、珍藏和关注,原创不易,三连反对,感激大家!

正文完
 0