重读Vue文档

Ref & Reactive

Ref Unwrapping in Reactive Objects

参考Vue文档
reactive包含ref,直接修改ref,都会变化

1
2
3
4
5
6
const counter = ref(0)
const searchParams = ref({
count: counter // 不解构直接用ref
})

counter.value++ // counter: 1 searchParams.count: 1

Conditional Rendering

参考Vue文档

v-if on <template>

控制包含多个元素时,可以用 <template> 包裹(渲染出来时不包含template元素),∴不需要再套一层div

1
2
3
4
5
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-show不支持写在 <template>

v-if with v-for

  • vue3:v-if > v-for
  • vue2:v-if < v-for

Dynamic Arguments

参考Vue文档

Value Constraints

string | null

Syntax Constraints

  • html attribute names 不能有空格和引号。∴ 可以用computed来代替直接写在template中。
  • 直接使用in-DOM template时(直接写在hmtl文件里的),要避免大写字母。∵ 浏览器会强制转为小写

List Rendering

参考Vue文档

v-for with an Object

顺序依据的是 Object.keys()

1
2
3
<li v-for="(value, key, index) in myObject" :key="value">
{{ index }}. {{ key }}: {{ value }}
</li>
  • v-for支持写在 <template>上。
  • 若遇到v-forv-if一起,推荐在<template>上写v-forv-if就可以访问到v-forscope中的值啦

Replacing an Array

  • filter(), concat()slice()不会改变原数组的方法时:需要用生成的新数组代替旧的
  • computed property中使用改变原数组的方法时:需要先复制一个再进行操作 return [...numbers].reverse()

高效

  • 不会「丢弃现有的 DOM 并重新渲染整个列表」
  • TODO:原因

Watchers

参考Vue文档

推荐写法

  1. watch single ref: 直接watch

    1
    2
    3
    4
    const value = ref("value")
    watch(value, (newVal) => {
    console.log(`value is ${newVal}`)
    })
  2. watch a property of a reactive object: getter写法

    1
    2
    3
    4
    5
    6
    const config = ref({
    name: 'waynzh'
    })
    watch(() => config.name, (newName) => {
    console.log(`name is ${newName}`)
    })

Deep Watchers - getter写法返回 Reactive Object

  • 问题:用getter写法返回 Reactive Object时,只有整个替换object才能被监听到。
  • 解决方式:
    1. 不用getter写法就自带deep
    2. 显式加 { deep: true }
1
2
3
4
5
6
7
const config = ref({
name: 'waynzh'
})
watch(() => config, (newVal, oldVal) => {
// `newValue` will be equal to `oldValue`
// *unless* config 被整个替换
}, { deep: true })
  • newValueoldValue是相等的, 除非 Object 被整个替换掉

watchEffect

  • 副作用发生期间追踪依赖,但响应性依赖关系不那么明确。勉强可以替代 watch 中的 immediate
1
2
3
4
5
6
7
8
9
10
11
12
13
const url = ref('https://...')
const data = ref(null)

watch(url, async () => {
const response = await fetch(url.value)
data.value = await response.json()
}, { immediate: true })

// 收集到能访问到的响应式 property
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})

Callback Flush Timing - 回调时机

  • 默认,watch会在组件更新之前被调用,∴ 在watch中访问的DOM的是更新之前的状态
  • 若想访问更新之后的DOM,需要指明flush: 'post'
  • watchEffect() 中,可以使用别名 watchPostEffect()
1
2
3
4
watch(url, async () => {
const response = await fetch(url.value)
data.value = await response.json()
}, { flush: 'post' })

Template Refs

参考Vue文档

组件ref TS支持: InstanceType

  • typeof 获得组件的类型,再用 InstanceType 获取其实例类型
    1
    2
    3
    4
    5
    6
    7
    import MyModal from './MyModal.vue'

    const modal = ref<InstanceType<typeof MyModal> | null>(null)

    const openModal = () => {
    modal.value?.open()
    }

Refs inside v-for

  • 可以为一个数组,但顺序 不一定 是array的顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
const list = ref([
/* ... */
])

const itemRefs = ref([]) // 顺序 *不一定* 是array的顺序
</script>

<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>

setup 显式暴露子组件内容

  • 使用了 <script setup> 的组件是默认私有的,除非子组件在其中通过 defineExpose 宏显式暴露
1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 显式暴露
defineExpose({
a,
b
})
</script>

Components Basics

props TS支持:defineProps

参考Vue文档

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">
// runtime
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})

// type-based
const props = defineProps<{
foo: string
bar?: number
}>()
</script>

限制:不用使用import的interface

