Vue3.0与Vue2.x的差异

写在前面的话

虽然开发一直在使用Vue,但是必须要承认在实际开发中对于Vue的使用不尽人意,很多地方并没有能够利用Vue的优点来简化开发,比如父子组件的传值和修改,多个组件总是用到同一个方法,对象数据的响应式处理等,所以回炉重看了Vue的文档,看完Vue2(以下简称v2)再看Vue3(以下简称v3),在实际开发目前还用不到v3,所以自己记一下关于v2和v3之间的差异,以便自己后期使用。

Vue3的更新

关于ref在v-for时的处理

在v2中,如果在一个v-for的DOM上使用了 ref,那么这个ref值自动生成一个数组,包含循环的所有的DOM对象,在v3中,不再自动生成这样的一个数组(refs的数组),而是可以选择绑定一个函数,如下

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

此时的setItemRef是一个函数,有一个参数是每一个DOM元素。

export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      if (el) {
        this.itemRefs.push(el)
      }
    }
  }
}

可以处理每一个DOM,增加了ref在v-for使用的多样性。

异步组件

  • defineAsyncComponent 是个新增的重点方法,处理异步组件
    = = 没看明白

Vue实例创建

v2
import Vue from 'vue'
new Vue({
	render:h => h(App)
}).$mount('#app')
v3
import Vue from 'vue'
Vue.createApp(App).mount('#app')

且v3支持链式调用
如下

Vue.createApp({})
  .component('SearchInput', SearchInputComponent)
  .directive('focus', FocusDirective)
  .use(LocalePlugin)

<template>上可使用v-for

因为<template>模板是被当作一个透明外壳渲染,既实际渲染时他是不存在的,所以在v2中不支持v-for在其上面的使用,而在v3中,支持v-for<template>上使用,且在渲染时<template>依旧渲染为无内容。

v-if和v-for优先级

v2中,v-for拥有更高的优先级;
v3中,v-if拥有更高的优先级,这意味着,v-if无权访问v-for里面的变量,但是v-for可以在<template>上使用,所以可以在<template>上使用v-for,在内部元素中使用v-if,同样可以实现依据列表项内容来选择是否渲染元素。

多事件处理器

事件处理器中可以有多个方法,这些方法由逗号分隔开,点击依次触发两个事件。

<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event), two($event)">
  Submit
</button>
// ...
methods: {
  one(event) {
    // first handler logic...
  },
  two(event) {
    // second handler logic...
  }
}

啊,,,这,我记得v2好像是没有的,而且这个方法的意义我还不知道在哪。。

键盘事件

键盘事件支持按键修饰符来绑定事件,在v2中,修饰符包括keyCode,在v3中,取消了这个修饰符。

Emitsproperty

之前确实没见过这个属性,可以检查组件抛出的所有事件

app.component('blog-post', {
  props: ['title'],
  emits: ['enlarge-text']
})

事件验证

