Vue3新特性&重大改变(部分)

644 阅读5分钟

Vue3新特性&重大改变(部分)

参考:v3.vuejs.org/guide/migra…

1. 新特性

1.1. Composition API

在Vue2中,开发者通过使用Vue Option API,将组件逻辑分布在不同的组件options当中(data,computed,methods,watch等)。当组件变得 越来越大,组件的职责也越来越多,我们不得不将这些职责的代码拆分在组件options当中,假如你想了解某一个职责,阅读代码时需要在不同 options之间“跳跃”。这严重降低了代码的可读性和可维护性。

Vue3带来了Composition API来解决这个问题,允许开发者根据代码职责来组织代码。

1.1.1. setup 组件选项

通过setup组件选项,我们可以声明一个函数,它会在组件被创建前执行,它是composition api的入口。

setup接受两个参数:

  • props
  • context
    • 一个普通js对象
    • 暴露了3个组件属性
// MyBook.vue

export default {
  setup(props, context) {
    // Attributes (Non-reactive object)
    console.log(context.attrs)

    // Slots (Non-reactive object)
    console.log(context.slots)

    // Emit Events (Method)
    console.log(context.emit)
  }
}

setup返回一个对象,此对象可以被组件访问

实例

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
  	repositories.value = await fetchUserRepositories(props.user)
  }

  return {
    repositories, // 返回的reactive变量能够直接在模版中以及其他组件选项中访问
    getUserRepositories // 返回的函数相当于methods
  }
}

*由于setup在执行时组件尚未创建,无法访问this。除了props,剩下任何组件选项都无法访问。

1.1.2. 使用ref声明响应式变量

在setup当中,我们可以通过ref来声明响应式变量:

实例

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
  	repositories.value = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories
  }
}

当我们调用getUserRepositories后,repositories会被改变,并相应更新模版。

*setup内部读取/修改响应式变量时,需要通过value属性。

1.1.3. setup中注册生命周期钩子

Vue提供了注册生命周期钩子的接口。on+钩子名

*不需要beforeCreate和created生命周期钩子!因为setup的执行时机与这两个钩子类似,不需要专门声明这两个钩子。

实例

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
  	repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

1.1.4. setup中声明watcher

Vue也提供了注册watcher的接口。

实例

import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
	console.log('The new counter value is: ' + counter.value)
})

watch接受3个参数:

  • 一个响应式引用或一个getter函数
// watching a getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
  	/* ... */
  }
)

// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
	/* ... */
})
  • 回调
  • 配置对象(可选)
    • deep
    • immediate
    • flush

1.1.5. setup中声明computed函数

computed返回一个read-only的响应式引用:

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

1.2. Teleport

用于将组件模版添加到DOM树的任意位置。

最常见的例子就是全屏的modal。当某个嵌套较深的子组件中存在一个全屏modal时,该modal不得不依据离自己最近的position不为static的父 组件进行定位,然而我们的预期是modal基于body进行定位。 Vue2的解决办法是在mounted钩子中手动将modal对应的DOM元素插入到body,但在Vue3中我们可以利用Teleport特性,优雅地将指定模版添加到 DOM树的任意位置。

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
    	Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal!
          (My parent is "body")
          <button @click="modalOpen = false">Close</button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return {
      modalOpen: false
    }
  }
})

利用teleport标签告诉Vue,将以下模版编译为的DOM元素“传送”到body元素下。

结果就是modal将会成为body的子元素。

1.2.1. 用teleport包裹一个Vue组件

当teleport中包含Vue组件时,虽然该组件会被“传送”到DOM树的其他位置,但该组件仍然会和当前组件保持逻辑父子组件的关系!

1.2.2. 多个teleport指向同一个目标

被传送的元素会按照teleport声明的顺序,被依次渲染到目标元素下:

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

1.2.3. API

teleport标签接受2个props:

// 必须是有效的query selector
to: {
  type: String,
  required: true,
}
// 用于禁用传送功能,包裹的元素会在原地渲染。允许我们通过判断某个条件来开启传送
disabled: {
  type: Boolean,
  required: false,
}

