Vue3新特性全面剖析

一款框架诞生需要的阶段

查看版本: npm view vue versions

  • 开发
  • alpha版:内部测试版 α
  • beta版:公开测试版 β
  • rc版:Release Candidate(候选版本)
  • stable版:稳定版

npm i vue@lasted:下载的是vue2

  • vue2
  • vuex3
  • vue-router3
  • element-ui 2

npm i vue@next:下载的是vue3

  • vue3
  • vuex4
  • vue-router4
  • element-plus 1
  • npm i vuex@next vue-router@next element-plus

vue3的安装使用

  • 一、安装vue3:npm i vue@next
  • 二、安装vue3版本的项目
  • 三、安装vue-router、vuex
  • 四、安装less@3、less-loader@7
  • 五、配置vue.config.js,与vue2相同

vue3与vue2的十大区别

  • 版本号不同
  • 引用的vuex/vue-router版本不同
  • vue3的APP.vue的《template》 中支持多个根节点

区别一:==vue3中推崇使用函数式编程==:各种创建实例,都调用函数来创建,我们从vue中解构出各种各样的函数来使用 

区别二.Performance [pəˈfɔːməns]

  • 重写了虚拟DOM的实现(跳过静态节点,只处理动态节点)
  • update性能提高1.3~2倍
  • SSR速度提高了2~3倍

区别三:Tree shaking [ˈʃeɪkɪŋ] 可以将无用模块“剪辑”,仅打包需要的)【webpack新版本的】

区别四 Fragment ['frægmənt] 不再限于模板中的单个根节点[文档碎片]

区别五 <Teleport> [ˈtelɪpɔːt] 以前称为<Portal> [ˈpɔːtl],译作传送门,可以把组件内部的部分内容挂载到除#app的容器中(<Teleport to="#fotter">),

  <div id="app"></div>
  <div id="footer"></div>
 <Teleport to="#footer"> 你好,世界</Teleport>

区别六<Suspense> [səˈspens]可在嵌套层级中等待嵌套的异步依赖项,嵌套一个异步组件,可以在获取到数据之前加载loading效果

父组件------------------
<template>
  <Suspense>
    <template #default><AsyncComponent></AsyncComponent></template>
    <template #fallback>loading.....</template>
  </Suspense>
</template>

<script>
import AsyncComponent from "./AsyncComponent.vue";
export default {
  name: "App",
  components: {
    AsyncComponent,
  },
};
</script>
子组件--------------------------
<template>
  <div>{{ msg }}</div>
</template>

<script>
import { defineComponent } from "vue";
const query = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("我是异步组件");
    }, 2000);
  });
};
export default defineComponent({
  async setup() {
    let msg = await query();
    return {
      msg,
    };
  },
});
</script>

区别七:TypeScript更好的TypeScript支持

区别八:vue3支持把组件中的某些内容以图形的方式绘制到canvas画布上(Custom Renderer API自定义渲染器API)Apache ECharts 

  • 自定义渲染器API
  • 用户可以尝试WebGL自定义渲染器

区别九:vue3使用的Composition API:(Composition API聚合API):vue2遵循options API,vue3遵循Composition API

options API:数据根据功能划分到data、methods、computed、watch区域(分散式API)

Composition API:vue3中所有的数据方法都放到了setup()方法中,全部组合到了一起,没有细分(聚合式API)

  • 组合式API,替换原有的 Options [ˈɒpʃnz] API
  • 根据逻辑相关性组织代码,提高可读性和可维护性
  • 更好的重用逻辑代码(避免mixins混入时命名冲突的问题)
  • 但是依然可以延用 Options [ˈɒpʃnz] API

区别十:vue3中的响应式数据

劫持,基于Proxy实现的==:vue3中的响应式数据劫持,不再基于vue2的Object.defineProperty(),而是基于ES6内置的Proxy:但是不兼容IE

defineProperty&Proxy

扫盲

深克隆&&浅克隆

空间地址赋值,是空间地址的引用,不是克隆

        let arr = [10, 20]
        let cloneArr = arr
        console.log(arr === cloneArr);//true

