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
})

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

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

const param = ref({
  name: 'lili',
  age: 25
})
console.log(param, param.value.name)

控制台可以看到,对于复杂的对象,值是一个被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是对象的属性,返回响应式变量(采用引用的方式,修改响应式数据,会影响原始数据,并且数据发生改变)

<template>
    <div>{{ name }}:{{ data.age }}:{{ pq }}</div><br /><button @click="change2">职业</button>
</template>
<script>import { reactive, toRef } from'@vue/reactivity';
 
exportdefault {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 23,
            job: {
                p: {
                    zhiye: '打工人'
                }
            }
        });
        functionchange2() {
            data.name = 'xiaobai';
            data.age = 34;
            data.job.p.zhiye = 'hh';
        }
        return {
            data,
            name: toRef(data, 'name'),
            pq: toRef(data.job.p, 'zhiye'),
            change2
        };
    }
};
</script><stylescoped></style>
toRefs:批量处理

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

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

<script>
import { reactive, toRefs } from'vue'exportdefault {
  setup () {
    const data = reactive({
      list: [],
      count: 0,
      title: ''
    })
    return {
      ...toRefs(data)
    }
  }
}
</script>

2.3 watch & watchEffect

watch

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

watch有三个参数:

参数1:监听的参数

参数2:监听的回调函数

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

<template>
    <h2>我是TestA组件</h2>
    <h2>当前求和为:{{sum}}</h2>
    <button @click="sum++">点击按钮sum +1</button>
    <h2>现在页面展示信息:{{info}}</h2>
    <button @click="info = info + 'test组件'">点击按钮</button>
    <div>
        <div>姓名:{{obj.name}}</div>
        <div>年龄:{{obj.age}}</div>
        <button @click="obj.age++">点击修改年龄</button>
        <button @click="obj.name = '李四'">点击修改姓名</button>
    </div>
</template>

<script>
import { reactive, ref, watch } from'vue'
export default {
    name: 'TestA',
    setup () {
        // 数据const sum = ref(0)
        const info = ref('我是')
        const obj = reactive({
            name: 'ls',
            age: 20
        })
        
        // 监视属性
        watch(sum, (newValue, oldValue) => {
        // 回调函数形式
            console.log('求和的值变了', '变化后的值是' + newValue, '变化前的值是' + oldValue)
        })
        watch([sum, info], (newValue, oldValue) => {
            // 回调函数形式console.log(newValue, 'new=value', oldValue)
            console.log('求和的值变了', '变化后的值是' + newValue, '变化前的值是' + oldValue)
        })
        watch(obj, (newValue, oldValue) => {
            // 回调函数形式
            // watch属性是强制开启深度监视的,无论数据有多少层,只要数据一改变,在Vue3中都是能被监视到,
            // 但是在Vue2中,如果不开启深度监视的话,watch属性是无法监视到深层次数据的改变的
            console.log(newValue, 'obj=new=value', oldValue)
        })
        watch(() => obj.age, (newValue, oldValue) => {
            // 监听响应书数据中一个数值的改变
            console.log(newValue, 'new=value', oldValue)
        })
        // 返回对象
        return {
            sum,
            info,
            obj
        }
    }
}
</script>

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

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

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

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

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

<template>
    <div>
        <h1>{{state.search}}</h1>
        <button @click="handleSearch">改变查询字段</button>
    </div>
</template>
<script>
import {reactive ,watchEffect } from'vue'
export default {
  setup(){
    let state=reactive({
      search:Date.now()
    })
    watchEffect(
      ()=>{
        console.log(`监听${state.search}`)
      }
    )
    consthandleSearch=()=>{
      state.search=Date.now()
    }
    return{
      state,
      handleSearch
    }
  }
}
</script>

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
    }
  }
}
</script>

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 正常写一个功能

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

<template>
Mouse position is at: {{ x }}, {{ y }}
</template>
<script setup>
  import { ref, onMounted, onUnmounted } from 'vue'
  
  const x = ref(0)
  const y = ref(0)
  
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }
  
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

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 }
}
<template>Mouse position is at: {{ x }}, {{ y }}</template>
<script setup>
    import { useMouse } from'./mouse.js'const { x, y } = useMouse()
</script>

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文件并且还可以传参 (终极版)

