从Vue2到Vue3【一】——Composition API(第一章)

系列文章目录

内容 链接
从Vue2到Vue3【零】 Vue3简介及创建
从Vue2到Vue3【一】 Composition API(第一章)

文章目录

  • 系列文章目录
  • 前言
  • 一、 composition API 与 options API
  • 二、setup
  • 三、ref
  • 四、reactive
  • 五、reactive与ref对比
  • 六、setup注意事项
  • 七、script setup语法
  • 八、计算属性与监听
    • 8.1 computed
    • 8.2 watch
    • 8.3 watchEffect
  • 总结


前言

Vue3作为Vue.js框架的最新版本,引入了许多令人激动的新特性和改进。其中,组合式API是Vue3最引人注目的特性之一。在本文中,我们将深入探讨Vue3的组合式API,并探索其在开发过程中的优势和用法。无论是新手还是有经验的Vue开发者,通过学习如何使用组合式API,我们都能更高效地构建复杂的应用程序,提高代码的可维护性和重用性

一、 composition API 与 options API

  • vue2 采用的是 options API
    (1) 优点:易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)
    (2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
    (3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护
  • vue3 新增的是 composition API
    (1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
    (2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
    (3) 大大的提升了 代码可读性 和 可维护性

案例 鼠标移动显示鼠标坐标 x, y
vue2 options API 传统做法

<template>
  <div>当前鼠标位置div>
  <div>x: {{ mouse.x }}div>
  <div>y: {{ mouse.y }}div>
  <div>当前点击次数:{{ count }}div>
  <button @click="add">点击button>
template>

<script>
export default {
  // vue2 中采用的是 options API
  // 常见的配置项: data created methods watch computed components
  data() {
    return {
      mouse: {
        x: 0,
        y: 0,
      },
      count: 0,
    }
  },
  mounted() {
    document.addEventListener('mousemove', this.move)
  },
  methods: {
    move(e) {
      this.mouse.x = e.pageX
      this.mouse.y = e.pageY
    },
    add() {
      this.count++
    },
  },
  destroyed() {
    document.removeEventListener('mousemove', this.move)
  },
}
script>

vue3 composition API 组合api做法

<template>
  <div>当前鼠标位置div>
  <div>x: {{ mouse.x }}div>
  <div>y: {{ mouse.y }}div>
  <div>当前点击次数:{{ count }}div>
  <button @click="add">点击button>
template>

<script>
import { onMounted, onUnmounted, reactive, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const add = () => {
      count.value++
    }

    const mouse = reactive({
      x: 0,
      y: 0,
    })

    const move = (e) => {
      mouse.x = e.pageX
      mouse.y = e.pageY
    }
    onMounted(() => {
      document.addEventListener('mousemove', move)
    })
    onUnmounted(() => {
      document.removeEventListener('mousemove', move)
    })
    return {
      count,
      add,
      mouse,
    }
  },
}
script>

可以继续抽离,把实现逻辑的具体代码抽离封装起来,方便复用和维护

function useMouse() {
  const mouse = reactive({
    x: 0,
    y: 0,
  })
  const move = (e) => {
    mouse.x = e.pageX
    mouse.y = e.pageY
  }
  onMounted(() => {
    document.addEventListener('mousemove', move)
  })
  onUnmounted(() => {
    document.removeEventListener('mousemove', move)
  })
  return mouse
}

function useCount() {
  const count = ref(0)
  const add = () => {
    count.value++
  }
  return {
    count,
    add,
  }
}

把抽离封装好的函数放到src目录下的hooks文件夹
这样做有什么好处呢?
基于逻辑功能组织代码,方便复用与维护

二、setup

  • Vue3.0中一个新的配置项,值为一个函数,是composition API的起点(舞台)
  • 从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
  • setup 中不能使用 this, this 指向 undefined
  • 在模版中需要使用的数据和函数,需要在 setup 中返回
  • 组件中所用到的:数据、方法等等,均要配置在setup中
<template>
  <div class="container">
    <h1 @click="say()">{{msg}}h1>
  div>
template>

<script>
export default {
  setup () {
    console.log('setup执行了')  //setup先执行
    console.log(this)  // undefined
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
	
	// 需要使用的一定要导出
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了') 
    console.log(this)
  }
}
script>
  • 注意点

    • 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed…)
      • 如果有重名, setup优先
    • setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合

三、ref

  • 定义一个响应式数据(可以是基本类型、也可以是对象类型)
<template>
  <div>{{ money }}div>
  <button @click="money++">改值button>
template>

<script>
import { reactive, ref } from 'vue'
export default {
  setup() {
    let money = ref(100)
    money.value++
    return {
      money
    }
  }
}
script>
  • 使用

    • 操作数据 (xxx.value)

    • 使用数据 ( {{xxx}}} )

  • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。

  • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

四、reactive

  • 传入一个复杂数据类型,将复杂类型数据, 转换成响应式数据 (返回该对象的响应式代理)
<template>
  <div>{{ obj.name }}div>
  <div>{{ obj.age }}div>
  <button @click="obj.name = 'ls'">改值button>
template>

<script>
import { reactive } from 'vue'

export default {
  setup () {
    // 1. setup 需要返回值, 返回的值才能在模板中使用
    // 2. 默认的普通的值不是响应式的, 需要用 reactive 函数
    const obj = reactive({
      name: 'zs',
      age: 18
    })

    return {
      obj
    }
  }
}
script>
  • 使用

    • 操作数据 (xxx)

    • 使用数据 ( {{xxx}}} )

  • reactive定义的响应式数据是“深层次的”

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据