浅克隆就是将栈内存中的复制一份,赋给一个新的变量,内容也相同,(只对最对象最外层克隆, 第二层不克隆)

        // 数组浅克隆
        let arr = [10, 20, [1, 2, 3]]
        // let cloneArr = arr.slice();//截取整个数组实现浅克隆
        // let cloneArr = arr.concat([]); //拼接一个空数组
        // let cloneArr = [...arr]; //es6剩余运算符
        let cloneArr = [];
        arr.forEach((item, index) => {
            cloneArr[index] = item;
        });
        console.log(arr === cloneArr); //false
        console.log(arr[3] === cloneArr[3]);//true,浅克隆,只克隆最外层
         // 对象浅克隆
        let obj = {
            name: 'lisa',
            age: 18,
            obj2: {
                job: 'teacher'
            }
        }
        // let cloneObj = { ...obj };
        // let cloneObj = Object.assign({}, obj);
        let cloneObj = {}
        for (let a in obj) {
            cloneObj[a] = obj[a]
        }
        console.log(obj === cloneObj);//false

深克隆就是创建一个新的空对象,开辟一块内存,然后将原对象中的数据全部复制过去,完全切断两个对象间的联系。

1.  Lodash 或者 underscore 这些类库中都提供了深浅拷贝的办法

2. //先把原始对象变为JSON格式字符串,再把字符串变为对象「所涉及的内存,浏览器都会重新开辟一份全新的出来」

  • 如果值是bigint则会报错 TypeError: Do not know how to serialize a BigInt
  • + 如果值是 undefined/symbol/function 转换为字符串就没有了
  •  + 如果值是 正则/错误对象实例 直接变为“{}”
  •   + 如果值是 日期对象 变为字符串就回不来的
        let arr = [10, 20, 'lisa', { name: 'lisa' }];
        let cloneArr = JSON.parse(JSON.stringify(arr));
        console.log(cloneArr === arr); //false
        console.log(cloneArr[3] === arr[3]); //false

纯粹对象:通过 "{}" 或者 "new Object" 创建的对象,原型链直接指向Object

vue2中的响应式原理

基于Object.defineProperty()方法做get和set劫持的

  • 依次迭代对象中的每一项,给每一项分别做数据劫持
  • 对深层级别的对象,我们需要基于递归的方式,再次进行劫持
  • 对于数组的每一个索引项不做劫持,但是修改其原型指向,指向自己重构的原型对象【包含7个方法,执行这7个方法除了修改数据之外,还会通知视图重新渲染arr->自己重构的原型对象->Array.prototype->Object.prototype
 //检测是否为纯粹对象
const isPlainObject = function isPlainObject(obj) {
            let proto, Ctor;
            if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
            proto = Object.getPrototypeOf(obj);
            if (!proto) return true;
            Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
            return typeof Ctor === "function" && Ctor === Object;
        };

        let obj = {
            x: 1,
            y: {
                n: 2,
                m: 3
            },
            z: [10, 20, 30]
        };
        //vue2中的响应式原理
        //重新数组七大方法
        const proto = {
            push(...params) {
                //this:数组
                Array.prototype.push.call(this, ...params)
                //通知视图渲染
            },
            pop() { },
            shift() { },
            unshift() { },
            splice() { },
            sort() { },
            reverse() { }
        };
        //保证重新的方法先找到Array原型
        Object.setPrototypeOf(proto, Array.prototype);
        const observe = function observe(obj) {
            if (Array.isArray(obj)) {
                //如果是一个数组,每一项无需做劫持,需要改变原型指向
                Object.setPrototypeOf(obj, proto);
                return
            }
            if (!isPlainObject(obj)) return;//不是对象就中断

            //把obj深拷贝JSON.stringify转换为字符串,JSON.parse在转为对象
            let proxyObj = JSON.parse(JSON.stringify(obj))
            //Reflect 是一个内置的对象,ownKeys返回一个包含所有自身属性
            let keys = Reflect.ownKeys(obj)
            keys.forEach(key => {//迭代对象中的每一项,给每一项做劫持
                //方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
                Object.defineProperty(obj, key, {
                    get() {
                        //同于proxyObj[ky]
                        return Reflect.get(proxyObj, key)
                    },
                    set(value) {
                        if (value === Reflect.get(proxyObj, key)) return
                        Reflect.set(proxyObj, key, value)
                        //通知视图重新渲染
                    }
                });
                //通过递归做深劫持
                if (isPlainObject(obj[key]) || Array.isArray(obj[key])) {
                    observe(obj[key])
                }
            });

        }
        observe(obj)//执行方法把要做劫持的对象传进去