app.component('custom-form', {
  emits: {
    // 没有验证
    click: null,

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

以布尔值来判断是否有效
tip:当在 emits 选项中定义了原生事件 (如 click) 时,将使用组件中的事件替代原生事件侦听器。
例子

// 父组件
<template>
  <div>{{count}}</div>
  // 把click原生事件传给子组件
  <test-component @click="sendMessage"/>
</template>
<script>
export default {
  name: 'App',
  data(){
    return {
      count:0
    }
  },
  methods:{
  // 接收子组件触发时传递的一个参数。
    sendMessage(count){
      this.count += count
      console.log('emit')
    }
  }
}
</script>

// 子组件
<template>
<div>
// 按钮双击触发click事件
  <button @dblclick="$emit('click',2)">点</button>
    <template v-for="item in 10" :key="item">
      <h2>标题</h2>
      <span>小标题</span>
      <div>内容</div>
    </template>
</div>
</template>
<script>
export default {
  namespace:'TestComponent',
  emits:['click']
};
</script>

通过上述实验代码,可以测试是单击/双击时父组件的sendMessage触发。测试可得是双击时,验证了组件中的事件替代了原生事件

v3支持v-model修饰符为自定义修饰符

添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件,

// 组件使用
<my-component v-model.capitalize="myText"></my-component>

// 组件定义
const app = Vue.createApp({
  data() {
    return {
      myText: ''
    }
  }
})

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
  template: `<input
    type="text"
    :value="modelValue"
    @input="emitValue">`
})
app.mount('#app')

对于带参数的 v-model 绑定,生成的 prop 名称将为 arg + “Modifiers”:

<my-component v-model:description.capitalize="myText"></my-component>
app.component('my-component', {
// v-model的参数值为description,所以生成的prop名称为descriptionModifiers
  props: ['description', 'descriptionModifiers'],
  emits: ['update:description'],
  template: `
    <input type="text" 
      :value="description"
      @input="$emit('update:description', $event.target.value)">
  `,
  created() {
    console.log(this.descriptionModifiers) // { capitalize: true }
  }
})

vue3的全局数据的绑定

// v2
import Vue from 'vue'
import axios from 'axios'
Vue.prototype.$axios = axios

// v3
let app = createApp(APP)
app.config.globalProperties.$axios = axios

组合式API

基础setup

新的setup组件选项在创建组件之前执行,一旦props被解析,并充当合成API的入口点。
由于在执行setup时,组件实例尚未创建,所以在setup中没有this。这意味着,除了props之外,你将无法访问组件声明的任何属性----本地状态、计算属性或方法。
setup方法应该是一个接受props和context的函数,我们从setup中返回的所有内容都将暴露给组件的其余部分(计算属性、方法、生命周期钩子等等)以及组件的模板。

export default {
  components: { componentA, componentB, componentC},
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup(props) {
    console.log(props) // { user: '' }

    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}

ref的响应式变量

在v3中,我们可以通过一个新的ref函数使任何响应式变量在任何地方起作用,

import {  ref  } from 'vue'
const counter = ref(0)
console.log(counter) //{value:0}
counter.value ++
console.log(counter.value) //1

ref接受参数并返回它包装在具有value属性的对象中,然后可以使用该属性访问或更改响应式变量的值。

在setup中注册生命周期钩子、watch、computed

生命周期钩子:钩子组合式API上的生命周期钩子与选项式API的名称相同,但前缀为on:即mounted看起来像onMounted
watch:我们也可以从Vue中导入的watch函数只想相同的操作,它接受三个参数:

  • 一个响应式引用或我们想要侦听的getter函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

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

每当counter被修改时,watch将触发病执行回调(第二个参数)
computed:从Vue中导入computed函数,computed函数返回一个作为第一个参数传递的getter类回调的输出的一个制度的响应式引用。为了访问新创建的计算变量的value,我们需要像使用ref一样使用.value属性。

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

为了确保我们的侦听器能够对prop所作出的更改做出反应,我们可以使用toRefs(从Vue中引入)
toRefs(props)

setup

使用setup函数,他接受两个参数

  1. props
  2. context
    props是第一个参数,setup函数中的props是响应式的,当传入新的prop时,它将被更新。
    warning: 你不能使用es6解构props,因为他会消除prop的响应性。
    如果需要解构,可以通过使用setup函数中的toRefs来完成操作:
import { toRefs } from 'vue'

setup(props) {
	const { title } = toRefs(props)
	// tilte 是ref
	console.log(title.value)
}

第二个参数是context,其是一个普通的js对象,他暴露三个组件的属性:
context.attrs // Attribute 非响应式对象
context.slots //插槽 非响应式对象
context.emit //触发事件 方法

context是一个普通的js对象,他不是响应式的,你可以安全的对context使用es6解构。

export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}

attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。attrs 和 slots 是非响应式的,如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。

访问组件的property

执行setup时,组件实例尚未被创建。因为只能访问一下property:

  • props
  • attrs
  • slots
  • emit
    你无法访问以下组件选项
  • data
  • computed
  • methods

在setup内部调用生命周期钩子

beforeCreate和created无法被调用。因为setup就是围绕这两个钩子写的,在这两个钩子写的任何内容都会在setup中被调用。

在setup内部使用provide/inject

使用provide

provide函数允许你通过两个参数定义property:

  1. name
  2. value
import { provide } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
  				//name      //value
    provide('location', 'North Pole')
    			//name
    provide('geolocation', {//value
      longitude: 90,
      latitude: 135
    })
  }
}
</script>
使用inject

inject函数有两个参数

  1. 要inject的property的名称
  2. 一个默认的值(可选)
<script>
import { inject } from 'vue'

