一、options api 选项式API

# 一、options api 选项式API

Options Api可以理解为就是组件的各个选项,data、methods、computed、watch等等就像是组件的一个个选项,在对应的选项里做对应的事情。

export default {
    data () {
        return {
            // 定义响应式数据的选项
        }
    },
    methods: {
        // 定义相关方法的选项
    },
    computed: {
        // 计算属性的选项
    },
    watch: {
        // 监听数据的选项
    }
    ...
}

在data中定义的数据,是无法做到响应式的,那是因为Object.definePropety只会对data选项中的数据进行递归拦截

在实际项目的开发过程中,数据定义在data中,方法定义在methods中,当我们的代码多起来,比如达到四、五百行的时候,如果我们想改动某个功能,就要去data中改数据,再去methods中改方法,来回地寻找。

# 二、composition api 组合式api

# 1、Composition Api

支持将相同的功能模块代码写在一起,甚至可以将某个功能单独的封装成函数,随意导入引用;也可以将任意的数据定义成响应式,再也不用局限于data中,我们只需要将每个实现的功能组合起来就可以了。

示例:

<template>
    <div>{{count}}</div>
</template>
<script setup>
    import { ref } from "vue";
    let count = ref(0);
</script>

# 2、watchEffect

1、watchEffect是立即执行的,不需要添加immediate属性。

2、watchEffect不需要指定对某个具体的数据监听,watchEffect会根据内容自动去感知,所以我们也可以在一个watchEffect中添加多个数据的监听处理

3、watchEffect不能获取数据改变之前的值。

同时,watchEffect会返回一个对象watchEffectStop,通过执行watchEffectStop,我们可以控制监听在什么时候结束。

简单理解watchEffect会在第一次运行时创建副作用函数并执行一次,如果存在响应式变量,取值会触发get函数,这个时候收集依赖存储起来,当其他地方给响应式变量重新赋值的时候,set函数中会触发方法派发更新,执行收集到的副作用函数,如果不存在响应式变量,就不会被收集触发

vue3不再只能有一个根元素

# 3、ref和reactive

ref和reactive的区别是什么呢,我们可以这样简单理解,它们都是用来定义响应式数据的,但是ref是用来给简单的数据类型定义响应式数据的,比如number、string、boolean等,而reactive是针对复杂的数据结构的,比如一个对象。

它们写法的区别主要在:ref定义的数据,修改的时候是需改xxx.value的,而reactive定义的不用,产生这个区别的原因是它们实现响应式的方法不一样。

# 4、小结:

Options Api

1、选项式的api,相关代码必须写在规定的选项中,导致相同功能的代码被分割,代码量上来后查找相关代码很麻烦,后期维护修改难度较大。

2、数据都挂载在同一个this下,对typescript的支持不友好,类型推断很麻烦。

3、代码的复用能力很差。

Composition Api

1、组合式api,代码定义很自由,相同功能代码整合到一起,查找修改都很方便。

2、公共代码的复用很简单,不同功能的代码也可以自由组合。

3、Vue相关的api都是通过import导入的,这在打包的时候很友好。

另外,vue3是支持options api的写法的

# 三、Vue3 响应式的实现

在Vue2.x中,响应式的机制深入人心,我们只需要在data中定义我们需要的数据,就会在初始化时被自动转为响应式数据。

但是在Vue2中,响应式的使用还存在一些限制,比如对象属性的增加和删除等并不能被监听到,在Vue3中,重新设计了响应式系统来解决这些问题。

# 1、Vue2.x的响应式——Object.defineProperty

Vue2响应式失效的现象

<template>
    <div>
        <span>姓名:</span>
        <span>{{person.name}}</span>
        
        <span>年龄:</span>
        <span>{{person.age}}</span>
        
        <button @click="changeName">修改姓名</button>
        <button @click="addAge">增加年龄</button>
    </div>
</template>
export default { 
    data () { 
        return {
            person: {
                name: '小明'
            }
        } 
     }, 
     methods: {
         addAge() {
             this.person.age = '18';
         },
         changeName() {
             this.person.name = '小红';
         }
     }
    ... 
}

data里面只定义了一个响应式对象person,我们定义了2个方法,一个是修改名称、一个是增加年龄,但是使用增加年龄方法时,会给响应式对象添加一个新的属性age,页面上的年龄部分并不会发生改变。

Vue2是通过Object.defineProperty循环遍历拦截data中的数据来实现响应式的。

Object.defineProperty其实不是真正的代理,而应该是拦截

而且Object.defineProperty也不是对对象进行拦截,而是拦截对象的具体的某个属性。

const person = {};

Object.defineProperty(person, 'name', {
  set (value) {
    console.log('name:',  value)
  },
  get () {
    return '小明'
  }
});

console.log(person.name);