<script setup>
  import { useFeatureA } from './featureA.js'
  import { useFeatureB } from './featureB.js'
  import { useFeatureC } from './featureC.js'
  
  const { foo, bar } = useFeatureA()
  const { baz } = useFeatureB(foo)
  const { qux } = useFeatureC(baz)
</script>

综上所诉,核心逻辑一点都没有被改变,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样,你也可以在组合式函数中使用所有的组合式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. 必须要有返回值,值是一个对象,在模板中直接使用

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
//父组件代码
<template>
    <div>
      info:{{info}}
      <InjectCom ></InjectCom>
    </div>
</template>
<script>
import InjectComfrom"./InjectCom"
import { provide,readonly,ref } from"vue"
export default {
    setup(){
          let info = ref("今天你学习了吗?")
          setTimeout(()=>{
            info.value = "不找借口,立马学习"
          },2000)
          provide('info',info)
          return{
            info
          }
    },
    components:{
          InjectCom
    }
  }
</script>


// InjectCom 子组件代码
<template>
{{info}}
</template>
<script>import { inject } from"vue"
export default {
    setup(){
      const info = inject('info')
      setTimeout(()=>{
        info.value = "更新"
      },2000)
      return{
        info
      }
    }
  }
</script>

上述示例,在父组件或子组件都会修改 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:

<template>
<div class="app">
  <h1>这是祖先组件</h1>
  <ChildComponent></ChildComponent>
  </div>
</template>

<script>
  import ChildComponent from "@/components/ChildComponent";
  
  export default {
    name: 'App',
    components: {ChildComponent}
  }
</script>

<style>
  .app {
    background-color: gray;
    padding: 10px;
  }
</style>

子组件ChildComponent.vue:

<template>
  <div class="child">
    <h1>这是孩子组件</h1>
    <GrandsonAssembly></GrandsonAssembly>
  </div>
</template>

<script>
import GrandsonAssembly from "@/components/GrandsonAssembly";

export default {
  name: "ChildComponent",
  components: {GrandsonAssembly}
}
</script>

<style scoped>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

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

<template>
<div class="son">
  <h1>这是孙子组件</h1>
  <ModalBox></ModalBox>
  </div>
</template>

<script>
  import ModalBox from "@/components/ModalBox";
  
  export default {
    name: "GrandsonAssembly",
    components: {ModalBox}
  }
</script>

<style scoped>
  .son {
    background-color: orange;
    padding: 10px;
  }
</style>

A模块框组件ModalBox.vue:

<template>
 <div>
  <button type="button" @click="isShow = true">弹出模态框</button>
  
  <teleport to="body"><!-- 将元素传送到body标签的最后面 -->
    <section v-if="isShow" class="mask"><!-- 语义化标签,表示一个独立的区块,这里用来做遮罩效果 -->
      <div class="modalBox"><!-- 模态框 -->
        <h3>这是一些内容</h3>
        <h3>这是一些内容</h3>
        <h3>这是一些内容</h3>
        <h3>这是一些内容</h3>
        <h3>这是一些内容</h3>
        <button type="button" @click="isShow = false">关闭模态框</button>
  </div>
  </section>
  </teleport>
  
  </div>
</template>

<script>
  import {ref} from "vue";
  
  export default {
    name: "ModalBox",
    setup() {
      let isShow = ref(false)
      return {isShow}
    }
  }
</script>

<style scoped>
  .mask {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
  }
  
  .modalBox {
    width: 300px;
    height: 300px;
    background-color: lightgreen;
    text-align: center;
    
    /* 相对于父元素来达到水平和垂直的居中 */
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
</style>

3.Suspense(实验阶段)

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

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

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

  • 使用步骤

  • 异步引入组件:

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

<template>
<div class="app">
  <h3>我是App组件</h3>
  <Suspense>
    <template v-slot:default>
      <Child/>
</template>
<template v-slot:fallback>
<h3>加载中。。。</h3>
</template>
</Suspense>
</div>

</template>

7、tips

1、关于过滤器

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

2、状态驱动的动态 CSS

<template>
<div>
  <h1 class="bg-red">11111</h1>
  </div>
</template>
<script>
  import { ref } from 'vue'
  export default {
    setup () {
      const color = ref('red')
      return {
        color
      }
    }
  }
</script>
<style>
  .bg-red {
    color: v-bind(color);
  }
</style>

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

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值