vue2与vue3的差异(转载)

1、响应式

响应式:这是一个比较模糊的概念。通常可以理解成对某些操作有所反应。

vue2与vue3的差异(转载)_第1张图片

vue2和vue3响应式原理的区别

  • 无法检测到对象属性的新增或删除(vue2提供了vue.set方法来解决)

  • 直接通过下标修改数组,无法监听数组的变化,

  • 深度监听,层层处理,影响性能,性能不好,需要对每一个key循环递归处理,特别是处理大数据尤为明显

  • Object.defineproperty()每调用一次都只能对对象的某一个属性进行数据劫持,所以要采用循环遍历,代码写起来比较麻烦。

Vue2时代Option Api ,data、methos、watch.....分开写,这种是碎片化的分散的,代码一多就容易高耦合,维护时来回切换代码是繁琐的!

Vue3时代Composition Api,通过利用各种Hooks和自定义Hooks将碎片化的响应式变量和方法按功能分块写,实现高内聚低耦合

形象的讲法:Vue3自定义Hooks是组件下的函数作用域的,而Vue2时代的Mixin是组件下的全局作用域。全局作用域有时候是不可控的,就像var和let这些变量声明关键字一样,const和let是var的修正。Composition Api正是对Vue2时代Option Api 高耦合和随处可见this的黑盒的修正,Vue3自定义Hooks是一种进步。

2. 响应式api

Vue3 提供了两种方式构建响应式数据:ref 和 reactive

2.1 ref & reactive

ref 用于构建简单值的响应式数据,比如String,Number,基于 Object.defineProperty 监听 value 值,原理是将普通的值转化为对象,并且在获取和设置值时可以增加依赖收集和触发更新功能

// ref 源码部分functionref(value){
  returncreateRef(value)
}

functionconvert(rawValue){
  returnisObject(rawValue) ? reactive(rawValue) : rawValue
}
// shallwfunctioncreateRef(value) {
  const refImpl = newRefImpl(value);

  return refImpl;
}
exportclassRefImpl {
  private _rawValue: any;
  private _value: any;
  public dep;
  public __v_isRef = true;

  constructor(value) {
    this._rawValue = value;
    // 看看value 是不是一个对象,如果是一个对象的话// 那么需要用 reactive 包裹一下this._value = convert(value);
    this.dep = createDep();
  }

  getvalue() {
    // 收集依赖trackRefValue(this);
    returnthis._value;
  }

  setvalue(newValue) {
    // 当新的值不等于老的值的话,// 那么才需要触发依赖if (hasChanged(newValue, this._rawValue)) {
      // 更新值this._value = convert(newValue);
      this._rawValue = newValue;
      // 触发依赖triggerRefValue(this);
    }
  }
}
let num1 = ref(111)
// Vue 3.0 内部将 ref 悄悄的转化为 reactivelet num1 = reactive({
  value: 111
})
vue2与vue3的差异(转载)_第2张图片

可以看到,ref方法将这个字符串进行了一层包裹,返回的是一个RefImpl类型的对象,译为引用的实现(reference implement),在该对象上设置了一个不可枚举的属性value,所以使用name.value来读取值。

ref通常用于定义一个简单类型,那么是否可以定义一个对象或者数组?

const param = ref({
  name: 'lili',
  age: 25
})
console.log(param, param.value.name)
vue2与vue3的差异(转载)_第3张图片

控制台可以看到,对于复杂的对象,值是一个被proxy拦截处理过的对象,但是里面的属性name和age不是RefImpl类型的对象,proxy代理的对象同样被挂载到value上,所以可以通过obj.value.name来读取属性,这些属性同样也是响应式的,更改时可以触发视图的更新

通过上面ref的使用案例,起始不管是复杂引用类型,如array,object等,亦或者是简单的值类型string,number都可以使用ref来进行定义,但是,定义对象的话,通常还是用reactive来实现