Vue2.x的响应式实现其实就是递归遍历data中返回的对象,对每一个属性都使用Object.defineProperty进行拦截,而不在data中被初始化的数据是没有添加拦截的。

Vue2如何添加和删除响应式数据?

需要额外的api来实现,Vue.$set 和Vue.$delete方法分别实现添加、删除响应式数据

Vue2响应式的局限性

1、无法监听整个对象,只能对每个属性单独监听。

2、无法监听对象的属性的新增,删除(需要补充额外的api来解决)。

3、无法监听数组的变化。

# 2、Vue3的响应式-proxy

proxy是真正地对整个对象进行代理,因为proxy可以劫持整个对象,所以Object.defineProperty中新增,删除某个属性无法检测的问题就不存在了,同时proxy也可以检测数组的变化

const person = {
    name: '小明',
    age: 18
};
const personProxy = new Proxy(person, {
    get: function(target, prop) {
        console.log(`获取了${prop}:`, target[prop]);
        return target[prop];
    },
    set: function(target, prop, value) {
        console.log(`修改了${prop}:`, value);
        target[prop] = value;
    }
});

console.log('name:', personProxy.name); // 获取了name:小明
personProxy.age = 20; // 修改了age:20

参数target,表示当前代理的对象,prop是我们具体要操作的属性,set多了一个参数value是我们对新属性的赋值。

从方法的参数我们其实就能看出来,proxy是真的对整个对象进行拦截的,我们如果有新增或删除的属性,也不需要单独去添加处理,可以直接被检测代理。

在添加删除属性时,无需额外的api。proxy不支持IE11。

vue3另外一个代理的方法,那就是对象本身的get、set方法

const count = {
    _value: 0,
    set value(num) {
        console.log('修改了count:', num);
        this._value = num;
    },
    get value() {
        console.log('获取了count');
        return this._value
    }
}

console.log(count.value); // 获取了count
count.value = 1; // 修改了count: 1

这其实就是为什么我们使用ref定义的数据,赋值和取值的时候需要使用xxx.value了

一个Vue3 composition api常用的工具集:VueUse

# 四、Vue2升级到Vue3的非兼容性变更

Vue3中做了很多重构,有部分内容对于Vue2来说是不兼容的,所以说Vue2的代码直接升级到Vue3是不能直接运行的。

# 1、createApp的非兼容性变更

Vue2根实例挂载及全局组件注册方法:

import Vue from 'vue';
import App from './App.vue';
// 引入全局组件
import GlobalComponent from './GlobalComponent.vue';
// 注册全局组件
Vue.component('GlobalComponent', GlobalComponent);

new Vue({
  render: h => h(App),
}).$mount('#app')

Vue3不直接在Vue对象上进行操作了,而是通过createApp来创建一个App应用实例,所有的操作都在App上进行

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app');

现在我们想要在一个App上引入store,就可以使用下面的写法(全局对象被共享是一件非常危险的事情)

// 引入封装好的store
import store from "./store";

createApp(App).use(store).mount('#app');
createApp(App2).mount('#app2');

App实例上的store不会影响App2

# 2、api的import导入

我们在使用这些挂载在Vue对象下的Api时,需要经过import导入的方式来使用。

import { nextTick } from 'vue';

nextTick(() => {
    ...
})

按需加载的使用。

在Vue2的Api中,都是挂载在Vue下面,那么在打包的时候,会不管你有没有使用到这个Api,都会一起打包进去,如果都是这样,随着Vue的全局Api越来越多,冗余的代码也就越多,打包的耗时、体积或者说代价也就越大。

在Vue3中,通过import导入Api来使用,那我们在打包的时候,则只会将对应的模块打包进去,做到真正的用了多少就打包多少,就算Vue中再增加多少代码,也不会影响我们打包的项目。

# 3、小结

升级Vue3不仅需要更换Vue版本,还有一些非兼容性变更内容需要了解

  1. 全局的操作不再使用Vue实例,而是使用通过createApp创建的app实例。
  2. 全局和内部API已经被重构,需要使用import导入使用,并且支持tree-shake。

如何将localStorage中的数据变成响应式的?

// tool.js
import {ref, watchEffect} from 'vue';

const useLocalStorage = (name, value = {}) => {
    const localData = ref(JSON.parse(localStorage.getItem(name)) || value);

    watchEffect(() => {
        // 监听本地localstorage数据对应的响应式变量更改
        localStorage.setItem(name, JSON.stringify(localData.value));
    })

    return localData;
}

export {
    useLocalStorage
}

假设有一个计数器,需要将数据同步到本地的localStorage中,我们只需要在计数器文件中引入useLocalStorage方法即可:

<script setup>
  import {useLocalStorage} from './useLocalStorage';
  // 定义响应式数据
  let count = useLocalStorage('count', 0);

  const addCount = () => {
    count.value ++;
  }

</script>