Vue3.0学习笔记

Vue3.0的六大亮点

  • Performance:性能比VUE2.x快1.2-2倍
  • Tree shaking support按需编译,体积比Vue2.x更小
  • Composition API:组合API(类似React Hooks)
  • Better TypeScript support:更好的Ts支持
  • Custom Renderer API:暴露了自定义渲染API
  • Fragment,Teleport(Protal),Suspense:更先进的组件

性能更快

1、diff方法优化,Vue3 中仅对静态标记标记对象进行比较

在创建虚拟DOM的时候会根据DOM中的内容,会不会发生变化,添加静态标记。

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

2、HoistStatic静态提升

将静态内容提取出来,变成常量再进行赋值

  • Vue2中无论元素是否参与更新,每次都会重新创建
  • Vue3中对于不参与更新的元素,只会被创建一次。以后每次进行render的时候,就不会重复创建这些静态的内容,而是直接从一开始就创建好的常量中取就行了
image

image

3、cacheHandlers事件侦听器缓存

onClick 方法被存入 cache。在使用的时候,如果能在缓存中找到这个方法,那么它将直接被使用。如果找不到,那么将这个方法注入缓存。总之,就是把方法给缓存了

image

image

4、ssr渲染


原教程教案

Alt text

静态标记附录

Alt text

组合API 官方文档链接

组合API的入口函数: setup

执行时间:先于beforeCreate
setup 中创建并 return 的所有东西,都将被得到外部的解析,无论是过去在 data 中创建的数据也好,还是在 methods 创建的方法也好,都将变成允许被响应式地使用,仿佛 Vue2 中的这些 API 都被融合在一起了一样,而实际上 Vue3 也是为了实现这个目的。
基础写法:

image

image

ref

本质:ref的本质其实还是reactive,当我们给ref函数传递一个值之后,ref函数底层会自动将ref转换成reactive。例:ref(18)->reactive({value: 18})。

什么是ref ?
  • ref和reactive一样,也是用来实现响应式数据的方法
  • 由于reactive必须传递一个对象,所以导致在企业开发中如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue3提供了ref方法,实现简单值的监听。

Ref是这样的一种数据结构:它有个key为Symbol的属性做类型标识,有个属性value用来存储数据。这个数据可以是任意的类型,唯独不能是被嵌套了Ref类型的类型。

Ref类型的数据,是一种响应式的数据。

Ref写法简单,但也有弊端,它只能监听一些如数字、字符串、布尔之类的简单数据。复杂数据需要用到 reactive 。(实际上也可以用 Ref 来封装对象,只不过在访问上多一层 value ,稍微麻烦了一些)

ref注意点:
  • 在Vue中使用ref的值不用通过value获取
  • 在JS中使用ref的值必须通过.value获取

reactive

本质:就是将传入的数据包装成一个proxy对象

什么是reactive?
reactive注意点:
  • reactive参数必须是对象(json/arr)
  • 如果给reactive传递了其他对象。默认情况下,修改对象无法实现界面的数据绑定更新。如果需要更新,需要进行重新赋值。
  • 基本类型(数字、字符串、布尔值)在reactive中无法被创建成proxy对象,也就无法实现监听。
  • reactive中使用Date参数需要特殊处理,特殊处理就是重新赋值。
ref和reactive的区别:

如果在template里使用的是ref类型的数据,那么Vue会自动添加.value
如果在template里使用的是reactive类型的数据,那么Vue不会自动添加.value(这里的.value指的是对象里面的键名)

Vue如何决定是否需要自动添加.value?

Vue在解析数据之前,会自动判断这个数据是否是ref类型的。

vue怎么区分是ref还是reactive定义的数据?

通过当前数据的__v_ref来判断的。
如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据

开发者怎么区分是ref还是reactive定义的数据?

import { isRef, isReactive } from 'vue';