reactive 用于构建复杂的响应式数据,不能定义普通类型,基于 Proxy 对数据进行深度监听

reactive 参数必须是对象(json 或 Array),不能定义普通类型

【ref 与reactive的区别与联系】

一般来说,ref被用来定义基本数据类型,reactive定义引用数据类型

ref定义对象时,value返回的是proxy,reactive定义对象时返回的也是proxy,而这确实存在一些联系,ref来定义数据时,会对里面的数据类型进行一层判断,当遇到复杂的引用类型时,还是会使用reactive来进行处理

2.2 toRef & toRefs

toRef

接收两个参数target和attr,target是一般是reactive的响应式对象,attr是对象的属性,返回响应式变量(采用引用的方式,修改响应式数据,会影响原始数据,并且数据发生改变)

vue2与vue3的差异(转载)_第4张图片

toRefs:批量处理

作用将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象提供给外部使用

批量处理只能处理一层数据,深层的单独取出,在setup()中可以用return { ...toRefs(object)}的方式,将整个响应式对象object的所有属性提供给外部使用。

2.3 watch & watchEffect

watch

watch 的功能和之前的 Vue 2.0 的 watch 是一样的。和 watchEffect 相比较,区别在 watch 必须指定一个特定的变量,并且不会默认执行回调函数,而是等到监听的变量改变了,才会执行。并且你可以拿到改变前和改变后的值

watch有三个参数:

参数1:监听的参数

参数2:监听的回调函数

参数3:监听的配置(immediate)



监视reactive所定义的响应式数据中的某一个值和监视reactive所定义的响应式数据中的一些数据的改变区别于直接对reactive所定义的响应数据中所有数据进行监视,在默认情况下,监视reactive所定义的响应数据中所有数据是开启深度监视的,也就是说,无论数据在第几层,都能监视到,但是最后情况,是针对其中某一个值或某一些值进行监视的,如果还要监视其属性下的更深层的值,是要开启深度监视的,否则无法监视得到

watchEffect
  • 它是立即执行的,在页面加载时会主动执行一次,来收集依赖

  • 不需要传递需要侦听的内容,它可以自动感知代码依赖,只需要传递一个回调函数

  • 它不能获取之前数据的值

  • 它的返回值用来停止此监听


watchEffect 函数返回一个新的函数,我们可以通过执行这个函数或者当组件被卸载的时候,来停止监听行为

setup() {
  let timer = nulllet state = reactive({
    search: Date.now()
  })
 
  // 返回停止函数const stop = watchEffect((onInvalidate) => {
    console.log(`监听查询字段${state.search}`)
  })
 
  consthandleSearch = () => {
    state.search = Date.now()
  }
 
  setTimeout(() => {
    console.log('执行 stop 停止监听')
    stop() // 2 秒后停止监听行为
  }, 2000)
  
  return {
    state,
    handleSearch
  }
}

watchEffect 的回调方法内有一个很重要的方法,用于清除副作用。它接受的回调函数也接受一个函数 onInvalidate。重要的是它将会在 watchEffect 监听的变量改变之前被调用一次

export default {
  setup () {
    const state = reactive({
      search: Date.now()
    })
    // 返回停止函数
    const stop = watchEffect((onInvalidate) => {
        console.log(`监听查询字段${state.search}`)
        onInvalidate(
            () => {
                console.log('执行 onInvalidate')
        })
    })
    const handleSearch = () => {
        state.search = Date.now()
    }
    return {
          state,
          handleSearch
    }
  }
}

2.3 shallowRef & shallowReactive(浅响应)

shallowRef

只监听.value属性的值的变化,对象内部的某一个属性改变时并不会触发更新,只有当更改value为对象重新赋值时才会触发更新

const foo = shallowRef({
  c: 1,
})
const change = () => {
      foo.value.c = 2// 视图不更新
      foo.value={a:1} // 视图更新
}
shallowReactive(浅响应)

