简述

最近把vue网课看完了,vue2,vue3大体过了一遍,目前准备这几天水两篇博客,整理笔记,然后学习cloud
不过学习cloud之前,我觉得还是应该过一下后端的内容,学vue学了两个月,很多东西忘记了,不过记了不少
笔记,回顾起来没有太大困难,下面介绍下本篇的具体内容:

vue响应式原理

在基础原理上篇我们讲过,vue实现响应式原理,主要解决两个问题:

  • 如何拦截到数据的变化,即数据劫持的实现
    在上篇中,我们知道vue2实现数据劫持就是通过Object.defineProperty方法实现
  • 当数据发生改变时,如何让使用该数据的页面发生改变?
    发布订阅者模式

下面主要讲讲vue中发布订阅者模式,也就是说页面如何跟着数据改变而改变,以下是个人理解,不妥之处还望见谅

//先来一个简单的案例,仅仅只展示姓名,年龄
<template>
  <div id="app">
    {{name}}
    {{name}}
		{{name}}
		{{age}}
		{{age}}
  </div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
      name: '万一',
      age: 23
    }
  }
}
</script>

如上,当我们data中存在name,age数据时,vue2底层会使用Object.defineProperty方法,对这些数据进行
数据劫持,也就是说,当我们访问name,修改name,都能劫持到操作,进入到name的get,set方法中去
这些经过”加工”的数据被称为响应式数据,因为这些数据都有对应的get,set方法,vue底层将这些响应式数据
保存到一个Observer观察者对象中,然后根据每个响应式数据,生成对应的Dep对象,生成name的Dep对象和age的Dep对象,

当页面中使用到了name,例如{{name}},就会执行name的get方法,进入get方法后
生成一个Watcher对象,每使用一个name就会生成一个Watcher对象,像上面例子中,name会生成3个name的Watcher对象
并且,这三个Watcher对象会被放到name对应的Dep对象的数组中保存起来,这是在name的get方法发生的事情,同理age也是如此。

而当我们修改数据时,例如修改name数据后,会进入到name的set方法中,会调用name对应的Dep对象
遍历其中每一个Watcher对象,调用watcher对象中的update方法,页面中使用到的name数据的地方就会发生改变。

以上就是发布订阅者模式:watcher对象就是订阅者,对应的Dep对象就是发布者,当数据更新,发布者通知所有
订阅该数据的watcher对象进行页面更新。

下面是来自网课中响应式原理的图片:

vue3中数据代理/数据劫持

在vue3中,如果想让数据是响应式的,需要使用ref函数,reactive函数,ref函数可以将普通数据,对象数据包装成响应式的
而reactive函数则只能将对象数据包装成响应式,当然,其实ref函数对于对象数据的处理也是使用reactive函数。

vue3中,对于基本类型使用Object.defineProperty方法实现响应式数据,ref函数内部对于基本数据就是如此,
对于对象类型数据,使用ES6的Proxy对象实现

下面来看看ref函数和reactive的使用:

  • ref函数的使用

    • ref案例之处理基本类型

      <template>
        我的名字:{{name}}<br/>
        我的年纪:{{age}}<br/>
        <button @click="changeName">更改姓名</button>
      </template>
      <script>
      import {ref} from 'vue'
      export default {
        name: 'App',
        setup(){
          let name = ref("夜归风似雪")   //使用ref函数创建的对象是ref对象,这里的数据实现了响应式
          let age = ref(23)
          function changeName() {
            name.value="晚来天欲雪"	//通过ref函数创建的响应式数据,使用时需要加上.value才是真正数据
          }
          return {
            name,
            age,
            changeName
          }
        }
      }
      </script>
    • ref案例之处理对象类型

      <template>
        person信息:{{person.sex}}
        <button @click="changeName">更改性别</button>
      </template>
      <script>
        import {ref} from 'vue'
        export default {
          name: 'App',
          setup(){
            let person = ref({color: 'yellow',sex: 'boy'})
            function changeName() {
              person.value.sex='girl'
            }
            return {
              changeName,
              person
            }
          }
      }
      </script>
    • 总结
      经过ref函数调用,生成的name,age,person都是refImpl引用对象,数据都保存在refImpl对象的value属性中
      如果是普通数据类型,value属性保存的就是定义时的数据,例如”夜归风似雪”,23等等, 这些数据使用
      Object.defineProperty实现响应式的(有setter,getter方法)如果是对象类型,则value属性中保存的是proxy对象
      如果想修改对象类型中的数据:person.value.sex="girl", 对象类型的数据,内部使用vue3中的reactive函数实现响应式

  • reactive函数的使用

    <template>
      性别:{{person.sex}}
      <button @click="changeSex">更改性别</button>
    </template>
    
    <script>
    import {reactive} from 'vue'
    export default {
      name: 'App',
      setup(){
        let person = reactive({color: 'yellow',sex: 'boy'})
        function changeSex() {
          person.sex='girl'	//访问,修改由reactive包装的响应式对象,不需要使用.value
        }
        return {
          changeSex,
          person
        }
      }
    }
    </script>

    作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
    语法:const 代理对象 = reactive(源对象),接收一个对象(或数组),返回一个代理对象(Proxy实例对象,简称proxy对象)
    reactive定义的响应式数据是”深层次的”,reactive函数内部基于ES6的Proxy实现,通过代理对象操作源对象
    内部数据进行操作,ref函数之所以能对对象类型数据进行响应式,底层也是通过reactive函数

