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 })
counter.value++
|
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-for
和v-if
一起,推荐在<template>
上写v-for
,v-if
就可以访问到v-for
scope中的值啦
Replacing an Array
- 用
filter()
, concat()
和 slice()
不会改变原数组的方法时:需要用生成的新数组代替旧的
- 在
computed property
中使用改变原数组的方法时:需要先复制一个再进行操作 return [...numbers].reverse()
高效
- 不会「丢弃现有的 DOM 并重新渲染整个列表」
- TODO:原因
Watchers
参考Vue文档
推荐写法
watch single ref: 直接watch
1 2 3 4
| const value = ref("value") watch(value, (newVal) => { console.log(`value is ${newVal}`) })
|
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才能被监听到。
- 解决方式:
- 不用getter写法就自带deep
- 显式加
{ deep: true }
1 2 3 4 5 6 7
| const config = ref({ name: 'waynzh' }) watch(() => config, (newVal, oldVal) => { }, { deep: true })
|
newValue
和 oldValue
是相等的, 除非 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 })
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([]) </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">
const props = defineProps({ foo: { type: String, required: true }, bar: Number })
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'
defineProps<Props>()
|
emit TS支持: defineEmits
1 2 3 4 5 6 7 8 9 10
| <script setup lang="ts">
const emit = defineEmits(['change', 'update'])
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>
<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
| <script setup> defineProps(['modelValue']) defineEmits(['update:modelValue']) </script>
<template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template>
<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
| <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
| <MyComponent v-model:title="bookTitle" />
<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
| <MyComponent v-model.capitalize="text" />
<script setup> const props = defineProps({ modelValue: String, modelModifiers: { type: Object, default: () => {} } })
console.log(props.modelModifiers); </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <MyComponent v-model:title.capitalize="text" />
<script setup> const props = defineProps({ title: String, titleModifiers: { 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
| <BaseLayout> <template v-slot:header> </template> </BaseLayout>
<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')
|
异步组件
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'),
loadingComponent: LoadingComponent, delay: 200,
errorComponent: ErrorComponent, timeout: 3000 })
|
Custom Drectives
参考Vue文档
- setup中,任何
v
开头的都可以作为自定义指令。
- 没有setup中,需要用
directives
注册
1 2 3 4 5 6 7 8 9 10
| <script setup>
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
| <KeepAlive include="a,b"> <component :is="view" /> </KeepAlive>
<KeepAlive :include="/a|b/">
<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
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) { debugger }, onTrigger(e) { debugger } })
watch(source, callback, { onTrack(e) { debugger }, onTrigger(e) { debugger } })
|