重学Vue 2.0


事件处理

当我们需要访问内联处理程序中的原始 DOM 事件 可以用特殊变量将其传递给方法$event 或者使用内联箭头函数

1
2
3
4
5
6
7
8
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>

<!-- using inline arrow function -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>

表单上输入绑定

  • <input>文本类型和<textarea>元素使用value属性和input事件;
  • <input type="checkbox">``<input type="radio">使用checked和事件change
  • <select>用作valuechange事件。

v-model将忽略在任何表单元素上找到的初始或属性value。它将始终将当前绑定的 JavaScript 状态视为事实来源。您应该使用反应性 API在 JavaScript 端声明初始值。checked``selected

如果您的表达式的初始值v-model与任何选项都不匹配,则该<select>元素将呈现为“未选择”状态。在 iOS 上,这将导致用户无法选择第一项,因为在这种情况下 iOS 不会触发更改事件。因此,建议提供一个带空值的禁用选项

默认情况下 v-model 在每个事件之后将输入与数据同步 可以添加修饰符lazy修改为在事件后同步

1
2
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" />

如果希望用户输入的自动转化为数字 可以添加.number

如果希望自动修剪用户输入中的空白 可以加入.trim

生命周期钩子

Watcher

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
<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>

<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>

当你在 watch()直接调用一个响应式对象时 它会隐式创建一个深度观察者模式-回调将在所有嵌套突变上触发

1
2
3
4
5
6
7
8
9
10
11
const obj = reactive({ count: 0});
watch(obj, (newValue, oldValue) => {

});
obj.count ++;
//而使用getter:() => state.someObject则只会在放回不同的对象时才会触发回调
可以显示使用deep选项来进入深度观察者模式
watch(
() => state....,
(newValue, oldValue) => {}
{ deep: true})

**tips:**深度监视需要遍历监控对象中的所有嵌套属性 并且在用于大型数据结构时可能会开销昂贵 所以仅在必要时使用它并注意性能影响

watchEffect()

watch 是惰性的 在观察源发生变化之前不会立即调用 但是如果我们想要获取一些初始数据 然后在相关状态发生变化时重新获取数据

1
2
3
4
5
6
7
8
9
10
11
12
const url = ref('https://...')
const data = ref(null)

async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}

// fetch immediately
fetchData()
// ...then watch for url change
watch(url, fetchData)

watchEffect 允许我们在自动追踪效果的反应依赖的同时立即执行副作用函数

1
2
3
4
5
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
//在这苦 回调函数立即执行 他还会跟踪url.value为依赖项(类似于计算属性) 当url.value发生变化时 回调将再次运行
1
2
3
4
5
6
7
8
9
10
11
12
const API_URL = `https://api.github.com/repos/vuejs/core/commits?per_page=3&sha=`
const branches = ['main', 'v2-compat']

const currentBranch = ref(branches[0])
const commits = ref(null)

watchEffect(async () => {
// this effect will run immediately and then
// re-run whenever currentBranch.value changes
const url = `${API_URL}${currentBranch.value}`
commits.value = await (await fetch(url)).json()
})

tips:watch 仅在其同步执行期间跟踪依赖项 当它与异步回调一起使用时 只会跟踪 await 跟踪第一个 tick 之前访问的属性

  • watch 仅跟踪明确监视的源 将依赖跟踪与副作用分开
  • 将依赖跟踪与副作用结合到一个阶段 会自动跟踪在同步执行期间访问的每一个反应属性 会使代码更简洁 但是依赖关系不明确

回调刷新时间

vue 默认在组件更新之前调用用户创建的观察者回调 所以如果尝试在观察者回调中访问 dom 则 dom 将处于 Vue 应用任何更新之前的状态 所以如果想要在 vue 更新后在 watcher 回调中访问 dom 需要指定 flush:’post’

1
2
3
4
5
6
7
watch(source, callback, {
flush: 'post'
})

watchEffect(callback, {
flush: 'post'
})

停止观察者

Watchers 在内部同步声明setup()<script setup>绑定到所有者组件实例,并在所有者组件卸载时自动停止。如果观察者是异步创建的 那么不会绑定到所有者组件 必须手动停止以避免内存泄漏

