Vue3 学习笔记

1、Vue3 响应式原理

浅浅的先复习一下Vue2的响应式原理

1.1—— 回忆Vue2的响应式原理是:

1、对象类型使用Object.defineProperty() 对属性的读取、修改进行拦截操作,即数据劫持。
2、数组类型是通过重写更新数组的一系列方法来实现拦截操作。(对数组的更新方法进行了包裹)。

缺点

  1. 新增属性、删除属性,视图是不会更新的。
  2. 直接通过下标修改数组,界面不会更新。
<script>
        // Vue2响应式原理 通过Object.defineProperty() 对属性的读取,修改的操作进行拦截,即数据劫持
        // 1、VM.$data就是原始数据 , Vue的实例实际上也代理了data对象上的property, 因此访问VM.a === VM.$data.a
        // 2、当原始数据的属性以 ‘_’ 和 ‘$’ 开头,则不会被Vue实例代理, 因为这些属性可能会与Vue内置的property、API发生冲突

        // 原始数据对象 相当于 VM.$data
        let person = { 
            name:"张三",
            age: 18
        }

        // 模拟Vue2响应式原理
        // Vue实例代理的data对象上的所有property
        let p = {}

        Object.defineProperty(p, "name", {
            enumerable: true, // 可枚举的
            configurable: true, // 属性是否可删除
            get(){
                console.log("有人读取了obj的name属性")
                return person.name
            },
            set(value){
                person.name = value
                // 在这里进行更新视图的操作
                console.log("name变化了,Vue就是在这实现数据变化后,视图也更新的操作");
            }
        })

        Object.defineProperty(p, "age", {
            get(){
                console.log("有人读取了obj的age属性")
                return person.age
            },
            set(value){
                person.age = value
                console.log("age变化了,Vue就是在这实现数据变化后,视图也更新的操作");
            }
        })

    </script>
1.2——Vue3响应式原理

ref是通过Object.defineProperty() 实现的响应式

reactive 是通过Proxy()实例进行数据拦截,由Reflect实现数据的获取、修改、新增、删除操作的。

实现原理:

  • 通过Proxy代理:拦截对象中任意属性的变化,包括属性值的读取、修改、添加、删除、等
  • 通过Reflect反射:对被代理对象的属性进行操作
<script>
        // 原始数据
        let person = {
            name: "张三",
            age: 18
        }

        // 模拟Vue3响应式原理
        let p = new Proxy(person, {
            get(target, propName) {
                console.log(`有人读取了${propName}属性`);
                // return target[propName]
                return Reflect.get(target, propName)
            },
            set(target, propName, value) {
               // target[propName] = value
                Reflect.set(target, propName, value)
                console.log(`有人修改${propName}属性, 界面开始更新`);
            },
            deleteProperty(target, propName) {
                console.log(`有人删除了${propName}属性`);
                return Reflect.deleteProperty(target, propName)
                // return delete target[propName] // 返回删除的结果
            }
        })
    </script>	
1.3——什么是Object.defineProperty

1、Object.defineProperty(obj, prop, descriptor)是一个声名对象属性的方法

参数:

obj:要定义属性的对象。

prop:要定义或修改的属性的名称或 Symbol

descriptor:要定义或修改的属性描述符。

descriptor的配置项

属性描述默认值
configurable当该属性的值为true时,该属性才可以被delete删除false
enumerabletrue 只有在枚举相应对象上的属性时该属性显现。false
value该属性的值undefined
writabletrue只有与该属性相关联的值被assignment operator (en-US)改变时。false
get作为属性的getter函数,读取改属性的时候调用undefined
set作为属性的setter函数,修改改属性的时候调用undefined
<script>
    let p = {}
    Object.defineProperty(obj, prop, {
        configurable: false,
        enumerable: false,
    })
</script>
2、descriptor又是什么?

这就相当于一个配置项,具体配置如上表所示。

也就是控制一个对象属性的获取、修改、新增、删除的配置。

1.4、 ref与reactive的对比

1.5、 setup的两个注意点