1.3. Fragments

在Vue2中,组件只能声明一个根节点,否则会报错,通常我们会使用一个额外的div去包裹所有子组件,然而在Vue3中,组件支持多个根节点!

1.3.1. Vue2语法

<!-- Layout.vue -->
<template>
  <div>
    <header>...</header>
    	<main>...</main>
    <footer>...</footer>
  </div>
</template>

1.3.2. Vue3语法

<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

1.4. Emits Component Option

类似props option,Vue3加入了emits option,用于在组件中声明该组件会触发的事件信息,主要有两个目的:

1.方便开发者了解当前组件支持的事件,一目了然

*当emits option中声明了native event时,将会使用组件触发的事件,而不是native event

app.component('custom-form', {
  emits: ['inFocus', 'submit']
})

2.支持对事件参数进行校验

app.component('custom-form', {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
      	return true
      } else {
      	console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})

1.5. SFC style

1.5.1. 深度伪元素

Vue2中的深度选择器(>>> 和 /deep/)被废弃 我们迎来了新的深度选择器:

<style scoped>
/* deep selectors */
::v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
</style>

1.5.2. 插槽伪元素

父元素通过slot传人的元素默认不会受子元素样式的影响,但如果子元素希望修改slot元素,可以利用新的::v-slotted伪元素来选择:

<style scoped>
/* targeting slot content */
::v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
</style>

1.5.3. 全局伪元素

我们可以通过新的::v-global()伪元素,在任意组件的标签中声明全局样式:

<style scoped>
/* one-off global rule */
::v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>

2. 重大改变(部分)

2.1. 模版指令

2.1.1. v-model

2.1.1.1. 改动

我们知道v-model是一个语法糖,在Vue2中的转化关系为:

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

在Vue3中,对默认的props名称做了修改,转化关系为 :

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
2.1.1.2. 改动

Vue2中我们可以利用.sync modifier,允许子组件通知父组件修改props:

// 子组件
this.$emit('update:title', newValue)
<!-- 父组件 -->
<ChildComponent :title.sync="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

Vue3中废弃了.sync modifier,但允许我们传递一个参数给v-model,转化关系为: *这里的title是prop name,pageTitle是需要绑定的数据 *当我们声明了参数后效果与.sync相同

<ChildComponent v-model:title="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
2.1.1.3. 改动

Vue2中提供了几种v-model的modifier,例如trim、number等,Vue3支持我们声明自定义modifier

2.2. key

当v-for声明在template上时,key也声明在template上(而不是在子元素上)

2.3. v-if和v-for在相同元素上的使用

在Vue2中,当v-if与v-for在相同元素上使用时,v-for具有更高的优先级

在Vue3中,当v-if与v-for在相同元素上使用时,v-if具有更高的优先级

**最好避免将v-if与v-for在相同元素上使用

2.4. v-bind=“object”的优先级

在Vue2中,单独绑定的prop具有更高的优先级:

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>

在Vue3中,props声明的顺序将影响props合并的结果:

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>

2.5. v-on:event.native modifier被移除

在Vue2中,.native modifier用于监听浏览器原生事件,而不是组件通过emit触发的事件:

<my-component
  v-on:close="handleComponentEvent"
  v-on:click.native="handleNativeClickEvent"
/>

在Vue3中,因为组件添加了emit option,当我们没有在emit option中声明某个事件,则事件监听器默认监听原生事件:

<!-- 子组件 -->
<script>
export default {
  emits: ['close']
}
</script>
<!-- 父组件 -->
<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>

2.6. v-for中的ref

在Vue2中,当ref属性与v-for指令同时出现时,相应的this.$refs.elementList属性为一个包含多个ref的数组:

<div v-for="item in list" ref="elementList"></div>

在Vue3中,当ref属性与v-for指令同时出现时,vue不再自动生成一个包含多个ref的数组,但我们可以传递一个函数给ref属性,用于达成相 同的效果:

<div v-for="item in list" :ref="setItemRef"></div>
export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      if (el) {
      	this.itemRefs.push(el)
      }
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}

*itemRefs不一定是数组,可以是其他数据类型