1
2
3
4
const unwatch = watchEffect(() => {})

// ...later, when no longer needed
unwatch()

应该军垦首选同步创建 如果需要等待一些异步数据 可以使用逻辑控制异步操作

1
2
3
4
5
watchEffect(() => {
if (data.value) {
// do something when data is loaded
}
})

Props

如果要将对象的所有属性作为 props 传递 则可以使用 v-bind 不带参数使用

1
2
3
<BlogPost v-bind="post" />
//相当于使用
<BlogPost :id="post.id" :title="post.title" />
  • prop 用于传入一个初始值:子组件之后希望将其用作本地数据属性 则定义一个使用 prop 作为其初始值的本地数据属性

  • ```
    const props = defineProps([‘initialCounter’])

    // counter only uses props.initialCounter as the initial value;
    // it is disconnected from future prop updates.
    const counter = ref(props.initialCounter)

    1
    2
    3
    4
    5
    6
    7
    8

    - prop 作为需要转换的原始值传入 最好使用 prop 的值定义计算属性

    - ```
    const props = defineProps(['size'])

    // computed property that auto-updates when the prop changes
    const normalizedSize = computed(() => props.size.trim().toLowerCase())

组件事件

tips:组件的事件不会冒泡 父组件只能接收直接子组件发出的事件

可以通过 defineEmits 显示声明

1
2
3
4
5
6
7
8
9
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
// type-based
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>

如果在选项中定义了本地事件如 click 则侦听器现在仅侦听组件发出的 click 而不再响应本地的 click 事件

如果使用对象语法则可以添加验证 为了添加验证 事件被分配了一个函数 改函数将接收传递给调用的参数并返回一个有效的布尔值以只是事件是否

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup>
const emit = defineEmits({
// No validation
click: null,

// Validate submit event
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})

function submitForm(email, password) {
emit('submit', { email, password })
}
</script>

自定义事件还能和 v-model 相结合

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

<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
使用:
<CustomInput v-model="searchText" />

v-model 还可以使用 computed 属性和 getter 以及 setter 该 get 方法应该返回 modelValue 属性并且该 set 方法应该发出相应的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

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

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

使用多个 v-model

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
<UserName
v-model:first-name="firstName"
v-model:last-name="lastName"
/>
<script setup>
defineProps({
firstName: String,
lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

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

处理 v-model 修饰符:自定义修饰符

eg:自定义一个将字符串第一个字母大写

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
38
39
40
41
<MyComponent v-model.capitalize="myText" />
添加到组件的修饰符v-model将通过modelModifiers prop提供给组件 我们创建了一个组件 其中包含一个modelModifiers默认为空的对象的prop
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})

defineEmits(['update:modelValue'])

console.log(props.modelModifiers) // { capitalize: true }
</script>

<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
------------------我们可以更改成当触发input事件后就会将字符串大写
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }//即传递过来的{ capitalize: true }
})
//设置默认抛出事件
const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)//触发事件
}
</script>
//设置input触发事件
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>

对于v-model同时具有参数和修饰符 生成的道具名称将为arg + 'Modifiers'

1
2
3
4
5
<MyComponent v-model:title.capitalize="myText">
//对应的声明应该为
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }

Fallthrough 属性

v-on传递给组件的属性或事件监听器 但是未在组件的 props 或 emits 中显示声明 如classstyleid

当组件渲染单个根元素时 将自动添加到根元素的属性中

1
2
3
4
5
<!-- template of <MyButton> -->
<button>click me</button>
<MyButton class="large" />
---最终渲染为
<button class="large">click me</button>

如果不希望组件自动继承属性 可以在inheriAttrs: false在组件的选项中进行设置

如果使用<script setup>,您将需要使用单独的普通<script>块声明此选项:

1
2
3
4
5
6
7
8
9
10
<script>
// use normal <script> to declare options
export default {
inheritAttrs: false
}
</script>

<script setup>
// ...setup logic
</script>

常见场景:当属性需要引用于根节点以为的其他元素时 可以将inheritAttrs选项设置为false,您可以完全控制在何处应用 fallthrough 属性