1
2
3
4
5
6
7
8
9
// ✅
interface Props {/* ... */}
defineProps<Props>()

// ❌
import { Props } from './other-file'

// NOT supported
defineProps<Props>()

emit TS支持: defineEmits

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
// runtime
const emit = defineEmits(['change', 'update'])

// type-based
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>

Components In-Depth

推荐写法:组件名用 PascalCase,prop用 kebab-case

1
<MyComponent greeting-message="hello" />

Binding Multiple Properties Using an Object

不加参数直接绑定object,相当于分别绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts">
const post = {
id: 1,
title: 'My Journey with Vue'
}
</script>

<template>
<!-- 不加参数直接绑定object -->
<BlogPost v-bind="post" />

<!-- 相当于分别绑定 -->
<BlogPost :id="post.id" :title="post.title" />
</template>

Events

参考Vue文档

最佳实践

组件的双向传值的两种最佳实现:
1. 父组件 v-model="value",自组件分别绑定值和事件
2. 子组件 v-model="value"绑定值,并利用 computed 设置get()set()计算值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>

<!-- index.vue -->
<CustomInput v-model="searchText" />

<!-- 相当于 -->
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- CustomInput.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed(() => {
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
}
})
</script>

<template>
<input v-model="value" />
</template>

v-model arguments

  • by default: modelValue 为 prop,update:modelValue 为emit事件
  • 添加一个变量作为指定的prop:
1
2
3
4
5
6
7
8
<!-- index.vue -->
<MyComponent v-model:title="bookTitle" />

<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

Handling v-model modifiers

  • by default: v-model="value", 会有一个modelModifiers 的prop
  • 添加一个变量后: v-model:title="value",props中modifiers 的规则为arg+"Modifiers"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- index.vue -->
<MyComponent v-model.capitalize="text" />

<!-- MyComponent.vue -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: {
type: Object,
default: () => {}
}
})

console.log(props.modelModifiers); // { capitalize: true }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- index.vue -->
<MyComponent v-model:title.capitalize="text" />

<!-- MyComponent.vue -->
<script setup>
const props = defineProps({
title: String,
titleModifiers: { // `arg + "Modifiers"`
type: Object,
default: () => {}
}
})
</script>

Fallthrough Attributes

In js: useAttrs() / setup(props, {attrs})

  • const attrs = useAttrs()
  • setup(props, {attrs}) {}

Not reactive

  • 如果需要动态变化,可以使用prop 或者 onUpdated()

Slots

参考Vue文档

Named Slots

  • 子组件:用name命名slot <slot name="header">
  • 父组件:用v-slot:header 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- index.vue -->
<BaseLayout>
<template v-slot:header>
<!-- content for the “header” slot -->
</template>
</BaseLayout>

<!-- BaseLayout.vue -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

参考图片

Dynamic Slot Names

语法:<template v-slot:[dynamicName]>

Scoped Slots

子组件内的参数传给父组件,在父组件中访问并安排样式。详情参考 List生成例子

  • 写法类似 render functions中的逻辑
  • 和 Composable 概念类似,Renderless Component即只处理逻辑(fetching,pagination),不处理样式。

Provide / inject

参考Vue文档

inject Default Values

  • 如果没有匹配到provide的Key(’message'),则使用default value
  • ∵ 运行时不知道是否会提供,inject的type会默认带有 undefined
    • 如果设置了default value就会移除
    • 如果确认肯定会提供,可以断言 inject('foo') as string
1
const value = inject('message', 'default value')

最佳实践 - 将更改保持在provide

  • Keep any mutations to reactive state inside of the provider whenever possible
    • provide mutation methods
    • wrap the provided value with readonly()

最佳实践 - Provide Symbol Keys

  • Provide Symbol Keys
  • export the Symbols in a dedicated file

InjectionKey

1
2
3
4
5
import type { InjectionKey } from 'vue'

const key = Symbol() as InjectionKey<string>

provide(key, 'foo') // providing non-string value will result in error

异步组件

defineAsyncComponent

  • 最后得到的 AsyncComp 是一个包装器组件,仅在页面需要它渲染时才调用加载函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)

onst AsyncComp = defineAsyncComponent(() => {
// 加载函数
loader: () => import('./Foo.vue'),

// Loading 组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
// 防止网络很好,看起来像在闪烁
delay: 200,

// Error 组件
errorComponent: ErrorComponent,
// timeout 时间限制,超时了会显示报错组件,默认值是:Infinity
timeout: 3000
})

Custom Drectives

参考Vue文档

  • setup中,任何v开头的都可以作为自定义指令。
  • 没有setup中,需要用 directives 注册
