vue3&composition Api

setup

  • setup 是 Vue3 中一个新的配置项,值为函数

  • 组件中使用的数据、方法等都要配置在 setup 中

  • setup 函数返回值:对象,函数等数据,可在模板中直接使用

setup 函数参数:props,context

  • props :自定义属性,组件外部传进来,且组件内部声明接收的属性(父传子)
  • context:上下文对象,也称之为是一个SetupContext,它里面包含三个属性
    • attrs: 所有非 prop 的 attribute,组件外部传进来,且组件内部没有声明接收的属性,相当于 this.$attrs
    • slot:父组件传递过来的插槽,相当于 this.$slots
    • emit:触发自定义事件,相当于 this.$emit
export default {
  name: 'App',
  props: ['title'],
  // 声明自定义事件,虽然不声明也能运行
  emits: ['changeCount'],
  
  setup(props, context) {
    let name = 'Vue3'
    function sayHello() {}
    function test() {
     // 创建自定义事件
      context.emit('changeCount', 888)
    }
    // 在 setup 函数 定义的值,函数
    // return 出去可以在模板上使用
    return {
      name,
      sayHello,
      test,
    }
  },
}

注意

  • setup 在 beforeCreate 钩子之前执行,this 为 undefined
  • setup 不要和 Vue2 配置混用。Vue2 的配置可以访问到 setup 的属性方法,反过来不行;如有重名,setup 优先
  • setup 不能是 async 函数,因为 async 函数返回的是 promise 不是对象,会导致模板无法访问属性方法
  • 若要返回 promise 实例,需要 Suspense 和异步组件的配合

定义响应式数据

ref 函数

  • 语法:const msg = ref('hello world')

  • 返回: ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值

  • ref 函数可以接收基本数据类型和引用数据类型

  • 原理:

    1. 基本类型数据的响应式还是靠 Object.defineProperty() 完成
    2. 对象类型数据使用 ES6 的 Proxy 实现响应式,Vue3 把相关操作封装在 reactive 函数中
    3. 按照之前的办法,对于对象数据,应该遍历每一层的属性添加 getter 、setter,但 Vue3 使用 Proxy 把内部数据一口气监测了
  • 注意:

    1. 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式
    2. 在模板中引入 ref 的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用

reactive函数

使用和 ref 大致一样

reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型

ref 和reactive

  1. 定义数据:
  • ref 用于定义基本类型数据
  • reactive 用于定义对象或数组类型数据
  • ref 也可定义对象或数组类型数据,内部通过 reactive 转为代理对象
  • 一般使用 reactive 函数,可以把所有数据封装为一个对象
  1. 原理:
  • ref 通过 Object.defineProperty() 实现响应式
  • reactive 通过 Proxy 实现响应式,Reflect 操作源对象数据
  1. 使用角度:
  • ref 定义数据,访问数据需要 .value,模板中不需要
  • reactive 定义的数据,都不需要
  1. 开发中选择 reactive/ref :
  • 通过服务器的建议使用 ref
  • 本地的数据使用 reactive

readonly的使用

单项数据流

概念:所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解

简单理解:父组件传到子组件中的原始数据,不可以在子组件修改,为了避免混乱不清楚哪一个组件修改数据的,统一返回父组件修改

readonly

  • readonly: 让一个响应式数据变为只读的(深只读)
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)
  • 应用场景: 不希望数据被修改时,如你用了别人的响应式数据,但是别人不希望你修改时
setup() {
  let sum = ref(0)
  let person = reactive({...})

  sum = readonly(sum)
  person = shallowReadonly(person)

  return {
    sum,
    person
  }
}

判断 Reactive 的 API

  • isProxy
    检查对象是否是由 reactive 或 readonly创建的 proxy。

  • isReactive
    检查对象是否是由 reactive创建的响应式代理:

    如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;

  • isReadonly
    检查对象是否是由 readonly 创建的只读代理。

  • toRaw
    返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。

  • shallowReactive
    创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。

  • shallowReadonly
    创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。

toRefs

使用ES6的解构语法,对 reactive 返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修 reactive返回的state对象,数据都不再是响应式的

使用 toRef 可以将reactive返回的对象中的属性都转成 ref

如果我们只希望转换一个reactive对象中的属性为 ref , 那么可以使用 toRef的方法

const state = reactive({
  name: 'jay',
  age: 18
})
// 使用 toRefs/toRef 数据成响应式
// toRefs
const {name, age} = toRefs(state)
// toRef
const name = toRef(state,'name')

setup中不能使用this

  • this 并没有指向当前组件实例;
  • 并且在 setup 被调用之前,data、computed、methods 等都没有被解析;
  • 所以无法在 setup 中获取 this;

computed计算属性

  1. (简写)接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
  2. (完整)接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象
import { reactive, computed } from 'vue'

export default {
  setup() {
    let person = reactive({
      firtName: 'jay',
      lastName: 'FT'
    })
    
    // 简写模式
    const fullName = computed(()=>{
      return firtName.value + '' + lastName.value
    })
    
    // 完整写法
    const fullname = computed({
      get: ()=> {
        return firtName.value + ' ' + lastName.value
      },
      set: newValue => {
        const names = newValue.split(' ')
        firtName.value = names[0]
        lastName.value = names[1]
      }
    })
    function setFullname() {
      fullname.value = "jay FT"
    }

  }
}