只监听对象的第一层属性,对嵌套的对象不做响应式处理

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})
const change = () => {
  state.foo = 2// 视图更新
  state.nested={count:2}// 视图更新
  state.nested.bar =3// 视图不更新
}

3. hooks(组合式函数)

介绍

当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间而抽取一个可复用的函数。这个格式化函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。

本质是一个函数,将setup函数中的composition API进行了封装

类似vue2的mixin

复用代码,是setup中的逻辑更清楚易懂

3.1 正常写一个功能

需求:鼠标经过打印出当前经过点的坐标


3.2 使用hooks

import { ref, onMounted, onUnmounted } from'vue'
    // 按照惯例,组合式函数名以“use”开头exportfunctionuseMouse() {
    // 被组合式函数封装和管理的状态
    const x = ref(0)
    const y = ref(0)
    
    // 组合式函数可以随时更改其状态。
    functionupdate(event) {
        x.value = event.pageX
        y.value = event.pageY
    }
    
    // 一个组合式函数也可以挂靠在所属组件的生命周期上
    // 来启动和卸载副作用
    onMounted(() =>window.addEventListener('mousemove', update))
    onUnmounted(() =>window.removeEventListener('mousemove', update))
    
    // 通过返回值暴露所管理的状态
    return { x, y }
}


    import { useMouse } from'./mouse.js'const { x, y } = useMouse()

3.3 嵌套组合式函数 (升级版)

还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。实际上,这正是我们决定将实现了这一设计模式的 API 集合命名为组合式 API 的原因。

import { onMounted, onUnmounted } from'vue'
export function useEventListener(target, event, callback) {
    // 如果你想的话,
    // 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
    onMounted(() => target.addEventListener(event, callback))
    onUnmounted(() => target.removeEventListener(event, callback))
}
import { ref } from'vue'import { useEventListener } from'./event'
export functionuseMouse() {
    const x = ref(0)
    const y = ref(0)
    
    // 组合式函数可以随时更改其状态。
    functionupdate(event) {
        x.value = event.pageX
        y.value = event.pageY
    }

  useEventListener(window, 'mousemove', update)

  return { x, y }
}

3.4 可以引入多个hook文件并且还可以传参 (终极版)

综上所诉,核心逻辑一点都没有被改变,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样,你也可以在组合式函数中使用所有的组合式API函数。现在,在任何组件中都可以使用 useMouse() 功能了。

相比于 Mixin 区别

  1. mixin向外暴露出的是一个对象,hooks则是一个可传参数的方法

  1. Mixin 命名容易发生冲突:因为每个 mixin 的变量和方法都被合并到同一个组件中,所以为了避免变量名和方法名冲突,仍然需要了解其他每个特性;Mixin同名变量会被覆盖,Vue3自定义Hook可以在引入的时候对同名变量重命名

  1. 可重用性是有限的:我们不能向 mixin 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。

  1. Mixin不明的混淆,我们根本无法获知属性来自于哪个Mixin文件,给后期维护带来困难

export default {
  mixins: [ a, b, c, d, e, f, g ], //一个组件内可以混入各种功能的Mixinmounted() {
    console.log(this.name)  //问题来了,这个name是来自于哪个mixin?
  }
}

4. setup

4.1 理解

一个组件选项,在组件被创建之前,props 被解析之后执行。它是composition API(组合式 API) 的入口

setup的执行周期在beforeCreate之前,因此this为undefined

  1. setup 是一个新的配置项,值是一个函数

  1. 所有的composition Api都放在setup里面

  1. 必须要有返回值,值是一个对象,在模板中直接使用

vue2与vue3的差异(转载)_第5张图片

4.1 setup的用法

export default {
  props: {
    title: String
  },
  setup(props,context) {
    console.log(props.title)
    // Attribute (非响应式对象,等同于 $attrs)
    console.log(context.attrs)
    
    // 插槽 (非响应式对象,等同于 $slots)
    console.log(context.slots)
    
    // 触发事件 (方法,等同于 $emit)
    console.log(context.emit)
    
    // 暴露公共 property (函数) ****** 查询下二是否有
    console.log(context.expose)
  }
}