vue3中的响应式原理==:基于ES6的Proxy类来实现,为啥这么做

网址
Proxy - JavaScript | MDN

  • 因为Proxy各方面表现都比Object.defineProperty好一些,【除了不兼容IE浏览器
  • Proxy可以直接代理整个对象,无需依次迭代对象每一项做get/set劫持【性能好
  • 对于数组来讲,直接基于Proxy代理即可,无需像vue2中一样,再自己重写7个方法【对数组友好
  • 基于defineProperty只能做get/set劫持,但是基于基于Proxy可以做的劫持方式很多:【功能强大
  • get/set劫持
  • getPrototypeOf()劫持 获取要返回其原型的对象
  • setProot ypeOf()劫持  设置要返回其原型的对象
  • defineProperty()劫持   方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • deleteProperty()劫持 删除对象中的某个属性
  • has劫持 指示对象自身属性中是否具有指定的属性
  • ownKeys()劫持  返回一个由目标对象自身的属性键组成的数组。

==相同点==:都是需要基于递归的方式,对“对象”深层次进行劫持代理

  let obj = {
            x: 1,
            y: {
                n: 2,
                m: 3
            },
            z: [10, 20, 30]
        };
        obj = [10, 20, 30]//对数组也做了劫持

        //vue3中的响应式
        //基于ES6中的Proxy来实现的,new Proxy返回obj的代理对象
        let proxObj = new Proxy(obj, {//对obj的第一层做劫持
            //target代理的对象,key值
            get(target, key) {
                return target[key]
            },
            set(target, key, value) {
                if (target[key === value]) return;
                target[key] = value;
                //控制视图重新渲染,操作代理对象时触发渲染
                console.log('rendering');
            }
        })

vue2生命周期顺序

  • 初始化props
  • beforeCreate[实例还没创建好,也就是不能使用this  挂载数据/方法
  • created[可以用this了]
  • beforeMount   渲染
  • mounted[可以获取DOM了]

setup()钩子函数

  • setup 函数是一个新的组件选项,作为在组件内使用 Composition API(聚合式) 的入口点,组件内的大部分内容都要在这处理
  • 发生在初始化props之后,和beforeCreate之前,函数中没有this
  •  this值是undefined,可以基于props接收到初始化的属性值
  •  +属性值是一个对象,而且基于Proxy代理后的响应式对象
  • +并且属性是只读[readonly]的,当我们修改这个属性值的时候控制台会提示警告
  • 可以使用watch监听属性的改变,父组件重新调用子组件,传递不同的属性值.重新渲染
  • 函数执行会返回一个对象,对象中包含啥,那么这些东西可以直接在视图中渲染和使用

  setup(props) {
    console.log(this); // undefined
    console.log(props); //可以拿到传递过来的数据,Proxy做了代理
    props.title = "哈哈"; //只读,只能调用组件的时候传递不同的值
    return {
      // 函数执行会返回一个对象,对象中包含啥,那么这些东西可以直接在视图中渲染和使用
      text: "哈哈",
    };
  },
};

vue3中的10+ 响应式API

ref:响应式数据

  • 响应式系统API之一,ref let xxx =ref(初始值)创建一个响应式数据
  • 它是RefImpl类的一个实例,
  • 他是基于Object.defineProetry实现数据劫持的(不基于Proxy),并且是给RefImpl实例对象中的value属性处理的数据劫持,所以后期我们操作的是xxx的value属性
  • 视图中渲染的时候 我们{{xxx}},无需自己写value,模板编译的时候会自动取RefImpl对象的value值渲染
