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 钩子之前执行,thisundefined
  • 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. 按照之前的办法,对于对象数据,应该遍历每一层的属性添加 gettersetter,但 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 函数是 围绕 beforeCreatecreate 生命周期钩子运行的,所有不需要显式的定义它们。在这些钩子的代码直接在 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 的优势

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