4.2 单文件组件

//子组件 代码


- **添加响应性**

为了给 provide/inject 添加响应性,使用 ref 或 reactive 。

完整实例2:provide/inject 响应式

```vue
//父组件代码




// InjectCom 子组件代码

上述示例,在父组件或子组件都会修改 info 的值。

provide / inject 类似于消息的订阅和发布,遵循 vue 当中的单项数据流,什么意思呢?

就是数据在哪,修改只能在哪,不能在数据传递处修改数据,容易造成状态不可预测。

在订阅组件内修改值的时候,可以被正常修改,如果其他组件也使用该值的时候,状态容易造成混乱,所以需要在源头上规避问题。

readonly 只读函数,使用之前需要引入,如果给变量加上 readonly 属性,则该数据只能读取,无法改变,被修改时会发出警告,但不会改变值。

使用方法:

import { readonly } from "vue"
let info = readonly('只读info值')
setTimout(()=>{
 info="更新info"//两秒后更新info的值
},2000)

运行两秒后,浏览器发出警告,提示 info 值不可修改。

所以我们就给provide发射出去的数据,添加一个只读属性,避免发射出去的数据被修改。

完整实例2的 provide 处添加 readonly 。

provide('info', readonly(info))

在子组件修改值的时候,会有一个只读提醒。

修改值的时候,还是需要在 provide 发布数据的组件内修改数据,所以会在组件内添加修改方法,同时也发布出去,在子组件处调用就可以了。如:

完整示例3:修改数据

6. 新增的一些组件

1. Fragment

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

  • 在Vue3 中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处:减少标签层架,减少内存占用,并且该标签不会出现在dom树中。

2.Teleport

Teleport是一种能将我们的组件html结构移动到指定位置的技术。

好处:当我们使用组件时,不用担心展开模态框会破坏页面结构

父组件App.vue:





子组件ChildComponent.vue:





孙组件GrandsonAssembly.vue:(用到了A模块框组件)





A模块框组件ModalBox.vue:





3.Suspense(实验阶段)

  • 类似于 keep-alive 的形式不需要任何的引入,可以直接进行使用。

  • 自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。

  • 等待异步组件时渲染一些额外内容,有更好的用户体验

  • 使用步骤

  • 异步引入组件:

import { defineAsyncComponent } from"vue";
constChild = defineAsyncComponent(()=>import('./compoments/Child.vue'))
  • 使用Suspense包裹组件,并配置好 default 与 fallback

7、tips

1、关于过滤器

在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。

2、状态驱动的动态 CSS



3. VueUse

官网文档:https://vueuse.org/

是为Vue 2和3服务的一套Vue Composition API的常用工具集

通俗的来说,这就是一个工具函数包,它可以帮助你快速实现一些常见的功能,免得你自己去写,解决重复的工作内容。以及进行了基于 Composition API 的封装

//isFullscreen 当前是否是全屏//toggle  是函数直接调用即可const { isFullscreen, toggle } = useFullscreen();


//text 粘贴的内容//copy 是粘贴函数const { text, copy, isSupported } 
= useClipboard({ copiedDuring: 1500 });


const title = useTitle()
console.log(title.value) // print current title
title.value = 'Hello'// change current title

VueUse常用方法总结

VueUse将所有方法按照功能性进行了分类,包含:Animation、Browser、Component、Formatters、Misc、Sensors、State、Utilities、Watch,详见vueuse.functions。其中较为常用的有:

useClipboard 复制到剪贴板

useFetch fetch 请求

useFullscreen 全屏

useLocalStorage localStorage 存储

useDebounceFn 防抖/节流

useThrottleFn

你可能感兴趣的:(vue.js)