import { ref } from "vue"; //导入ref方法
setup() {
    let supNum = ref(0), //创建响应式数据,RefImpl类的一个实例
      oppNum = ref(50);
    console.log(supNum.value); //基于Object.defineProetry实现数据劫持的
    supNum.value++; //只对value属性做了劫持,操作value属性
    return { supNum, oppNum }; 返回出去
  },
-------视图中不需要.value----------
<p>支持人数{{ supNum }}人</p> 

reactive:响应式数据

  •  响应式系统API之reactive, 基于ES6中的Proxy实现响应式代理,而且也进行了深层次的劫持和代理
  • 获取 console.log(state.xxx),设置 (state.xxx=100)
  • 处理了proxy的这些劫持函数: get/set/deletePropetry/has/ownKeys
  • 如果我们在JS中把state的状态进行了解构处理,一定要了解下面这几点
  • 如果只是获取使用,则没有任何问题,但是如果用解构后变量去进行修改,不会触发代理对象的set函数,实现不了通知视图的重新渲染
<script>
import { reactive, computed} from "vue";
export default {
  name: "Vote",
  props: ["title"],

  setup() {
    let state = reactive({
      supNum: 100,
      oppNum: 50,
    });
    const change = (type) => {
      //   let { supNum, oppNum } = state;
      //   等同于 let n=supNum,修改的是n的值,但是只对state的属性做了劫持,不能解构修改
      type === "sup" ? state.supNum++ : state.oppNum++;

      //   state.supNum = 200; //设置属性
      //   console.log(state.supNum); //获取属性
    };
    const ratio = computed(() => {
      let { supNum, oppNum } = state; //获取使用解构没有问题
      let total = supNum + oppNum;
      return total === 0 ? "----" : ((supNum / total) * 100).toFixed(2) + "%";
    });

    console.log(state); //基于ES6中的Proxy实现响应式代理,而且也进行了深层次的劫持和代理
    return { change, ratio, state };
  },
};
</script>

toRefs && toRef:转换响应式类型

  •    toRefs(state),把基于 reactive创建的响应式状态,改为基于ref管理的状态,目的是视图中渲染RefImpl对象比较简单(返回一个对象,把state中每一项修变为ObjectRefImpl的实例对象)
  • 项目中:一般在JS中用reactive创建状态,导出toRefs变成ref的响应式状态
  • toRef把数据中的某一项变成变成ref对象, refs把所有项变成ref对象
    return { ...toRefs(state) };

    let state=reactive({x:100,y:200})
    let refs=toRefs(state);//将state中的x和y转为refImpl对象

    let state=reactive({x:100,y:200})
    let ref=toRef(state.x);//将state中的x和y转为refImpl对象

computed:计算属性

  • vue3推崇函数式编程,我们想做计算属性,需要在vue中解构出computed函数
  •   let xxx=computed([getter函数])
  • 它是 ComputedRefImpl类的一个实例对象,也是对其value属性的数据劫持,我们操作的也应该是xxx.value这个属性
  • 默认情况下,创造的计算属性是只读的,所以xxx.value=100会抛出警告
  •  computed({get(){},set(){}}) 基于这种方式设置computed可写性
import {  computed } from "vue";
export default {
  name: "Vote",
  props: ["title"],
  setup() {
    let supNum = ref(100),
      oppNum = ref(50);
    //相当于methods中的普通方法
    const change = (type) => (type === "sup" ? supNum.value++ : oppNum.value++);
    // 计算属性
    const ratio = computed(() => {
      //ComputedRefImpl的一个实例,
      let total = supNum.value + oppNum.value;
      return total === 0
        ? "----"
        : ((supNum.value / total) * 100).toFixed(2) + "%";
    });
    console.log(ratio.value); //也是对其value属性的数据劫持
    ratio.value = "100%"; //创造的计算属性是只读的,设置会抛出警告
    return { supNum, oppNum, change, ratio };
  },
};
--------------------------------------
  const ratio = computed({
      get() {
        let total = supNum.value + oppNum.value;
        return total === 0
          ? "----"
          : ((supNum.value / total) * 100).toFixed(2) + "%";
      },
      set(value) {
        console.log(value);
      },
    });
    console.log(ratio.value);
    ratio.value = "100%";