let age = ref(18);
let list = reactive([1, 2, 3, 4]);
let obj = reactive({
    person: {
        name: 'lisa',
        age: '10'
    }
})
function myFn() {
    console.log(isRef(age)); // true
    console.log(isReactive(age)); // false
    console.log(isRef(list)); // false
    console.log(isReactive(list)); // true
    console.log(isRef(obj)); // false
    console.log(isReactive(obj)); // true
}

组合API refreactive简单示例代码:

<script>
import { ref, reactive } from 'vue';
export default {
    name: 'App',
    setup() {
        let count = ref(0);
        function myFn() {
            // do somthing
            // 注意点:如果是通过ref创建的数据,在template中使用,不用通过.value来获取,因为vue会自动给我们添加.value
            // 在Vue中使用ref的值不用通过.value获取
            // 在JS中使用ref的值必须通过.value获取
            count.value = 18;
        }
        //let msg = reactive({
        //    num: {
        //        inner: 0
        //    }
        //})
        let msg = reactive([1,2,3])
        let state = reactive({
            time: new Date()
        });
        function changeTime() {
            // 直接修改以前的,界面不会更新
            // state.time.setDate(state.time.getDate() + 1);
            // 特殊处理:重新赋值
            const newTime = new Date(state.time.getTime());
            newTime.setDate(state.time.getDate() + 1);
            state.time = newTime;
        }
        //在组合API中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx }暴露出去
        return { count, myFn, msg, changeTime,state };
    }
}
</script>

组合API功能代码封装, 简单代码如下:

image

image

在上面的基础上,再加一个封装模块,多个封装模块的情况下,通过传参可以使用其他封装模块的数据:

image

image

image

多个封装模块的另一种写法,将封装的代码放到另外的js文件,通过import引入该js文件,举例如下:

image

image

image

customRef

返回一个ref对象,可以显式地控制依赖追踪和触发相应

<script>
import { ref, customRef } from 'vue';
function myRef(value) {
    return customRef((track, trigger) => {
        // fetch 是es6 es7中的异步请求方法
        fetch(value).then((res) => {
            return res.json();
        })
        .then((data) => {
            console.log(data);
            value = data;
            trigger();
        })
        .catch((err) => {
            console.log(err);
        })
        return {
            get() {
                track(); // 告诉Vue这个数据是需要追踪变化的
                console.log('get', value);
                // 注意点:不能再get方法中发送网络请求
                return value;
            },
            set(newValue) {
                console.log('set', newValue);
                value = newValue;
                trigger(); // 告诉Vue触发界面更新
            }
        }
    })
}
export default {
  name: 'App',
  setup() {
    //  同步
    // let state = myRef(12);
    // function myFn() {
    //     state.value += 1;
    // }
    // 异步
    let state = myRef('路径')
    return { state, myFn };
  }
}
</script>

readonly、shallowReadonly、isReadonly

readonly:用于创建一个只读的数据,并且是递归只读
shallowReadonly:用于创建一个只读数据,但是不是递归只读
isReadonly:判断是否是readonly定义的变量

const和readonly的区别:
const:赋值保护,不能重新赋值,但是对象里面的属性值可变。例:

const obj = {name: 'ljh', attr: {age: 10, height: 1.2}};
obj = 'test'; // 报错, const定义的变量不可重新赋值
obj.name = 'test'; // 不报错,值可以变化

readonly:属性保护,不能给属性重新赋值

let state = readonly({name: 'ljh', attr: {age: 10, height: 1.2}})
state.name = 'test';
state.attr.age = 9;
state.attr.height = 1.1;
// state的全部属性都是只读,不会有变化

shallow(重要)

非递归监听:shallowRef、shallowReactive

没有shallow,对原始的值进行整个的 reactive 化,如果有shallow,那么只对最外层的数据执行监听。
通过shallowRef创建的数据,监听的是.value的变化

如何触发非递归监听属性更新界面?

shallowRef:

  1. 可以通过triggerRef触发;
  2. 通过给.value赋值触发;
    shallowReactive:
  3. 给最外层的数据赋值