可以通过$attrs来访问

  • 与 props 不同,fallthrough 属性在 JavaScript 中保留了它们的原始大小写,因此foo-bar需要将属性 like 访问为$attrs['foo-bar'].
  • 像这样的v-on事件侦听@click器将作为$attrs.onClick.

我们希望所有诸如classv-on监听器之类的贯穿属性都应用于内部<button>,而不是外部<div>。可以使用 inheritAttrs: false 和

v-bind="$attrs"来使用

1
2
3
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>

如果 v-bind 没有参数 则将对象的所有属性绑定为目标元素的属性

多个根节点的继承

具有多个根节点的组件没有自动属性失效的行为 需要明确绑定$attrs

1
2
3
4
<CustomLayout id="custom-layout" @click="changeValue" />
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
1
2
3
4
5
6
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>
//尽管attrs始终反应最新的fallthrough属性 但是并不是响应性的 所以无法使用观察者观察变化 如果需要响应性 可以使用prop 或者在onUpdated()时执行最新的副作用

插槽

插槽的作用

  • 使用插槽 子组件负责样式的渲染 而内容由父组件提供
  • 通过使用插槽 我们可以使<FancyButton>更加灵活和重用 我们可以在不同的地方使用它 具有不同的内部内容 但是都具有同样花哨的样式

渲染范围

  • 插槽可以访问父组件的数据范围 因为它是在父组件中定义的
  • 插槽无权访问子组件的数据 因为父模板中的所有内容都在父范围内编译 子模板中的所有内容都在子范围内编译

命名插槽

可以通过使用 name 用来为不同的插槽分配一个唯一的 ID 这样可以控制在哪里呈现内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
//当我们要传递指定内容时 可以使用v-slot
<BaseLayout>
<template v-slot:header>
<!-- content for the header slot -->
</template>
</BaseLayout>
//可以使用“#”简写
<template #header>。

当组件同时接受默认槽和命名槽时,所有顶级非<template>节点都被隐式视为默认槽的内容。

动态插槽名称

动态指令参数也适用于 v-slot 允许定义动态插槽的名称

1
2
3
4
5
6
7
8
9
10
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>

<!-- with shorthand -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>

命名插槽

可以通过命名插槽访问子组件的数据

1
2
3
4
5
6
7
8
9
10
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
//可以通过解构的方式来取值
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>

命名范围插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>

<template #default="defaultProps">
{{ defaultProps }}
</template>

<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
<slot name="header" message="hello"></slot>

name 不会包含在 prop 中 所以 headerProps 是{ message: 'hello' }.

作用域插槽的使用场景

如果需要一个渲染项目列表的组件–它可以封装加载远程数据的逻辑 使用数据显示列表 甚至是分页或无限滚动等高级功能 但是我们希望它能够灵活处理每个项目的外观 并将每个项目的样式留给使用它的父组件

1
2
3
4
5
6
7
8
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>

在内部,我们可以使用不同的项目数据多次<FancyList>渲染相同的内容(注意我们使用对象作为插槽道具传递):<slot>``v-bind

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
App.vue
<script setup>
import FancyList from './FancyList.vue'
</script>

<template>
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p class="meta">by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
</template>

<style scoped>
.meta {
font-size: 0.8em;
color: #42b883;
}
</style>
FancyList.vue
<script setup>
import { ref } from 'vue'

const props = defineProps(['api-url', 'per-page'])

const items = ref([])

// mock remote data fetching
setTimeout(() => {
items.value = [
{ body: 'Scoped Slots Guide', username: 'Evan You', likes: 20 },
{ body: 'Vue Tutorial', username: 'Natalia Tepluhina', likes: 10 }
]
}, 1000)
</script>

<template>
<ul>
<li v-if="!items.length">
Loading...
</li>
<li v-for="item in items">
<slot name="item" v-bind="item"/>
</li>
</ul>
</template>

