vue3组合api(composition)

composition api

composition api为vue应用提供更好的逻辑复用和代码组织。

<template>
  <div>
    <p>counter: {
   
   {counter}}</p>
    <p>doubleCounter: {
   
   {doubleCounter}}</p>
    <p ref="desc"></p>
  </div>
</template>

<script>
import {
  reactive,
  computed,
  watch,
  ref,
  toRefs,
  onMounted,
  onUnmounted,
} from "vue";

export default {
  setup() {
    const data = reactive({
      counter: 1,
      doubleCounter: computed(() => data.counter * 2),
    });

    let timer

    onMounted(() => {
      timer = setInterval(() => {
        data.counter++
      }, 1000);
    })

    onUnmounted(() => {
      clearInterval(timer)
    })

    const desc = ref(null)

    watch(()=>data.counter, (val,oldVal)=>{
      // console.log(`counter change from ${oldVal} to ${val}`);
      desc.value.textContent = `counter change from ${oldVal} to ${val}`
    })
    
    return {...toRefs(data), desc};
  },
};
</script>

setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。

使用setup时,它接受两个参数:

  1. props: 组件传入的属性
  2. context

setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。 错误代码示例, 这段代码会让 props 不再支持响应式

// demo.vue
export default defineComponent ({
    setup(props, context) {
        const { name } = props
        console.log(name)
    },
})

setup接受的第二个参数context,context提供了最常用的三个属性:attrs、slot 和emit,分别对应 Vue2.x 中的 a t t r 属 性 、 s l o t 插 槽 和 attr属性、slot插槽 和 attrslotemit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

生命周期

image

Vue3.0 新增了setup,这个在前面我们也详细说了, 然后是将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个mount和unmount的过程。其他 Vue2 中的生命周期仍然保留。
上边生命周期图中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如

image

beforeCreate和created被setup替换了(但是 Vue3 中你仍然可以使用, 因为 Vue3 是向下兼容的, 也就是你实际使用的是 vue2 的)。其次,钩子命名都增加了on; Vue3.x 还新增用于调试的钩子函数onRenderTriggered和onRenderTricked

import {  defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
  onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked,
  onRenderTriggered} from "vue";
export default defineComponent({
  //  beforeCreate和created是vue2的 
  beforeCreate() {
    console.log("------beforeCreate-----");
  },
  created() {
    console.log("------created-----");
  },
  setup() {
    console.log("------setup-----");
    // vue3.x生命周期写在setup中 
    onBeforeMount(() => {
      console.log("------onBeforeMount-----");
    });
    onMounted(() => {
      console.log("------onMounted-----");
    });
    // 调试哪些数据发生了变化
    onRenderTriggered((event) => {
      console.log("------onRenderTriggered-----", event)
    })
  }
});

reactive、ref 与 toRefs

Vue3.x 可以使用reactive和ref来进行数据定义。

通过ref()定义,需通过xx.value来获取值。reactive不能代理基本类型,例如字符串、数字、boolean 等。

toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象。

Teleport

传送门组件提供一种简洁的方式可以指定它里面内容的父元素。

<template>
  <button @click="modalOpen = true">
    弹出一个全屏模态窗口</button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        这是一个模态窗口!
        我的父元素是"body"!
        <button @click="modalOpen = false">Close</button>
      </div>
    </div>
  </teleport>
</template>

<script>
export default {
  data() {
    return {
      modalOpen: true
    }
  },
};
</script>

<style scoped>
.modal {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: white;
  width: 300px;
  height: 300px;
  padding: 5px;
}
</style>

Fragments

vue3中组件可以拥有多个根。

<template>
  <div id='teleport-box'></div>
  <img alt="Vue logo" src="./assets/logo.png" />
  <Bin v-model="counter" />
  <Tel />
</template>

Emits Component Option

Vue 3 目前提供一个 emits选项,和现有的props选项类似。这个选项可以用来定义组件可以向其父组件触发的事件。

vue3中组件发送的自定义事件需要定义在emits选项中:

  • 原生事件会触发两次,比如click
  • 更好的指示组件工作方式
  • 对象形式事件校验

Global API 改为应用程序实例调用

vue2中有很多全局api可以改变vue的行为,比如Vue.component等。这导致一些问题:

vue2没有app概念,new Vue()得到的根实例被作为app,这样的话所有创建的根实例是共享相同的全局配置,这在测试时会污染其他测试用例,导致测试变得困难。
全局配置也导致没有办法在单页面创建不同全局配置的多个app实例。

vue3中使用createApp返回app实例,由它暴露一系列全局api

import { createApp } from 'vue'
const app = createApp({})
	.component('comp', { render: () => h('div', 'i am comp') })
  .mount('#app')

2.x Global API 3.x Instance API (app)
Vue.config app.config
Vue.config.productionTip removed
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.filter removed

Global and internal APIs重构为可做摇树优化