简单示例代码:

<script>
import { shallowRef, triggerRef, shallowReactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let msg1 = shallowRef({
      a: {
        b: {
          c: 'c'
        }
      },
      e: {
        f: 'f'
      },
      g: 'g'
    });
    function c() {
      console.log(msg1);
      <!-- shallowRef start -->
      // 如果是通过shallowRef创建数据,那么vue监听的是.value的变化,并不是第一层的变化
      // 所以以下写法页面展示值不会更新
      // msg1.value.a.b.c = 'C';
      // msg1.value.e.f = 'F';
      // msg1.value.g = 'G';
      // 1.由于只有最外层的数据改变能被监测到,所以只有在直接改变 msg1.value 的时候才会产生监测,页面展示值会变
      msg1.value = {
        a: {
          b: {
            c: 'C'
          }
        },
        e: {
          f: 'F'
        },
        g: 'G'
      }
      <!-- shallowRef end -->
      
      <!-- triggerRef start -->
      // 2. 对于 shallow 过的 ref 对象,我们还可以手动去触发 ref 的变化监听事件来实现界面的改变,使用的api是triggerRef
      msg1.value.a.b.c = 'C';
      msg1.value.e.f = 'F';
      msg1.value.g = 'G';
      triggerRef(msg1);
      <!-- triggerRef end -->
      // 以上两种写法可以达到相同的效果
    };
    
    // shallowReactive 可以实现类似 shallowRef 的功能
    let msg2 = shallowReactive({
      a: {
        b: {
          c: 'c'
        }
      },
      e: {
        f: 'f'
      },
      g: 'g'
    });
    function d() {
      <!-- shallowReactive start -->
      msg2.a.b.c = 'C';
      msg2.e.f = 'F';
      msg2.g = 'G';
      console.log(msg2);
      <!-- shallowReactive end -->   
    }
    return { msg1, msg2, c, d };
  }
}
</script>

手写shallowRef、shallowReactive

image

toRaw

一个用来优化资源加载的方案
想修改定义的内容而不引起界面的改动,就只需要用 toRaw 取出它的 “源值” 进行修改就行了。

<template>
    <div>
        <p>{{state}}</p>
        <button @click="myFn"></button>
    </div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {name: 'ljh', age: 12};
    / ** 
    ref、reactive数据类型的特点: 
    每次修改都会被追踪,都会更新UI界面,这样非常消耗性能
    如果有一些操作不需要追踪,不需要更新UI界面,那么这个时候,就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪,也不会更新UI界面,性能得到了优化
    **/ 
    let state = reactive(obj);
    let obj2 = toRaw(state); // 拿到reactive原始数据
    // console.log(obj === obj2); // true
    // console.log(obj === state); // false
    // state和obj的关系:
    // 引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了Obj
    function myFn() {
        // 如果直接修改obj,是无法触发界面更新的
        // 修改通过toRaw拿到的原始数据,不会更新界面
        // 只有通过包装之后的对象来修改,才会触发界面的更新
        <!--obj.name = 'test'; // obj、state的值改变,但是界面不会更新-->
        console.log(obj); // {name: "test", age: 12}
        console.log(state); // {name: "test", age: 12} 
        
        <!--obj2.name = 'test'; // 界面不更新-->
        
        <!--state.name = 'test'; // 界面更新-->
    }
    
    let state3 = ref(obj);
    let obj3 = toRaw(state3.value); // 拿到ref原始数据
    return { state, state3, myFn };
  }
}
</script>

markRaw

永远无法被追踪,无法被改变

<script>
import { reactive, markRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {name: 'ljh', age: 12};
    obj = markRaw(obj);
    let state = reactive(obj);
    function myFn() {
        state.name = 'test'; // 界面state永远不会更新
    }
    return { state, myFn };
  }
}
</script>

toRef

ref和toRef的区别:
ref->复制,修改响应式数据不会影响以前的数据
toRef->引用,修改响应式数据会影响以前的数据
ref->数据发生变化,界面就会自动更新
toRef->数据发生变化,界面不会自动更新