<style scoped>
ul {
list-style-type: none;
padding: 5px;
background: linear-gradient(315deg, #42d392 25%, #647eff);
}
li {
padding: 5px 20px;
margin: 10px;
background: #fff;
}
</style>

Provide/Inject

1
2
3
4
5
script setup>
import { provide } from 'vue'

provide(/* key */ 'message', /* value */ 'hello!')
</script>

提供响应式值允许使用提供的值的后代组件建立到提供程序组件的响应式连接。

如果提供的值是 ref 它是按原样注入 不会自动解构 这允许注入器组件保留与提供者组件的反应性连接

注入默认值

如果想要让注入的属性和可选提供者一起工作 那么就需要声明一个默认值 类似于 props

1
const value = inject("message", "default value");

在某些情况下,可能需要通过调用函数或实例化新类来创建默认值。为了避免在不使用可选值的情况下产生不必要的计算或副作用,我们可以使用工厂函数来创建默认值:

1
const value = inject("key", () => new ExpensiveClas());

使用响应式数据

尽可能将任何对响应式状态的突变保留在提供程序内部 这确保了提供的状态以及可能的突变位于同一组件 从而更容易在将来维护

有时我们可能从租入器组件更新数据 这种情况我们一般提供一个负责改变状态的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

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

provide('location', {
location,
updateLocation
})
</script>
------------------------
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
<button @click="updateLocation">{{ location }}</button>
</template>

最后 我们想确保 provide 的数据不会被改变 可以使用 readonly

1
provide("read-only-count", readonly(count));

最好使用符号注入密钥来避免潜在的冲突 可以将符号到处到专有文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// keys.js
export const myInjectionKey = Symbol();
// in provider component
import { provide } from "vue";
import { myInjectionKey } from "./keys.js";

provide(myInjectionKey, {
/* data to provide */
});
// in injector component
import { inject } from "vue";
import { myInjectionKey } from "./keys.js";

const injected = inject(myInjectionKey);

异步组件

1
2
3
4
5
import { defineAsyncComponent } from "vue";

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

结果AsyncComp是一个包装器组件,它仅在实际呈现在页面上时才调用加载器函数。此外,它会将任何 props 传递给内部组件,因此您可以使用异步包装器无缝替换原始组件,同时实现延迟加载。

加载和错误状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const AsyncComp = defineAsyncComponent({
// the loader function
loader: () => import("./Foo.vue"),

// A component to use while the async component is loading
loadingComponent: LoadingComponent,
// Delay before showing the loading component. Default: 200ms.
delay: 200,

// A component to use if the load fails
errorComponent: ErrorComponent,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000,
});

如果提供了加载组件,它将在加载内部组件时首先显示。在显示加载组件之前有一个默认的 200 毫秒延迟 - 这是因为在快速网络上,即时加载状态可能会被替换得太快并最终看起来像闪烁。

如果提供了错误组件,当 loader 函数返回的 Promise 被拒绝时会显示。您还可以指定超时以在请求时间过长时显示错误组件。

Composables

Composables 是一种可以利用 Vue 组合 API 封装和重用有状态逻辑的函数

  • 鼠标跟踪器示例

  • ```js
    // mouse.js
    import { ref, onMounted, onUnmounted } from ‘vue’

    // by convention, composable function names start with “use”
    export function useMouse() {
    // state encapsulated and managed by the composable
    const x = ref(0)
    const y = ref(0)

    // a composable can update its managed state over time.
    function update(event) {

    x.value = event.pageX
    y.value = event.pageY
    

    }

    // a composable can also hook into its owner component’s
    // lifecycle to setup and teardown side effects.
    onMounted(() => window.addEventListener(‘mousemove’, update))
    onUnmounted(() => window.removeEventListener(‘mousemove’, update))

    // expose managed state as return value
    return { x, y }
    }
    //组件中的使用方式

    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

    COmposables 还可以嵌套使用:一个可组合函数可以调用另一个或多个其他可组合函数 这是的我们能够使用小的、隔离的单元来组合复杂的逻辑 了类似于使用组件来整合整个应用程序的方式

    实例 2:我们可以将添加和清理 DOM 事件监听器的逻辑提取到它自己的可组合中

    ```js
    // event.js
    import { onMounted, onUnmounted } from "vue";

    export function useEventListener(target, event, callback) {
    // if you want, you can also make this
    // support selector strings as target
    onMounted(() => target.addEventListener(event, callback));
    onUnmounted(() => target.removeEventListener(event, callback));
    }
    //现在我们的useMouse()可以简化为
    // mouse.js
    import { ref } from "vue";
    import { useEventListener } from "./event";

    export function useMouse() {
    const x = ref(0);
    const y = ref(0);

    useEventListener(window, "mousemove", (event) => {
    x.value = event.pageX;
    y.value = event.pageY;
    });

    return { x, y };
    }

    **tips:**每个组件实例调用useMouse()都将创建自己的副本xy状态,因此它们不会相互干扰。如果想管理组件之间的共享状态 可以使用 vuex

    异步状态示例

    在进行异步数据获取时 我们经常需要处理不同的状态:加载、成功和错误 我们可以将其提取到可组合中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // fetch.js
    import { ref } from "vue";

    export function useFetch(url) {
    const data = ref(null);
    const error = ref(null);

    fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err));

    return { data, error };
    }
    //现在在我们的组件中 可以这样做
    <script setup>
    import {useFetch} from './fetch.js' const {(data, error)} = useFetch('...')
    </script>;

    如果我们希望在 URL 更改时重新获取数据 可以使用响应式数据作为参数

    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
    // fetch.js
    import { ref, isRef, unref, watchEffect } from "vue";

    export function useFetch(url) {
    const data = ref(null);
    const error = ref(null);

    function doFetch() {
    // reset state before fetching..
    data.value = null;
    error.value = null;
    // unref() unwraps potential refs
    fetch(unref(url))
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err));
    }

    if (isRef(url)) {
    // setup reactive re-fetch if input URL is a ref
    watchEffect(doFetch);
    } else {
    // otherwise, just fetch once
    // and avoid the overhead of a watcher
    doFetch();
    }

    return { data, error };
    }

    useFetch()接收静态 URL 以及 URL 字符串的引用 当检测到 URL 是一个动态引用即 isRef()时 会设置一个响应式效果 WatchEffect()效果将立即运行 并在这个过程中将 URL ref 作为依赖项进行跟踪 每当 URL 更改时 数据将被重置并再次获取