vue2中不少global-api是作为静态函数直接挂在构造函数上的,例如Vue.nextTick(),如果我们从未在代码中用过它们,就会形成所谓的dead code,这类global-api造成的dead code无法使用webpack的tree-shaking排除掉。

import Vue from 'vue'

Vue.nextTick(() => {
  // something something DOM-related
})

vue3中做了相应的变化,将它们抽取成为独立函数,这样打包工具的摇树优化可以将这些dead code排除掉。

import { nextTick } from 'vue'

nextTick(() => {
  // something something DOM-related
})

受影响api:

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile (仅完整构建版本)
  • Vue.set (仅兼容构建版本)
  • Vue.delete (仅兼容构建版本)

v-model 参数

默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:

<my-component v-model:title="bookTitle"></my-component>

子组件将需要一个 title prop 并发出 update:title 要同步的事件:

app.component('my-component', {
  props: {
    title: String
  },
  emits: ['update:title'],
  template: `
    <input
      type="text"
      :value="title"
      @input="$emit('update:title', $event.target.value)">
  `
})

多个 v-model 绑定

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>
app.component('user-name', {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName'],
  template: `
    <input 
      type="text"
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">

    <input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName', $event.target.value)">
  `
})

model选项和v-bind的sync 修饰符被移除,统一为v-model参数形式

vue2中.sync和v-model功能有重叠,容易混淆,vue3做了统一。

<div id="app">
  <h3>{
   
   {data}}</h3>    
  <comp v-model="data"></comp>
</div>

app.component('comp', {
  template: `
    <div @click="$emit('update:modelValue', 'new value')">
    	i am comp, {
   
   {modelValue}}
    </div>
	`,
  props: ['modelValue'],
})

渲染函数API修改

渲染函数变得更简单好用了,修改主要有以下几点:

不再传入h函数,需要我们手动导入;拍平的props结构。scopedSlots删掉了,统一到slots

import {h} from 'vue'

render() {
  const emit = this.$emit
  const onclick = this.onclick
  return h('div', [
    h('div', {
      onClick() {
      	emit('update:modelValue', 'new value')
    	}}, 
      `i am comp, ${this.modelValue}`
    ),
    h('button', {
      onClick(){
      	onclick()
    	}}, 
      'buty it!'
    )
  ])
},

异步组件要求使用defineAsyncComponent 方法创建

由于vue3中函数式组件必须定义为纯函数,异步组件定义时有如下变化:

  • 必须明确使用defineAsyncComponent包裹
  • component 选项重命名为 loader
  • Loader 函数不在接收 resolve and reject 且必须返回一个Promise
import { defineAsyncComponent } from 'vue'

// 不带配置的异步组件
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

带配置的异步组件,loader选项是以前的component

import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// 待配置的异步组件
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  // 加载异步组件时要使用的组件
  loadingComponent: LoadingComponent,
  // 加载失败时要使用的组件
  errorComponent: ErrorComponent,
})

自定义指令API和组件保持一致

vue3中指令api和组件保持一致,具体表现在:

  • bind → beforeMount
  • inserted → mounted
  • beforeUpdate: new! 元素自身更新前调用, 和组件生命周期钩子很像
  • update → removed! 和updated基本相同,因此被移除之,使用updated代替。
  • componentUpdated → updated
  • beforeUnmount new! 和组件生命周期钩子相似, 元素将要被移除之前调用。
  • unbind → unmounted
const app = Vue.createApp({})

app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

<p v-highlight="yellow">Highlight this text bright yellow</p>

组件watch选项和实例方法$watch不再支持点分隔符字符串路径

以.分割的表达式不再被watch和watch支持,可以使用计算函数作为watch支持,可以使用计算函数作为watch参数实现。

this.$watch(() => this.foo.bar, (v1, v2) => {
  console.log(this.foo.bar)
})

keyCode 作为 v-on 修饰符被移除

vue2中可以使用keyCode指代某个按键,vue3不再支持。

<!-- keyCode方式不再被支持 -->
<input v-on:keyup.13="submit" />

<!-- 只能使用alias方式 -->
<input v-on:keyup.enter="submit" />

on,off and $once 移除

上述3个方法被认为不应该由vue提供,因此被移除了,可以使用其他三方库实现。

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>

// 创建emitter
const emitter = mitt()

// 发送事件
emitter.emit('foo', 'foooooooo')

// 监听事件
emitter.on('foo', msg => console.log(msg))

Filters移除

vue3中移除了过滤器,请调用方法或者计算属性代替。

keyCode方式不再被支持 -->

### on,off and $once 移除

上述3个方法被认为不应该由vue提供,因此被移除了,可以使用其他三方库实现。



// 创建emitter
const emitter = mitt()

// 发送事件
emitter.emit(‘foo’, ‘foooooooo’)

// 监听事件
emitter.on(‘foo’, msg => console.log(msg))

### Filters移除
vue3中移除了过滤器,请调用方法或者计算属性代替。





猜你喜欢

转载自blog.csdn.net/lyf976229437/article/details/122108638
今日推荐