vue2过渡vue3技术差异点指南

基础点

reactive()

定义响应式变量(仅仅引用类型有效:对象数组map,set):reactive(),类似于data中return的数据

例子:

js 复制代码
import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({ count: 0 })

    function increment() {
      state.count++
    }

    // 变量和方法 都需要return出来
    return {
      state,
      increment
    }
  }
}

另外 使用setup() 繁琐,可以用 直接包裹 无需return

reactive 默认是深层响应式,即obj.nested.count 对象属性的属性也是可以监听到的。 如果不想被监听可以使用 浅层响应式对象shallowReactive()

https://cn.vuejs.org/api/reactivity-advanced.html#shallowreactive

局限:

仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。

因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地"替换"一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

js 复制代码
let state = reactive({ count: 0 })

// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })

同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:

js 复制代码
const state = reactive({ count: 0 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++

// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)

ref()

任何类型

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象,使用的时候 用.value取值。但是在template中会被自动解包,直接用就行.

但是若定义ref的时候不是定义在顶层,而是const object = { foo: ref(1) } 这种,在模版中{{ object.foo + 1 }}渲染出来的是ref对象[object Object].

js 复制代码
const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
vue 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }} <!-- 无需 .value -->
  </button>
</template>

computed

当我们的模版逻辑复杂的时候,利用计算属性返回,computed()方法接受一个getter,返回一个ref,ref在模版中自动解包,无需使用.value

我们发现使用方法和computed的返回结果是一样的,差异是:

计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味响应不更新时不会重复执行getter函数。而方法调用在重渲染时会再次执行函数

ps:若在computed中返回Date.new() 是永远不会更新的,因为Date.new()不是响应式依赖

解释:所谓响应式依赖 在vue3中可以理解为用reactive(),ref()定义的,在vue2中就是data中return出来的。而不是响应式的依赖,可以理解为在vue3中没有通过这两种定义出来的,vue2中就是在方法中直接定义在this上的,而没有在data中声明并return出来的。

为什么使用computed:

假如我们有大量的数组计算逻辑依赖于某个list,不用缓存的话,会多次执行getter,耗费性能

默认只读的:

若尝试修改computed需要同时提供getter和setter

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

ps:

在此getter中不应做异步请求或更改dom

避免直接修改计算属性值,可以将它看作一个"临时快照",每当源状态发生变化的时候就会创建新的快照

事件处理

在内联事件处理器中访问事件参数:

可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数:

vue 复制代码
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>
js 复制代码
function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault()
  }
  alert(message)
}

v-on 事件修饰符

vue 复制代码
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>

<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>

<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>

<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>

ps:使用修饰符时需要注意调用顺序

请勿同时使用 .passive 和 .prevent,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。如果你这么做了,则 .prevent 会被忽略,并且浏览器会抛出警告。

生命周期

使用方法:

vue 复制代码
<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

具体生命周期api:

https://cn.vuejs.org/api/composition-api-lifecycle.html#onserverprefetch

watch

js 复制代码
const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

注意:

不能只监听响应式对象的属性值,而是用一个返回该属性的getter函数

js 复制代码
const obj = reactive({ count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

// 正确: 提供一个 getter 函数
watch(() => obj.count, (count) => {
    console.log(`count is: ${count}`)
  }
)

深层监听

监听响应式对象时,该回调函数在所有嵌套的变更时都会被触发,也就是对象上的属性变化都会被监听到,如果你只想监听某个对象当前层级的时候,可以用getter()函数的方式,此时只有整个对象被替换时才触发回调,属性变化时不会被监听,但是可以通过加deep:true监听到

js 复制代码
const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
})

obj.count++

watch(
  () => state.someObject,
  () => {
    // 仅当 state.someObject 被替换时触发
  }
)
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

ps:深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能

watchEffect()

watchEffect会自动跟踪回调的响应式依赖。

todoId既作为源又在回调中,利用watchEffect,在执行期间,会自动追踪todoId.value作为依赖,todoId.value变化时,回调会再次执行,使用watchEffect我们不需要明确传递todoId作为源值,

好处:

不必手动维护依赖列表。

当需要监听一个嵌套数据结构中的多个属性时,它只跟踪回调中被使用的属性,而不是递归的跟踪所有属性

js 复制代码
const todoId = ref(1)
const data = ref(null)

watch(todoId, async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
}, { immediate: true })
//可以改写成如下
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

触发时机

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect():

js 复制代码
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

注意:

尽量不要使用异步监听,需要手动停止,否则造成内存泄漏。如果需要等待异步数据,可以用条件判断监听

js 复制代码
// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
  if (data.value) {
    // 数据加载后执行某些操作...
  }
})

组件使用

props

<script setup> 中 通过defineProps({})

vue 复制代码
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

如果不使用<script setup> 通过props传入,并传给setup()

js 复制代码
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

事件传递

defineEmits(['xxx'])在<script setup> 和单独的setup中两种形态

vue 复制代码
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>
js 复制代码
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

动态组件 is

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持"存活"的状态。

vue 复制代码
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
相关推荐
码爸15 分钟前
flink doris批量sink
java·前端·flink
深情废杨杨17 分钟前
前端vue-父传子
前端·javascript·vue.js
工业互联网专业1 小时前
毕业设计选题:基于springboot+vue+uniapp的驾校报名小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
J不A秃V头A1 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂2 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码3 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!4 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr4 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选