在Vue2中,再给组件传入props的时候,组件内部都需要声名props,但是在组件内部未声名props也可以获取从VueComponents实例的$attrs属性上获取到传给组件的props,只不过在调用的时候可能会麻烦写。使用插槽往组件内部插入动态插入DOM结构,这些Dom结构也会出现在VueComponents实例身上。

setup的执行时机:在beforeCreate之前会执行一次,且 this 是 undefined


2、computed函数

Vue3的computed不再和Vue2一样为配置项,但是在Vue3中一样可以使用computed配置项。

Vue3中的computed需要从vue模块中引入

import {computed} from 'vue'	

Vue3计算属性的是一个函数

1.1 计算属性的简写形式

computed简写:

​ 参数:一个arrow fucntion(箭头函数)computed( ( )=>{ } )

person.fullName = computed(()=>{
	return person.firstName + '-' + person.lastName
})
2.2 计算属性的完整写法

computed完整写法:

​ 参数: { get(){}, set(){} }

person.fullName = computed({
    get(){
    	return person.firstName + '-' + person.lastName
    },
    set(newVal){ // newVal是修改后的值
        const arr = newVal.split('-')
        person.firstName = arr[0]
        person.lastName = arr[1]
    }
})

3、watch 函数

监视,监视的是一个地址、结构,无法直接监视某一个具体的值

就像ref所定义的响应式数据,let sum = ref(0) 可以监视sum, 但是不能直接监视sum.value

let sum = ref(0)
    let message = ref('你好啊')
    let person = reactive({
      name:"张三",
      age: 18,
      job: {
        j1:{
          salary: 20
        }
      }
    })
3.1 监视一个ref所定义的响应式数据
/* 
      参数:
        1、监视的对象,(监视多个属性时,可以使用数组)
        2、回调函数 (newVal, oldVal) 回调函数接收两个参数:1、新值 2、旧值
        3、监视属性的配置 immediate、deep
     */
    
    // 情况一 监视一个ref所定义的一个响应式数据
    watch(sum, (newVal, oldVal)=>{
      console.log(`sum变了。新值: ${newVal}  旧值${oldVal}`);
    }, {immediate:true, deep:true}) // 监视基本数据类型的时候deep是无意义的
3.2 监视多个ref所定义的响应式数据
// 情况二 监视多个ref所定义响应式数据
 watch([sum, message], (newVal, oldVal)=>{
       console.log('sum or message changed', newVal, oldVal) // newVal, oldVal,也是以数组的形式来区分的
 }, {immediate:true})
3.3 监视reactive所定义的一个响应式数据的所有属性

注意

1、此处无法正确的获取到oldValue,接收到的两个参数都是newValue

2、Vue3中的监视属性默认开启深度监视 deep:true; 注意:无法被关闭 。

// 情况三 监视reactive所定义的响应式数据的所有属性
    /* 
      注意:
        1、无法获取到oldValue
        2、默认开启深度监视, 但是无法关闭{deep:false} 是无效的
    */
    watch(person, (newVal, oldVal)=>{
      console.log('person有变化了', newVal, oldVal);
    }, {deep:false}) 

如何解决该问题?

切记,不可将reactive换成ref,因为ref在将对象做成响应式的时候,内部还是去使用了reactive的。

可以将需要监视对象的属性单独拿出来,使用ref所定义的响应式数据

3.4 监视reactive所定义的一个响应式数据的某个属性

注意:监视reactive所定义的响应式数据的某个属性时,watch函数的第一个参数不可以直接是对象的属性的形式, 应该写成函数的返回值形式

watch(()=>{}, ()=>{}, {}) // 监视的属性的回调函数,回调函数, 监视函数的配置

   1、无法监视对象的属性值为基本数据类型,可以监视对象的属性为引用数据类型
  
   2、如果需要监视的对象的属性为基本数据类型,那么watch的第一个参数应该写成函数的形式
// 监视对象的属性的为基本数据类型的数据
    // 将watch的第一个参数应该写成函数的形式,可以解决无法获取到oldVal的情况
    watch(()=>person.name, (newVal, oldVal)=>{
      console.log('person.name变化了', newVal, oldVal);
    })
3.5 特殊情况

这样子监视对象的时候依旧是无法获取到oldVal, 但是deep配置项是有效的,需要开启后才可以监视对象