1
2
3
4
5
6
7
8
9
10
<script setup>
// enables v-focus in templates
const vFocus = {
mounted: (el) => el.focus()
}
</script>

<template>
<input v-focus />
</template>

Build-in Components

KeepAlive

参考Vue文档

Include / Exclude

1
2
3
4
5
6
7
8
9
<!-- 逗号分隔,不需要v-bind -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>

<!-- 正则 -->
<KeepAlive :include="/a|b/">
<!-- Array -->
<KeepAlive :include="['a', 'b']">

Max Cached Instances

超过 max 时,最旧访问的缓存实例将被删除,为新的缓存提供空间

1
2
3
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>

Lifecycle of Cached Instance

  • onDeactivated():
    • <KeepAlive>中的cache当从DOM移除时。非 unmounted
    • unmount 和 每次从DOM移除时
  • onActivated():
    • <KeepAlive>中的cache添加到DOM时。
    • intial mount 和 每次插入DOM时

Teleport

参考Vue文档

To

  • to: a CSS selector string / an actual DOM node
1
2
3
4
5
6
7
8
<button @click="open = true">Open Modal</button>

<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
1
2
3
4
5
6
7
8
9
10
11
12
<Teleport to="#modals">
<div>A</div>
</Teleport>
<Teleport to="#modals">
<div>B</div>
</Teleport>

<!-- = -->
<div id="modals">
<div>A</div>
<div>B</div>
</div>

Disabled

1
2
3
<Teleport to="body" :disabled="isMobile">
...
</Teleport>

Reactivity in Depth

参考Vue文档

Component Debugging Hooks

  • onRenderTracked / onRenderTriggered
1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { onRenderTracked, onRenderTriggered } from 'vue'

onRenderTracked((event) => {
debugger
})

onRenderTriggered((event) => {
debugger
})
</script>

Computed / Watcher Debugging

  • onTrack / onTrigger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const plusOne = computed(() => count.value + 1, {
onTrack(e) {
// triggered when count.value is tracked as a dependency
debugger
},
onTrigger(e) {
// triggered when count.value is mutated
debugger
}
})

watch(source, callback, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
文章目录
  1. 1. Ref & Reactive
    1. 1.1. Ref Unwrapping in Reactive Objects
  2. 2. Conditional Rendering
    1. 2.1. v-if on <template>
    2. 2.2. v-if with v-for
  3. 3. Dynamic Arguments
    1. 3.1. Value Constraints
    2. 3.2. Syntax Constraints
  4. 4. List Rendering
    1. 4.1. v-for with an Object
    2. 4.2. Replacing an Array
      1. 4.2.1. 高效
  5. 5. Watchers
    1. 5.1. 推荐写法
    2. 5.2. Deep Watchers - getter写法返回 Reactive Object
    3. 5.3. watchEffect
    4. 5.4. Callback Flush Timing - 回调时机
  6. 6. Template Refs
    1. 6.1. 组件ref TS支持: InstanceType
    2. 6.2. Refs inside v-for
    3. 6.3. setup 显式暴露子组件内容
  7. 7. Components Basics
    1. 7.1. props TS支持:defineProps
      1. 7.1.1. 基本用法
      2. 7.1.2. 限制:不用使用import的interface
    2. 7.2. emit TS支持: defineEmits
  8. 8. Components In-Depth
    1. 8.1. Binding Multiple Properties Using an Object
    2. 8.2. Events
      1. 8.2.1. 最佳实践
      2. 8.2.2. v-model arguments
      3. 8.2.3. Handling v-model modifiers
    3. 8.3. Fallthrough Attributes
      1. 8.3.1. In js: useAttrs() / setup(props, {attrs})
      2. 8.3.2. Not reactive
    4. 8.4. Slots
      1. 8.4.1. Named Slots
      2. 8.4.2. Dynamic Slot Names
      3. 8.4.3. Scoped Slots
    5. 8.5. Provide / inject
      1. 8.5.1. inject Default Values
      2. 8.5.2. 最佳实践 - 将更改保持在provide中
      3. 8.5.3. 最佳实践 - Provide Symbol Keys
      4. 8.5.4. InjectionKey
  9. 9. 异步组件
    1. 9.1. defineAsyncComponent
  10. 10. Custom Drectives
  11. 11. Build-in Components
    1. 11.1. KeepAlive
      1. 11.1.1. Include / Exclude
      2. 11.1.2. Max Cached Instances
      3. 11.1.3. Lifecycle of Cached Instance
    2. 11.2. Teleport
      1. 11.2.1. To
      2. 11.2.2. Disabled
  12. 12. Reactivity in Depth
    1. 12.1. Component Debugging Hooks
    2. 12.2. Computed / Watcher Debugging