生命周期注册函数

在 setup 里,可以使用直接导入的 onX 函数注册生命周期钩子

import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

setup(){
  console.log('---setup---')
  let sum = ref(0)

  //通过组合式API的形式去使用生命周期钩子
  onBeforeMount(()=>{
    console.log('---onBeforeMount---')
  })
  onMounted(()=>{
    console.log('---onMounted---')
  })
  onBeforeUpdate(()=>{
    console.log('---onBeforeUpdate---')
  })
  onUpdated(()=>{
    console.log('---onUpdated---')
  })
  onBeforeUnmount(()=>{
    console.log('---onBeforeUnmount---')
  })
  onUnmounted(()=>{
    console.log('---onUnmounted---')
  })

  return {sum}
},
选项式 API Hook inside setup
beforeCreate setup()
create setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
activted OnActivted
deactivated Ondeactivated

setup 函数是 围绕 beforeCreate 和 create 生命周期钩子运行的,所有不需要显式的定义它们。在这些钩子的代码直接在 setup 函数编写

provide/inject

实现祖先组件与后代组件之间通信

// 祖先组件传递数据
import { provide, reactive, ref } from 'vue'

setup() {
  let car = reactive({...})
  let sum = ref(0)

  provide('sum', sum)
  provide('car', car)
}

// ======================
// 后代组件接收数据
import { inject } from 'vue'

setup() {
  const car = inject('car')
  const sum = inject('sum')
  return { car, sum }
}

watch/watchEffect

watch

// 数据
let sum = ref(0)
let msg = ref('hello')
let infoRef = ref({
  name: 'Jay',
  age: 18,
  song: {
    fantasy: {
      year: 2002
    }
  }
})
// reactive响应数据
let info = reactive({
  name: 'Jay',
  age: 18,
  song: {
    fantasy: {
      year: 2002
    }
  }
})

// 侦听 ref 定义响应式数据
watch(sum, (newVal, oldVal) => {
  console.log(newVal, oldVal)
})
// 侦听多个 ref 数据、
watch([sum, msg], (newVal, oldVal) => {
  // newVal,oldVal 也是数组
  console.log(newVal, oldVal)
})
// 侦听对象数据 需要开启深度监听
// 此时监听的是 RefImpl 实例
// Ref 实例的 value 是 Proxy 对象,存的是地址
// 因此无法监听 person 内部属性的变化
watch(infoRef, (newVal, oldVal) => {}, { deep: true })

// 侦听 reactive 定义的响应式数据某个属性
// 强制开启了深度侦听,deep 配置不生效!
// 如果监视的属性还是对象,则需要开启深度监听
// 侦听多个属性也是使用数组形式
watch(
  () => info.song,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  { deep: true }
)

可选项

  • immediate: true

    页面加载触发一次

  • deep: true

    深度侦听

watchEffect 函数

  • watchEffect 不需要指明监听哪个属性,回调里用到哪个属性,就自动监听哪个属性
  • computed 注重计算的值,即回调函数的返回值,因此必须有返回值
  • watchEffect 更注重过程,即回调函数的函数体,因此可没有返回值
  • watchEffect 没有开启深度监听,也不能开启深度监听!
  • watchEffect 内部自行修改数据,不会重新调用回调,因此不会出现递归调用
// 回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
  let total = sum.value
  let p = person
  console.log('watchEffect...')
})

watch/watchEffect区别

  • 1.watch必须制定数据源, watchEffect自动收集依赖
  • 2.watch监听到改变, 可以拿到改变前后value
  • 3.watchEffect默认直接执行一次, watch在不设置immediate第一次是不执行

script setup语法糖

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,当同时使用 SFC 与组合式 API 时则推荐该语法。

<script setup> 里面的代码会被编译成组件 setup() 函数的内容:

  • 这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同

  • <script setup> 中的代码会在每次组件实例被创建的时候执行。

  • 顶层的绑定会被暴露给模板:任何在 <script setup>声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容都能在模板中直接使用

  • 导入组件直接使用:<script setup> 范围里的值也能被直接作为自定义组件的标签名使用

defineProps && defineEmits

为了在声明 props 和 emits 选项时获得完整的类型推断支持,我们可以使用 defineProps 和 defineEmits API,它们将自动地在 <script setup> 中可用

<script setup>
const props = defineProps({
  name: {
    type: String,
    default: 'Jay'
  },
  age: {
    type: Number,
    default: 1
  }
})
const emit = defineEmits(['changeAge'])
function changeAge() {
  emit('changeAge', 200)
}
</script>

defineExpose

通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定

通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的 property

function foo() {
  console.log('foo执行')
}
defineExpose({
  foo
})

Compositon API 的优势

Options API 存在的问题

使用传统 Options API 中,新增或者修改一个需求,就需要分别在 data,methods,computed 等地方修改

composition API 的优势

可以更加优雅地组织代码、函数,让相关功能的代码更加有序的组织在一起。说白了就是让同一个功能的代码整合到一起,日后修改代码直接找对应的功能模块。