既然reactive函数内部基于ES6的Proxy实现,那么我们来看看proxy是如何使得数据是响应式的吧
通过Proxy(代理),我们可以拦截对象中任意属性的变化,即对属性增删改查操作都能够拦截,还记得以前
通过Object.defineProperty方法只有get,set方法。
下面是一个使用Proxy实现数据响应式的案例

/*
	数据代理和数据拦截本质上差不多,非要区分的话,下面是个人理解:
	数据代理指的是,如果A代理B,对A的操作就会使得B更改,
	而响应式原理指的是,在A对B更改的过程中可以做点事,即劫持到对数据的更改操作
*/

//源对象
let person = {
  name: '万一',
  age: 23
}

/*代理对象,通过对代理对象增删改查,已经实现了数据代理,但没实现响应式(数据劫持),即我不能拦截到对数据的增删改查操作*/
//let p = new Proxy(person,{});

/*做到数据劫持*/
let p = new Proxy(person,{
  get(target,propName){	
    console.log("已经成功劫持到对数据读取操作")
    //return target[propName]
    return Reflect.get(target,propName)   //vue3中使用Reflect对属性进行真正操作,好处是:使得框架不必写太多try,catch
  },
  set(target,propName,value){		//target是源对象,propName是拦截到的属性,value是接收的新值
    console.log("已经成功劫持到对数据修改/添加操作")
    //return target[propName]=value
    return Reflect.set(target,propName,value)
  },
  deleteProperty(target,propName) {
    console.log("已经成功劫持到对数据删除操作")
    //return delete target[propName]
    return Reflect.deleteProperty(target,propName)
  }
});

vue3中生命周期函数

vue3中的生命周期函数与vue2中的有一点小小的变化

  • 将vue2中的beforeDestroy,destroyed函数名改成beforeUnmount,unmounted

  • 由通过配置属性的方式,改成composition API的方式,不过原来vue2通过属性配置生命周期函数的方式
    在vue3中也能使用,Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

    beforeCreate===>setup()
    created=======>setup()
    beforeMount ===>onBeforeMount
    mounted=======>onMounted
    beforeUpdate===>onBeforeUpdate
    pdated =======>onUpdated
    beforeUnmount ==>onBeforeUnmount
    unmounted =====>onUnmounted
    注意:使用composition API的方式没有beforeCreate,created这两个钩子
    如果你想要使用,你可以通过配置项方式使用,另外setup钩子比beforeCreate钩子更早执行。

setup函数的学习

setup函数是所有Composition API(组合API)表演的舞台,我们可以在该函数中·,定义数据
创建函数
setup函数的两种返回值:

  • 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。
  • 若返回一个渲染函数:则可以自定义渲染内容。

注意点:

  • 尽量不要与Vue2.x配置混用
    • Vue2.x配置(data、methods、computed…)中可以访问到setup中的属性、方法。
    • 但在setup中不能访问到Vue2.x配置(data、methods、computed…)。
    • 如果有重名, setup优先。
  • setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。
    (后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
  • setup函数中this是undefined,因为setup函数在beforeCreate函数前面执行,此时组件中的data和methods还没有初始化。

setup的参数:

  • props:值为对象,包含:组件外部传递过来(组件标签上传递的数据),且组件内部声明接收了的属性(使用配置项prop接收的属性)
  • context:上下文对象
    • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
    • slots: 收到的插槽内容, 相当于 this.$slots
    • emit: 分发自定义事件的函数, 相当于 this.$emit

toRef函数

作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。简单点说就是创建一个响应式数据,指向某个对象中的属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
toRefs专业定义: toRefs()函数可以将reactive()创建出来的响应式对象,转换为普通对象,
只不过这个对象上的每个属性节点,都是ref()类型的响应式数据

toRef,toRefs的使用:

<template>
  person信息:{{sex}}
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
  name: 'App',
  components: {
    HelloWorld
  },
  setup(){

    let person = reactive({color: 'yellow',sex: 'boy'})
    console.log(toRef(person,'color'))  //ObjectRefImpl {_object: Proxy, _key: 'color', __v_isRef: true}
    console.log(toRefs(person));  //{color: ObjectRefImpl, sex: ObjectRefImpl}
    return {
      ...toRefs(person)  //...是扩展运算符,将对象中的属性加到另一个对象中,解构赋值
    }
  }
}
</script>
/*
	不要对一个响应式数据进行解构,会破坏响应式,但上面...refs(person)中refs(person)返回的普通对象
*/