export default {
  setup() {
  												//名称       //默认值
    const userLocation = inject('location', 'The Universe')
    												// 名称	
    const userGeolocation = inject('geolocation')
    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>
添加响应性

为了增加provide和inject值之间的响应性,我们可以在provide值时使用ref和reactive。

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    provide('location', location)
    provide('geolocation', geolocation)
  }
}
</script>
修改响应式property

尽可能在provide内保持响应式的更改。
有时我们需要在注入数据的组件内部更新inject的数据,在这种情况下,我们建议provide一个方法来负责改变响应式property。
最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly。

模板引用

ref和模板的联系 没看明白啊ToT。

生命周期钩子

app = Vue.createApp(options)
app.mount(el)
// 初始化events
beforeCreate
// 初始化注入和reactivity,实例已经创建,可以访问this
created
//编译模板
beforeMount
// 创建模板实例,可以访问DOM(v-if下的DOM除外)
mounted
// 当数据更新
beforeUpdate
// 虚拟DOM重渲染
updated
// 组件销毁,销毁前,依旧可以访问实例对象
beforeUnmount
// 组件销毁
unmounted

Teleport

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 teleport这个HTML到body标签上,它可以突破组件间的现实位置,直接挂载到任意DOM上,只要这个DOM是存在的。
如果teleport白哦韩Vue组件,则它仍将是teleport父组件的逻辑子组件,仍能从父组件中接收props。

在同一个目标上使用多个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>

深入响应式原理

Vue如何追踪变化

当把一个普通的js对象作为data选项传给应用或组件实例时,Vue会使用带有getter和setter的处理程序遍历其所有的书香并将其转换为Proxy,这是ES6仅有的特性,但是在v3中也使用了Object.defineProperty来支持IE。Proxy版本二更精简,同时提高了性能。
Proxy是一个包含另一个对象或者函数并允许你对其进行拦截的对象。
new Proxy(target,handler)
在hander中,有get和set函数,分别用来返回和修改函数值,在内容发生变化时,我们在名为track的函数中处理这些值,为此,我们将通过触发trigger函数来更改新的值(其实我没懂= =)

响应性基础

声明响应式状态

import { reactive } from 'vue'
const state = reactive({
	count:0
})

reactive相当于Vue2.x中的Vue.observable() API,其返回一个响应式的对象状态。该响应式转换时“深度转换”----- 它会影响嵌套对象传递的所有属性。
当响应式状态改变时视图回自动更新,这就是Vue响应式系统的本质。当组件中的data()返回一个对象的时候,他在内部由reactive()成为其响应式对象。模板会被编译成能够使用这些响应式属性的渲染函数。

创建独立的响应式值作为refs

import { ref } from 'vue'
const count = ref(0)

ref会返回一个可变的响应式对象,该对象作为他的内部值----一个响应式的引用,此对象只包含一个名为value的property:

import { ref } from 'vue'
const count = ref(0)
ref展开

当ref作为渲染上下文(setup中返回的对象)上的属性返回并可以在模板中被访问时,他将自动展开为内部值,不需要在模板中追加.value.

访问响应式对象

当ref作为响应式对象的属性被访问或更改时,为使其行为类似于普通属性,它会自动展开内部值:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

ref展开仅发生在被响应式Object嵌套的时候,当从Array或者原生集合类型如Map访问ref时,不会进行展开。

响应式状态解构
import { reactive } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book

如上,遗憾的是,解构的两个属性的响应式都会丢失,
那么,将响应式对象转换为一组ref,响应式关联将被保留。

import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'

使用readonly防止更改响应式对象

创建一个只读的proxy对象

import { reactive, readonly } from 'vue'

const original = reactive({ count: 0 })

const copy = readonly(original)

// 通过 original 修改 count,将会触发依赖 copy 的侦听器

original.count++

// 通过 copy 修改 count,将导致失败并出现警告
copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."

响应式计算和侦听

watchEffect

为了根据响应式状态自动应用和重新应用副作用,物品们可以使用watchEffect方法,它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)
停止侦听

当watchEffect在组件的setup函数或生命周期钩子被调用时,侦听器会被连接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显示调用返回值以停止侦听:

const stop = watchEffect(() => {
  /* ... */
})

// later
stop()

v3处理边界情况少了很多鸭

记住规则

  • 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值