/* 
      注意: 
        1、深度监视的配置项是有效的
        2、仍然无法获取到oldVal
    */
    watch(()=>person.job, (newVal,oldVal)=>{
      console.log("person.job变化了", newVal, oldVal)
    }, {deep: true}) 

4、watchEffect函数

4.1 watchEffect与watch的区别?

​ 1、watch需要指明所监视的属性,也要指明监视的回调函数;而watchEffect不需要指明监视的属性,只是在监视回调函数中用到哪个属性,就监视哪个属性。

4.2 watchEffect有点和computed相似。

computed: 只有在computed依赖的属性发生变化的时候会重新计算,且多次读取仅计算一次,因为computed更注重,所以一定需要返回值

watchEffect:监视回调函数,用到哪个属性就监视哪个属性。其更注重过程(回调函数)所以,不用写返回值

let sum = ref(0)
    let message = ref('你好啊')
    let person = reactive({
      name:"张三",
      age: 18,
      job: {
        j1:{
          salary: 20
        }
      }
    })
    // 所以这个watchEffect 会监视sum、person.job.j1.salary属性
watchEffect(()=>{
      let x1 = sum.value
      let salary = person.job.j1.salary
      console.log("watchEffect函数被调用了", x1, salary)
    })

5、生命周期

Vue3的生命周期与Vue2的生命周期的区别?

1、Vue3的生命周期在组合式api中没有了beforeCreate、和created这两个钩子相当于setup()

2、Vue3中的摧毁的生命周期钩子名称被替换了,具体见3 。

3、其他对应的生命周期为

​ Vue2 Vue3

  • beforeCreate ====> setup()
  • created ====> setup()
  • beforeMount ====> onBeforeMount
  • mounted ====> onMounted
  • beforeUpdate ====> onBeforeUpdate
  • update ====> onUpdate
  • beforeDestory ====> onBeforeUnmount
  • destory ====> onUnmounted

6、自定义hooks

hooks就是将多个组件中公用的的地方提取出来,在vue2中也有一个mixin混入,和这个差不多。、

hooks规范化:

​ 1、需要在src目录下创建hooks文件夹,然后里面都是一些js文件,这些文件的命名规则 useXxxx 需要在前面加上use

​ 2、组件需要用的话,可以直接引入

// usePoint.js
import {reactive, onMounted, onBeforeUnmount} from 'vue'
export default function(){

let point = reactive({
    x: 0,
    y: 0
  })

  function getPoint(event){
    console.log("event", event);
    point.x = event.clientX
    point.y = event.clientY
  }
  onMounted(()=>{
      console.log('移除getPoint监听事件');
      window.addEventListener('click', getPoint)
  })

  onBeforeUnmount(()=>{
      console.log('移除getPoint监听事件');
      window.removeEventListener('click', getPoint)
  })

  return point

}