Conventions and Best Practices

  • 以 use 开头 如 useFetch();

  • 如果正在编写一个可能被其他开发人员使用的组合 在处理输入参数时 refs 而不是原始值时是一个好主意 可以使用 unref

    1
    2
    3
    4
    5
    6
    7
    import { unref } from "vue";

    function useFeature(maybeRef) {
    // if maybeRef is indeed a ref, its .value will be returned
    // otherwise, maybeRef is returned as-is
    const value = unref(maybeRef);
    }

    如果在输入是ref是创建响应性 请确保使用watch()来跟踪ref数据 或者使用unref并在内部使用watchEffect()

  • 我们推荐返回一个 refs 对象 以便在组件中对其进行解构并保持响应性 如果更喜欢返回对象属性 可以使用reactive()以便解构引用

1
2
3
const mouse = reactive(useMouse());
// mouse.x is linked to original ref
console.log(mouse.x);

副作用

  • 如果正在开发服务端渲染(SSR)的原因程序 确保在安装后生命周期执行特定于 DOM 的副作用 如在 onMounted()确保可以访问 DOM
  • 确保清除 onUnmounted 在设置事件监听器后记得清理

使用限制

只能在钩子中同步使用 可以在<script setup>setup()onMounted()中调用

  • 生命周期钩子可以注册到它
  • 计算属性和观察者可以追踪并在组件卸载时进行处理
  • 你只能在<script setup>中使用 await 编译器会在执行异步操作后自动恢复活动实例上下文

为代码组织提取可组合项

可组合项不仅可以提取用于重用 还可以用于代码组织

1
2
3
4
5
<script setup>
import {useFeatureA} from './featureA.js' import {useFeatureB} from
'./featureB.js' import {useFeatureC} from './featureC.js' const {(foo, bar)} =
useFeatureA() const {baz} = useFeatureB(foo) const {qux} = useFeatureC(baz)
</script>

自定义指令

自定义指令主要用于重用涉及对普通元素进行低级 DOM 访问的逻辑

自定义指令被定义为包含类似于组件的生命周期钩子的对象 钩子接收指令绑定的元素

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>

文章作者: olddog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 olddog !
  目录