应用场景:如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef。

<script>
import { ref, toRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {name: 'ljh'};
    <!--let state = ref(obj.name);-->
    let state = toRef(obj, 'name');
    function myFn() {
        state.value = 'test';
    }
    <!--如果利用ref将某一个对象中的属性变成响应式数据,此时修改响应式数据是不会影响到原始数据的-->
    <!--console.log(obj); // {name: 'ljh'}-->
    <!--console.log(state); // Proxy对象,_value值是'test'-->
    
    <!--如果利用toRef将某一个对象中的属性变成响应式数据,此时修改响应式数据会影响到原始数据,但是并不会触发界面更新-->
    console.log(obj); // {name: 'test'}
    console.log(state); // Proxy对象,_value值是'test'
    return { state, myFn };
  }
}
</script>

toRefs

为了将多个数据都设置监听,或者对整个对象的所有数据发起监听,vue3 提供了便利的 API toRefs

<script>
import { toRefs } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {name: 'ljh', age: 10};
    let state = toRefs(obj);
    function myFn() {
        state.name.value = 'test';
        state.age,value = 8;
        console.log(obj); // {name: 'test', age: 8}
        console.log(state); // Proxy对象
    }
    return { state, myFn };
  }
}
</script>

Vue3.0递归监听、非递归监听、数据追踪、性能优化

默认情况下,Vue3 中的 ref 和 reactive 都是递归监听的,即能实时监听对象的底层变化。

只要我们对 ref 和 reactive 中的内容进行更改,在默认情况下,只要更改的对象不是我们在 Vue3-4 中提到的类似 Date 的类型,都是能察觉到并且进行双向数据绑定的。

在默认情况下,递归监听肯定是好的,它让数据的变化能被实时监测到。然而它也带来了性能消耗的问题。

  1. 递归监听存在的问题。如果数据量比较大,非常消耗性能。
  2. 非递归监听。shallowRef / shallowReactive。
  3. 如何触发非递归监听属性更新页面?如果是shallowRef类型数据,可以通过triggerRef来触发
  4. 应用场景。一般情况下使用ref和reactive即可,只有在需要监听的数据量比较大的时候,才会使用shallowRef、shallowReactive。

Vue3.0响应式数据本质

Vue2.x:通过defineProperty实现响应式数据
Vue3.x:通过Proxy实现响应式数据
Proxy注意点:

  1. set方法必须通过返回值告诉Proxy此次操作是否成功
let obj = { name: 'ljh', age: '20' }
let state = new Proxy(obj, handler: {
    get(obj, key) {
        // obj: { name: 'ljh', age: '20' }
        // key: name
        return obj[key]
    },
    set(obj, key, value) {
        obj[key] = value
        // 更新UI界面
        return true
    }
})

Vue3.0生命周期

  • beforeCreate → 请使用 setup()
  • created → 请使用 setup()
  • beforeMount → onBeforeMount
  • mounted → onMounted
  • beforeUpdate → onBeforeUpdate
  • updated → onUpdated
  • beforeDestroy(vue3别名:beforeUnmount)→ onBeforeUnmount
  • destroyed(vue3别名:unmounted)→ onUnmounted
  • errorCaptured → onErrorCaptured
  • renderTracked(vue3新增) → onRenderTracked
  • renderTriggered(vue3新增)→ onRenderTriggered
获取页面元素
<template>
 <div ref="box"></div>
</tempalte>
import { ref, onMounted } from 'vue'
export default {
    name: 'getDom',
    setup() {
        let box = ref(null)
        onMounted(() => {
            console.log('onMounted', box.value)
        })
        console.log('setup', box.value)
        return { box }
    }
}

备注:

vue2转换代码网站:https://template-explorer.vuejs.org/
vue3转换代码网站:https://vue-next-template-explorer.netlify.app/

文章截图代码来自知播渔李南江老师

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容