7、toRef

  • 作用:创建一个ref对象,将其的value指向另外一个对象中的某个属性
  • 语法: const name = toRef(person, 'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部(模板…)使用时
  • 扩展:toRef 与 toRefs 功能一致,但是可以批量创建多个ref对象 语法:toRefs(person)

为何会出现这个API?

因为使用reactive所定义的响应式数据,在模板中使用的时候都需要使用该对象然后访问其需要的属性。为了去除每次访问其都需要通过reactive所定义的对象,为使其简单化,可以使用…toRefs,也可以使用toRef将响应式对象中的某个属性提取出来,并且是响应式的数据。


8、其他 composition API

8.1 shallowReactive与shallowRef

shallow: 浅层;浅层次的

  • shallowReactive:只处理对象最外层的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不对对象进行响应式处理。
  • 什么时候用?
    • 如果有一个对象数据,结构比较深,但是只需要监测外层的响应式变化时
    • 如果有一个对象数据,后续功能不修改该对象中的属性,而是使用新的对象时

8.2 readOnly 与 shallowReadOnly
  • readOnly:让一个响应式的数据变为只可读 (深只可读)
  • shallowReadOnly:让一个响应式的数据变为只可读 (浅可读)
  • 应用场景: 不希望数据被修改
import {reactive, shallowReadonly, readonly} from 'vue'
export default {
  name: 'App',
  setup() {
    let person = reactive({
      name:"张三",
      age: 18,
      job:{
        j1:{
          salary: 8000
        }
      }
    })

    // 浅可读:对象属性外面一层是只可读的,内层的属性是响应式的
    let shallowPerson = shallowReadonly(person)
    // 深可读
    let deepReadOnlyPerson = readonly(person)
    return {
      person: deepReadOnlyPerson
    }
  }
}

8.2 toRaw 与 markRaw
  • toRaw
    • 作用:将一个reactive生成的响应式对象转为普通对象(非响应式的)
    • 使用场景:用于将一个响应式对象转化成普通对象,对该对象的所有操作,都不会引发页面更新
  • markRaw
    • 作用:标记一个对象,使其永远不再变为响应式对象
    • 应用场景:
      • 有些响应式对象的属性的值不需要是响应式的,例如复杂的第三方类库…
      • 当渲染具有不打可变的数据源列表时,跳过响应式转化的渲染消耗

9、 响应式数据的判断

Vue3官方提供了一些判断变量是否为响应式的方法

  • isRef: 检查是否为一个ref对象
  • isReactive:检查是否为一个reactive所创建的响应式对象
  • isReadOnly:检查一个对象是否由readOnly创建的只读对象
  • isProxy:检查一个对象是否是由reactive或者readOnly方法所创建的

10、Fragment组件

在Vue2中:组件必须有一个跟标签

但是在Vue3中:组件可以没有跟标签,内部的多个标签都会被包含在一个fragment虚拟元素中

好处:减少标签的层级嵌套, 减少内存占用


11、Teleport组件

什么是Teleport组件?

​ Teleport是一个能够将我们的HTML结构移动到我们指定位置的技术

<template>
    <div class="container">
        <button @click="showDialog = true">打开弹窗</button>
<!-- 这里将Dialog组件直接在body的子层渲染 -->
        <teleport to="body"> 
            <div class="model-container" v-if="showDialog"></div>
            <div class="dialog-container" v-show="showDialog">
                <h1>我是弹窗组件</h1>
                <h2>Teleport组件</h2>
                <p>
                    Teleport是一种能够将指定Html结构移动到指定位置上的技术
                </p>
                <button @click="showDialog = false">关闭弹窗</button>
            </div>
        </teleport>
    </div>
</template>

12、Suspence组件

  • 等待组件的异步引入,让用户有更好的体验

  • 可以让主要的组件先渲染出来,然后其他的组件比较耗时的组件就异步引入,这样子可以防止一个组件出错,整个页面渲染失败

  • 如何使用?使用步骤

    • 首先异步引入(动态引入)组件
    import {defineAsyncComponent} from "vue"
    const Demo = defineAsyncComponent(()=>import('./components/Demo.vue')
    
    • 使用Suspense包裹组件,并配置好插槽default和fallback

      • default: 就是渲染成功要显示的dom结构

        <template v-slot:default>
            <Demo/>
        </template>
        
      • fallback 就是未渲染或渲染失败显示的dom结构

        <template v-slot:fallback>
        	<h1>加载中... ...</h1>
        </template>
        
  • 完整的DOM结构

<suspense>
      <template v-slot:default>
        <Demo/>
      </template>
      <template v-slot:fallback>
        <h1>加载中... ...</h1>
      </template>
    </suspense>

13、Vue3的其他调整

13.1 API 调整在这里插入图片描述
13.2 Vue3动画样式调整

在这里插入图片描述

13.3 移除keyCode作为v-on的修饰符,同时也不在支持config.keyCodes

原因:浏览器对keyCode的兼容性问题,所以取消了这样的写法

13.4 移除v-on.native修饰符
  • ​ 父组件中绑定的事件

    <demo
    	v-on:click="handleClick"
    	v-on:close="handleClose"
    />
    
  • 子组件声名自定义事件

    <script>
    	export default {
    		emits:['close']
    	}
    </script>
    
13.5 移除过滤器

​ 因为过滤器虽然看起来很方便,但是需要自定义语法,学习成本高,还有实现成本, 建议使用计算属性替换过滤器

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