夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • 响应式

  • 组件

  • 过渡&动画

  • 可复用性

    • Mixin混入
    • 渲染函数
    • 全局API
    • Teleport
    • 组合式API
      • 全新的组合式勾子:setup
        • 参数
      • 带 ref 的响应式变量
        • ref
        • reactive
        • toRef
        • toRefs
        • ref 类型声明
        • shallowRef
        • triggerRef
        • markRaw
        • toRaw
      • setup 中的生命周期勾子
      • 监听属性 watch
        • watchEffect
        • 侦听器调试
      • 独立的 computed 属性
      • 渲染函数
      • Provide Inject
        • 使用 provide
        • 使用 Inject
        • 子组件内更新 inject 的情况
      • 侦听模板引用
    • 引入css样式
    • 自定义hook
    • 自定义插件
    • 自定义事件
    • 自定义指令
  • 工具

  • 规模化

  • 路由(核心)

  • 组合式

  • Vuex

  • Pinia

  • 其他

  • Vue3 源码学习记录

  • 《Vue》笔记
  • 可复用性
夜猫子
2022-06-26
目录

组合式API

# 组合式 API

作用:将逻辑关注点相同的代码收集到一起去。

# 全新的组合式勾子:setup

setup 中无法使用 this: 因为setup 发生在 data、computed 或 methods 被解析之前。

因此 setup 中只能访问到 props、attrs、slots、emit。

# 参数

setup 中接受 props 和 context 并将返回的内容暴露给组件的其他部分(计算属性、方法、生命周期钩子等等) 以及组件的模板。

  1. 其中 props 是响应式的,所以不能使用 ES6 的解构赋值,如要解构则需使用 torefs(props) 来包裹。

  2. context 是一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值:

  • attribute 是元素标签的属性,property 是元素对象的属性

  • input 的 value attribute 是通过标签里的 value=“test value” 定义的,可以通过input.getAttribute(‘value’) 获取,可以通过 input.setAttribute(‘value’, ‘New Value’) 更新

  • input 的 value property 可通过 input.value 获取和更新,初始值是与 attribute 中的赋值一致的

export default {
  setup(props, context) {
      
    // Attribute (非响应式对象,等同于 $attrs)    
    console.log(context.attrs)

    // 插槽 (非响应式对象,等同于 $slots)
    console.log(context.slots)

    // 触发事件 (方法,等同于 $emit)
    console.log(context.emit)

    // 暴露公共 property (函数),可以解决返回渲染函数时无法return返回其他内容的问题
    console.log(context.expose)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 带 ref 的响应式变量

# ref

使通过值传递的 string 和 number 类型也能变得响应式。

const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true
1
2
3
4
5

解析$ref

//html
<div ref='divRef'>
    myDiv
</div>

//js
const divRef=ref(null)
console.log(divRef.value.innerHtml) //'myDiv'
1
2
3
4
5
6
7
8

# reactive

用法与 ref 类似,不同的是ref用于基本数据类型,而reactive是用于复杂数据类型,比如对象和数组。

reactive 将解包所有深层的 refs 同时维持 ref 的响应性。

readonly 接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用于响应性追踪
  console.log(copy.count)
})

// 变更 original 会触发依赖于副本的侦听器
original.count++

// 变更副本将失败并导致警告
copy.count++ // 警告!
1
2
3
4
5
6
7
8
9
10
11
12
13
14

shallowReactive: 浅层次的数据改变,DOM会响应其变化;深层次的数据改变,DOM 不会随之刷新。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
1
2
3
4
5
6
7
8
9
10
11
12

# toRef

为源响应式对象上的某个 property 新创建一个 ref (opens new window)。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3
1
2
3
4
5
6
7
8
9
10
11
12

可对props的响应式封装,作用为使本身具有响应式特点的 props 可以使用解构赋值。

import { ref, onMounted, watch, toRef } from 'vue'
setup (props) {
  // 使用 `toRefs` 创建对 `props` 中的 `user` property 的响应式引用
  const { user } = toRef(props)
  }
1
2
3
4
5

# toRefs

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref (opens new window)。

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# ref 类型声明

有时我们可能需要为 ref 的内部值指定复杂类型。可以在调用 ref 时传递一个泛型参数以覆盖默认推断,从而简洁地做到这一点:

const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number>
//或 const foo:Ref<string | number>=ref('foo')
foo.value = 123 // ok!
1
2
3

关于 Ref

如果泛型的类型未知,则建议将 ref 转换为 Ref<T>是

interface Ref<T> {
  value: T
}

function ref<T>(value: T): Ref<T>
1
2
3
4
5
function useState<State extends string>(initial: State) {
  const state = ref(initial) as Ref<State> // state.value -> State extends string
  return state
}
1
2
3
4

# shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

# triggerRef

手动执行与 shallowRef 关联的任何作用 (effect)。

const foo = shallowRef({name:'true'})
// 改变 ref 的值是响应式的
foo.value = {name:'false'}//此时改变了
// 但是修改属性值时这个值不会被转换。
foo.value.name='true' // 还是 false
// 添加下面代码后进行强制转换
triggerRef(shallow) //变为 true 了
1
2
3
4
5
6
7

