Vue3.x

Vue3.0学习笔记

2021-12-14  本文已影响0人  多吃水果多锻炼

Vue3.0的六大亮点

性能更快

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

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

只比对带有 PF 的节点
并且通过 Flag 的信息得知当前节点要比对的具体内容
diff优化

2、HoistStatic静态提升

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

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

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

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

ref注意点:

reactive

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

什么是reactive?
reactive注意点:
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生命周期

获取页面元素
<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/

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

上一篇下一篇

猜你喜欢

热点阅读