监听器 :watch && watchEffect

 watchEffect([callback])

  •  第一次渲染组件[callback]回调函数触发执行一次
  •  自动根据函数中的代码建立相关依赖(用到啥依赖啥),当依赖发送改变,[callback]还会重新执行
  • setup函数只会在组件第一次渲染的时候执行一次,当组件更新的时候steup并不会执行,但是无论是计算属性,还是监听器,都会根据依赖的信息是否发生变化,重新执行(获取最新的值)
    watchEffect(() => {
      console.log(state.supNum);
    });

watch

watch API 完全等效于 2.x this.$watch

  •    watch可以指定监听哪一个状态/属性,在状态更改后触发指定的执行函数
  •    默认情况下第一次渲染组件并不会执行监听函数(可以配置)
  •    默认也不是进行深层次的监听(可以配置)

   watch(Reflmpl对象,函数) 监听某一个ref对象

   watch(state,函数) 监听所有基于 reactive创建的状态对象

   watch(()=>state.xxx,函数) 如果监听 state中的某一个状态,则必须写成函数的形式

  watch(监听的对象,函数,配置项immediate/deep)

   watch(
      () => state.supNum,
      (next, prev) => {
        // next之前的值,prev最新的值
        console.log(next, prev);
      }
    );
---------------------
   watch(
      state,
      (next, prev) => {
        console.log(next, prev);
      },
      {//配置项
        immediate: true, //第一次执行
        deep: true, //深层次监听劫持
      }
    );

 readonly只读

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理
一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的

const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
});
// original 上的修改会触发 copy 上的侦听
original.count++;
// 无法修改 copy 并会被警告
copy.count++; // warning!

检测的

isReadonly:判断状态值是不是Readonly类型的实例(是不是只读的)

isReactive判断状态值是不是使用reactive()创建的

isProxy判断状态值是不是Proxy类型的实例(是不是通过Proxy做代理的):reactive、computed、props生成的数据都是Proxy的实例)

isRef:是不是ref创建的响应式对象(ref创建的,computed 通过refs转换的)

unref:把响应式数据变为非响应式的

周期函数&&DOM元素操作

周期函数

可以直接导入 onXXX 一族的函数来注册生命周期钩子,这些生命周期钩子注册函数只能在 setup() 期间同步使用,在卸载组件时,生命周期钩子内部同步创建的侦听器和计算状态也将删除

  • vue2中的beforeCreate和created由==setup()==函数代替了
  • beforeMount->onbeforeMount
  • Mounted->onMounted
  • beforeUpdate->onBeforeUpdate
  • updated->onUpdated
  • beforeDestory->onBeforeUmount
  • destoryed->onUnMounted

dom元素的操作

vue3中的ref将创建状态值与绑定ref的DOM元素组合在了一起

   //创建一个ref实例对象
    let titlebox = ref(null);
    //导出状态值
    return { titlebox };
    //在dom元素上使用
      <h2 class="title" ref="titlebox">{{ title }}</h2>
    获取dom元素
    onMounted(() => {
      console.log(titlebox);
    });

掌握Vue3.0自定义指令处理技巧

export default function directive(app) {
    app.directive('xxx', {
        // 指令首次绑定到元素且在安装父组件之前...「等同于bind」
        beforeMount(el, binding, vnode, prevVnode) {
            // binding:数据对象
            //   + arg:传给指令的参数   v-xxx:n -> arg:"n"
            //   + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
            //   + value:指令绑定的值   v-xxx="1+1" -> value:2
            //   + oldValue:之前绑定的值
        },
        // 安装绑定元素的父组件时...「等同于inserted」
        mounted() {},
        // 在包含组件的VNode更新之前...
        beforeUpdate() {},
        // 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
        updated() {},
        // 在卸载绑定元素的父组件之前...
        beforeUnmount() {},
        // 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
        unmounted() {}
    });
};
// main.js
import {
    createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值