# markRaw

标记一个对象,使其永远不会转换为 proxy。返回对象本身。

  • 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
  • 当渲染具有不可变数据源的大列表时,跳过 proxy 转换可以提高性能。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
1
2
3
4
5
6

# toRaw

返回 reactive (opens new window) 或 readonly (opens new window) 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true
1
2
3
4

# setup 中的生命周期勾子

组合式 api 中的生命周期勾子与声明式一样,但前缀为 on:即 onMounted

需要注意的是,vue3中将 destoryed 重新命名为 unmounted ; beforeDestroy 被修改为 beforeUnmount

此外,因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在两个钩子中编写的任何代码都应该直接在 setup 函数中编写。

// src/components/UserRepositories.vue `setup` function
<template>
  <div>{{ value1 }}</div>
/* 在模板中使用setup的返回值时,refs被浅解析,因此不应使用.value*/
</template>

import { ref, onMounted } from 'vue'

// 在我们的组件中
setup (props) {
  const value1 = ref([])
  const fn =  () => {
    value1.value = 2
  }
  
  onMounted(fn) // 在 `mounted` 时调用函数 `fn`

  return {
    value1,
    fn
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 监听属性 watch

其接受三个参数

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

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

# watchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0)

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

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)
1
2
3
4
5
6
7
8
9

watch 与 watchEffect 在手动停止侦听、清除副作用 (将 onInvalidate 作为第三个参数传递给回调)、刷新时机和调试方面有相同的行为。

const id =ref<string>('001')
const stop=watchEffect(onInvalidate=>{//此处将监听函数返回 stop
 console.log(id,value)
 onInvalidate(() => {
    arr.clear()
  })
})
stop()//当stop被调用时,stop所代表的监听函数终止监听
1
2
3
4
5
6
7
8

# 侦听器调试

onTrack 和 onTrigger 选项可用于调试侦听器的行为。

  • onTrack 将在响应式 property 或 ref 作为依赖项被追踪时被调用。
  • onTrigger 将在依赖项变更导致副作用被触发时被调用。

这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger 语句来检查依赖关系:

watchEffect(
  () => {
    /* 副作用 */
  },
  {
    onTrigger(e) {
      debugger
    }
  }
)
1
2
3
4
5
6
7
8
9
10

onTrack 和 onTrigger 只能在开发模式下工作。

# 独立的 computed 属性

如watch、ref,通过导入属性可以实现在vue组件外部使用

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
1
2
3
4

# 渲染函数

expose :使 return 渲染函数时,同时对外可以暴露自定义 property

import { h, ref, reactive } from 'vue'

export default {
  setup() {
    const readersNumber = ref(0)
    const book = reactive({ title: 'Vue 3 Guide' })//复杂数据的ref封装
    
    const count = ref(0)
    const increment = () => ++count.value

    expose({//借助expose,类似于 return ,传递出 increment 对象
      increment
    })
    // 请注意这里我们需要显式使用 ref 的 value
    return () => h('div', [readersNumber.value, book.title,count.value])
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Provide Inject

# 使用 provide

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

  1. name (<String> 类型)
  2. value
<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
    //或是进行响应式包装,使子孙组件能够响应父组件的改变
    //const location = ref('North Pole')
    //const geolocation = reactive({
    //  longitude: 90,
    //  latitude: 135
    //})
    //provide('location', location)
    //provide('geolocation', geolocation)
    //return {
    //	location
    //}
  },
  methods: {
    updateLocation() {//此处修改 provide 数据
      this.location = 'South Pole'
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

建议:如果要对响应式的 provide / inject 进行修改时,尽可能的在定义的组件内修改。

如果需要在接收数据的组件内更新 inject 数据,可以类似于组件通讯一般,父组件将修改方法传递过去,然后在子组件内调用。

# 使用 Inject

inject 函数有两个参数:

  1. inject 的 property 的 name
  2. 默认值 (可选),如未取到第二个值,则给其一个默认值
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')//对inject进行重构
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 子组件内更新 inject 的情况

推荐对 property 使用 readonly 确保通过 provide 传递的数据不会被 inject 的组件更改。

<!-- src/components/MyMap.vue (祖先组件)-->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, readonly, 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
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', readonly(location))//使用了readonly
    provide('geolocation', readonly(geolocation))
    provide('updateLocation', updateLocation)
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    const updateUserLocation = inject('updateLocation')

    return {
      userLocation,
      userGeolocation,
      updateUserLocation
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 侦听模板引用

与生命周期钩子的一个关键区别是,watch() 和 watchEffect() 在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。

若想确保模板引用与 DOM 保持同步,并引用正确的元素,应该用 flush: 'post' 选项来定义。

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => <div>This is a root element</div>
      }, 
      {//若未加该选项,则上一行将输出 null
        flush: 'post'
      })

      return {//传递出 root 后,被模板引用,于是该模板被定义给了 root 的值
        root
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
编辑 (opens new window)
上次更新: 2023/7/11 18:57:41
Teleport
引入css样式

← Teleport 引入css样式→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式