五、reactive与ref对比

使用这两者函数之前,一定要先引入

import {reactive, ref} from 'vue'
  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

六、setup注意事项

  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

父组件App

<template>
	<Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
	    // 具名插槽
		<template v-slot:qwe>
			<span>尚硅谷span>
		template>
		<template v-slot:asd>
			<span>尚硅谷span>
		template>
	Demo>
template>

<script>
	import Demo from './components/Demo'
	export default {
		name: 'App',
		components:{Demo},
		setup(){
			function showHelloMsg(value){
				alert(`你好啊,你触发了hello事件,我收到的参数是:${value}`)
			}
			return {
				showHelloMsg
			}
		}
	}
script>

子组件Demo

<template>
	<h1>一个人的信息h1>
	<h2>姓名:{{person.name}}h2>
	<h2>年龄:{{person.age}}h2>
	<button @click="test">测试触发一下Demo组件的Hello事件button>
template>

<script>
	import {reactive} from 'vue'
	export default {
		name: 'Demo',
		props:['msg','school'],
		emits:['hello'],
		setup(props,context){
			// console.log('---setup---',props)
			// console.log('---setup---',context)
			// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
			// console.log('---setup---',context.emit) //触发自定义事件的。
			console.log('---setup---',context.slots) //插槽
			//数据
			let person = reactive({
				name:'张三',
				age:18
			})

			//方法
			function test(){
				context.emit('hello',666)
			}

			//返回一个对象(常用)
			return {
				person,
				test
			}
		}
	}
script>


七、script setup语法

从vue3.2开始,支持setup语法糖。
script setup是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁

顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用

<template>
  <div>
    <h3>根组件h3>
    <div>点击次数:{{ count }}div>
    <button @click="add">点击修改button>
  div>
template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
const add = () => {
  count.value++
}

// 不再需要return {} 直接可以读取数据
script>

使用script setup 语法 改写鼠标坐标案例

<template>
  <div>当前鼠标位置div>
  <div>x: {{ mouse.x }}div>
  <div>y: {{ mouse.y }}div>
  <div>当前点击次数:{{ count }}div>
  <button @click="add">点击button>
template>

<script setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue'
const count = ref(0)
const add = () => {
  count.value++
}
const mouse = reactive({
  x: 0,
  y: 0,
})
const move = (e) => {
  mouse.x = e.pageX
  mouse.y = e.pageY
}
onMounted(() => {
  document.addEventListener('mousemove', move)
})
onUnmounted(() => {
  document.removeEventListener('mousemove', move)
})
script>

八、计算属性与监听

8.1 computed

computed函数调用时, 要接收一个处理函数, 处理函数中, 需要返回计算属性的值

<template>
  <div>我今年的年纪 <input type="text" v-model="age" />div>
  <div>我明年的年龄 {{ nextAge }}div>
  <div>我后年的年龄 <input type="text" v-model="nextAge2" />div>
template>

<script setup>
import { computed, ref } from 'vue'
const age = ref(10)
// 不带set的计算属性 (简写)
const nextAge = computed(() => {
  return +age.value + 1  // 变量前的加号是转为number类型
})

// 带set的计算属性 (完整写法)
const nextAge2 = computed({
  get() {
    return +age.value + 2
  },
  set(value) {
    age.value = value - 2
  },
})
script>

8.2 watch

watch监视, 接收三个参数

  • 参数1: 监视的数据源
  • 参数2: 回调函数
  • 参数3: 额外的配置 (如立即侦听、深度侦听)
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
	console.log('sum变化了',newValue,oldValue)
},{immediate:true})

//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
	console.log('sum或msg变化了',newValue,oldValue)
}) 

/* 情况三:监视reactive定义的响应式数据
			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
*/
watch(person,(newValue,oldValue)=>{
	console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效

//情况四:监视reactive定义的响应式数据中的某个属性 属性值是简单数据类型
watch(()=>person.age,(newValue,oldValue)=>{
	console.log('person的age变化了',newValue,oldValue)
},{immediate:true}) 

//情况五:监视reactive定义的响应式数据中的某些属性 属性值是简单数据类型
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true})

//特殊情况 监视reactive定义的响应式数据中的某些属性 属性值是引用数据类型
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是job对象,所以deep配置有效

当ref的值是一个复杂数据类型,需要深度监听

let person = ref({
	name:'张三',
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
})
//由于监听的ref的值是一个对象 所以要开启深度侦听
watch(person,(newValue,oldValue)=>{
	console.log('person的值变化了',newValue,oldValue)
},{deep:true})

8.3 watchEffect

不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性

//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})

总结

总的来说,Vue3的组合式API为我们提供了一种更灵活、更强大的开发方式。通过充分理解和应用组合式API,我们可以开发出更高效、更精简的Vue应用程序,为用户提供更好的体验。不论是对于新手还是有经验的Vue开发者来说,掌握组合式API都是一个重要的技能,值得我们去学习和掌握。

你可能感兴趣的:(从Vue2到Vue3,vue.js,前端,javascript)