1076 lines
30 KiB
Markdown
1076 lines
30 KiB
Markdown
Vue 3.0 引入了很多新的 API 和功能,同时也保留了 Vue 2.x 的很多特性。以下是一个关于 Vue 3.0 常用功能的编程标准指南纲要:
|
||
1. **项目和组件结构**
|
||
- 项目目录和文件命名
|
||
- 单文件组件的结构:template, script, style
|
||
|
||
2. **组件数据**
|
||
- 定义和使用 props
|
||
- 定义和使用组件内部数据:`ref` 和 `reactive`
|
||
- 使用 `computed` 属性
|
||
- 使用 `watch` 和 `watchEffect` 监听数据变化
|
||
|
||
3. **模板语法**
|
||
- 插值:文本,HTML
|
||
- 指令:v-model, v-if/v-else-if/v-else, v-show, v-for, v-bind, v-on
|
||
- 过滤器和全局混入(虽然 Vue 3.0 推荐尽量避免使用,但在一些场景仍有用)
|
||
|
||
4. **组件通信**
|
||
- 使用 props 和自定义事件实现父子组件通信
|
||
- 使用 `provide`/`inject` 实现祖先和后代组件通信
|
||
- 使用 event bus 进行任意组件间的通信(Vue 3.0 不再内置,但可以自行实现)
|
||
- 使用 Vue 3.0 的新功能 `teleport` 进行组件内容的传输
|
||
|
||
5. **Composition API**
|
||
- 使用 `setup` 函数
|
||
- 使用 `ref`, `reactive`, `computed`, `watch`, `watchEffect`
|
||
- 创建和使用 composable 函数
|
||
|
||
6. **路由和状态管理**
|
||
- 使用 Vue Router:定义和使用路由,导航守卫,路由元信息
|
||
- 使用 Vuex:状态定义,Getter,Mutation,Action,模块化状态
|
||
|
||
7. **与外部交互**
|
||
- 使用 `axios` 或者 `fetch` 进行 HTTP 请求
|
||
- 使用 Vue 3.0 的新功能 `Suspense` 进行异步依赖处理
|
||
|
||
8. **测试和部署**
|
||
- 单元测试:使用 Jest 编写和运行测试
|
||
- E2E 测试:使用 Cypress 编写和运行测试
|
||
- 部署:编译,打包,发布
|
||
|
||
1. **项目和组件结构**
|
||
- 项目目录和文件命名
|
||
Vue 项目和组件的结构可以因项目需求和开发团队偏好而有所不同,但这里提供一种推荐的目录结构:
|
||
|
||
```
|
||
/my-app
|
||
├── /node_modules
|
||
├── /public
|
||
│ ├── index.html
|
||
│ └── favicon.ico
|
||
├── /src
|
||
│ ├── /assets // 静态资源如图片、样式等
|
||
│ ├── /components // Vue 组件
|
||
│ ├── /router // Vue Router 配置
|
||
│ ├── /store // Vuex 状态管理配置
|
||
│ ├── /views // 页面级 Vue 组件
|
||
│ ├── App.vue // 根组件
|
||
│ ├── main.ts // 应用入口文件
|
||
│ ├── shims-vue.d.ts
|
||
│ └── ...
|
||
├── package.json
|
||
├── babel.config.js
|
||
└── ...
|
||
```
|
||
|
||
- 文件命名:对于组件文件,推荐使用 'PascalCase' 的方式命名。例如:`MyComponent.vue`。而对于其它的 .js 或 .ts 文件,推荐使用 'kebab-case' 的方式命名。例如:`my-script.ts`。
|
||
- 单文件组件的结构:template, script, style
|
||
对于单文件组件(`.vue` 文件),其内部结构通常包含三部分:`<template>`, `<script>`, 和 `<style>`。`<template>` 中定义了组件的 HTML 结构,`<script>` 中定义了组件的逻辑,而 `<style>` 中定义了组件的样式。这三部分的顺序并不是固定的,但一般推荐将 `<script>` 放在 `<template>` 的下面,以便开发者能更快地看到组件的 HTML 结构。
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<!-- component HTML -->
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent } from 'vue'
|
||
|
||
export default defineComponent({
|
||
// component logic
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* component styles */
|
||
</style>
|
||
```
|
||
|
||
2. **组件数据**
|
||
|
||
在 Vue 3.0 中,组件数据可以通过两种方式进行管理:
|
||
|
||
1. **Props**: Props 是父组件向子组件传递数据的一种方式。Props 可以是任何类型的数据,如字符串,数字,对象,数组,布尔值等。在子组件中,我们需要定义接受的 props。
|
||
|
||
2. **组件内部数据**: 使用 `ref` 和 `reactive` 可以创建组件的响应式数据。`ref` 用于创建响应式的基本类型数据(如字符串、数字等),`reactive` 用于创建响应式的复杂类型数据(如对象和数组)。
|
||
|
||
以下是 props 和响应式数据的用法示例:
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<h1>{{ title }}</h1>
|
||
<p>{{ message }}</p>
|
||
<button @click="increment">Clicks: {{ count }}</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
|
||
export default defineComponent({
|
||
name: 'MyComponent',
|
||
props: {
|
||
title: String,
|
||
},
|
||
setup(props) {
|
||
const count = ref(0)
|
||
const message = ref('Hello, Vue 3 and TypeScript!')
|
||
|
||
function increment() {
|
||
count.value++
|
||
}
|
||
|
||
return {
|
||
count,
|
||
message,
|
||
increment
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在这个示例中,`title` 是一个从父组件传递过来的 prop,`count` 和 `message` 是组件内部的响应式数据。我们在 `setup` 函数中定义了一个方法 `increment`,这个方法会使 `count` 的值增加。
|
||
|
||
注意:在 Vue 3.0 中,使用 `ref` 创建的响应式数据在模板中访问时不需要 `.value`,但在 `setup` 函数或其他 JavaScript 代码中访问时需要 `.value`。例如,在上述例子中,我们在模板中使用 `{{ count }}`,但在 `increment` 函数中使用 `count.value++`。
|
||
|
||
3. **模板语法**
|
||
|
||
Vue.js 使用基于 HTML 的模板语法,允许你声明式地将 DOM 和 Vue 实例的数据绑定在一起。在 Vue 3.0 中,所有 Vue 2.x 的模板语法都得到了支持,包括插值、指令等。
|
||
|
||
以下是一些常用的模板语法:
|
||
|
||
- **插值**: 使用双大括号(`{{ }}`)进行文本插值。
|
||
|
||
```vue
|
||
<template>
|
||
<p>{{ message }}</p>
|
||
</template>
|
||
```
|
||
|
||
- **指令**: Vue 指令是带有 `v-` 前缀的特殊属性。指令属性的值预期是单一 JavaScript 表达式。例如 `v-model`、`v-if`/`v-else-if`/`v-else`、`v-show`、`v-for`、`v-bind`、`v-on` 等。
|
||
|
||
```vue
|
||
<template>
|
||
<input v-model="message">
|
||
<p v-if="visible">Hello Vue!</p>
|
||
<button v-on:click="handleClick">Click me</button>
|
||
<div v-for="(item, index) in items" :key="index">
|
||
{{ item }}
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
- **过滤器**: Vue 2.x 支持过滤器,但在 Vue 3.0 中过滤器被移除了。如果你需要使用过滤器功能,你可以使用计算属性或者方法来替代。
|
||
|
||
注意:在 Vue 3.0 中,你应该尽量使用 `setup` 函数和 Composition API 来组织你的代码,尽管 `data`、`methods`、`computed` 和 `watch` 等选项在 Vue 3.0 中仍然可用。
|
||
|
||
- **V3.0中不再支持或者不推荐的方法一览**
|
||
好的,这是一些在 Vue 3.0 中不再支持或不再建议使用的功能和相应的替代方法:
|
||
|
||
| Vue 2.x | Vue 3.0 替代方法 |
|
||
|----------------------------------|-----------------|
|
||
| Filters | 使用计算属性或者方法 |
|
||
| `Vue.set` 和 `Vue.delete` | 直接使用 `delete` 或 `=` 操作符来添加或删除响应式属性 |
|
||
| `Vue.observable` | 使用 `reactive` 或 `ref` |
|
||
| `Vue.prototype` 自定义属性或方法 | 使用 provide/inject 机制来共享全局数据或方法 |
|
||
| 异步组件的 `component` 工厂函数 | 使用 `defineAsyncComponent` |
|
||
| Inline-template attribute | 使用 JSX 或者 Vue 单文件组件(SFC) |
|
||
| `$on`, `$off` 和 `$once` 方法 | 使用新的事件处理方法,或者使用第三方库 |
|
||
| Functional components | 使用普通的组件或者带有 `setup` 函数的组件 |
|
||
|
||
需要注意的是,Vue 3.0 为我们提供了一种新的组件编写格式——Composition API,它提供了更好的 TypeScript 类型推断支持,并允许我们在大型组件中更好地组织我们的代码。虽然 Vue 2.x 的 Options API 在 Vue 3.0 中仍然可用,但建议新的项目尽可能地使用 Composition API。
|
||
|
||
- 以下是 Vue 2.0 和 Vue 3.0 在不同功能和写法上的对比:
|
||
|
||
1. **声明响应式数据**
|
||
|
||
- Vue 2.0
|
||
|
||
```vue
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
message: 'Hello Vue 2!',
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
- Vue 3.0
|
||
|
||
```vue
|
||
<script lang="ts">
|
||
import { ref } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const message = ref('Hello Vue 3!')
|
||
|
||
return {
|
||
message
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
2. **计算属性**
|
||
|
||
- Vue 2.0
|
||
|
||
```vue
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
count: 0,
|
||
}
|
||
},
|
||
computed: {
|
||
doubleCount() {
|
||
return this.count * 2
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
- Vue 3.0
|
||
|
||
```vue
|
||
<script lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const count = ref(0)
|
||
const doubleCount = computed(() => count.value * 2)
|
||
|
||
return {
|
||
count,
|
||
doubleCount
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
3. **侦听属性**
|
||
|
||
- Vue 2.0
|
||
|
||
```vue
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
count: 0,
|
||
}
|
||
},
|
||
watch: {
|
||
count(newVal, oldVal) {
|
||
console.log(`count changed from ${oldVal} to ${newVal}`)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
- Vue 3.0
|
||
|
||
```vue
|
||
<script lang="ts">
|
||
import { ref, watch } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const count = ref(0)
|
||
|
||
watch(count, (newVal, oldVal) => {
|
||
console.log(`count changed from ${oldVal} to ${newVal}`)
|
||
})
|
||
|
||
return {
|
||
count
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
4. **生命周期钩子**
|
||
|
||
- Vue 2.0
|
||
|
||
```vue
|
||
<script>
|
||
export default {
|
||
mounted() {
|
||
console.log('Component is mounted')
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
- Vue 3.0
|
||
|
||
```vue
|
||
<script lang="ts">
|
||
import { onMounted } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
onMounted(() => {
|
||
console.log('Component is mounted')
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
以上例子说明了 Vue 3.0 中 Composition API 的基本使用方法。你会注意到,Vue 3.0 提供的新的 API 允许我们在 `setup` 方法中更自由、更直观地组织代码,更容易进行类型推断,从而提高代码的可读性和可维护性。
|
||
**4. 组件和指令**
|
||
|
||
1. **组件定义**
|
||
|
||
- Vue 3.0 中的组件定义方式如下:
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
{{ message }}
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
|
||
export default defineComponent({
|
||
setup() {
|
||
const message = ref('Hello Vue 3!')
|
||
|
||
return {
|
||
message
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
2. **使用组件**
|
||
|
||
在 Vue 3.0 中,组件的使用方式和 Vue 2.x 基本相同。可以在父组件中引入子组件,并通过标签形式进行使用。
|
||
|
||
- Vue 3.0 中的使用方式如下:
|
||
|
||
```vue
|
||
<template>
|
||
<ChildComponent />
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent } from 'vue'
|
||
import ChildComponent from './ChildComponent.vue'
|
||
|
||
export default defineComponent({
|
||
components: {
|
||
ChildComponent
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
3. **指令**
|
||
|
||
Vue 3.0 中的指令和 Vue 2.x 在使用方式上也是相似的,包括内置指令如 `v-model`, `v-if`, `v-for`, `v-on`, `v-bind` 等,以及自定义指令。
|
||
|
||
以下是 Vue 3.0 中自定义指令的一个例子:
|
||
|
||
```vue
|
||
<template>
|
||
<p v-focus>Focus me!</p>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref, onMounted } from 'vue'
|
||
|
||
export default defineComponent({
|
||
directives: {
|
||
focus: {
|
||
mounted(el) {
|
||
el.focus()
|
||
}
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述例子中,我们创建了一个自定义指令 `v-focus`,当元素被插入到 DOM 中时,这个指令会使元素自动获取焦点。
|
||
|
||
**使用 props 和自定义事件实现父子组件通信**
|
||
|
||
在 Vue.js 中,props 和自定义事件是实现父子组件通信的主要方式。父组件通过 props 向子组件传递数据,子组件通过发射 (emit) 自定义事件向父组件传递数据或者通知父组件某个事件已经发生。
|
||
|
||
- 以下是一个使用 props 和自定义事件实现父子组件通信的例子:
|
||
|
||
1. **父组件 (ParentComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<ChildComponent :propData="parentData" @childEvent="handleChildEvent" />
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
import ChildComponent from './ChildComponent.vue'
|
||
|
||
export default defineComponent({
|
||
components: {
|
||
ChildComponent
|
||
},
|
||
setup() {
|
||
const parentData = ref('Data from parent component')
|
||
|
||
function handleChildEvent(payload) {
|
||
console.log('Received event from child component with payload:', payload)
|
||
}
|
||
|
||
return {
|
||
parentData,
|
||
handleChildEvent
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述父组件中,我们使用了 `:propData="parentData"` 来向子组件传递 prop,以及使用了 `@childEvent="handleChildEvent"` 来监听子组件发射的 `childEvent` 事件。
|
||
|
||
2. **子组件 (ChildComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<button @click="emitEvent">Emit Event</button>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, PropType } from 'vue'
|
||
|
||
export default defineComponent({
|
||
props: {
|
||
propData: {
|
||
type: String as PropType<string>,
|
||
required: true
|
||
}
|
||
},
|
||
setup(props, { emit }) {
|
||
function emitEvent() {
|
||
emit('childEvent', 'Data from child component')
|
||
}
|
||
|
||
return {
|
||
emitEvent
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述子组件中,我们定义了一个 prop `propData` 来接收父组件传递的数据,以及一个方法 `emitEvent` 来发射 `childEvent` 事件。
|
||
|
||
注意,在 TypeScript 中,我们需要使用 `PropType` 来显式地指定 prop 的类型。
|
||
|
||
**使用 `provide`/`inject` 实现祖先和后代组件通信**
|
||
|
||
在 Vue.js 中,`provide` 和 `inject` 是实现祖先和后代组件之间通信的一种方式,主要用于避免 prop drilling(也就是多层级的组件传递 prop)。祖先组件使用 `provide` 选项来提供某些数据,后代组件使用 `inject` 选项来注入这些数据。
|
||
|
||
以下是一个使用 `provide` 和 `inject` 实现祖先和后代组件通信的例子:
|
||
|
||
1. **祖先组件 (AncestorComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<DescendantComponent />
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
import DescendantComponent from './DescendantComponent.vue'
|
||
|
||
export default defineComponent({
|
||
components: {
|
||
DescendantComponent
|
||
},
|
||
setup() {
|
||
const ancestorData = ref('Data from ancestor component')
|
||
|
||
provide('ancestorData', ancestorData)
|
||
|
||
return {}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述祖先组件中,我们使用了 `provide` 函数来提供一个名为 `ancestorData` 的数据。
|
||
|
||
2. **后代组件 (DescendantComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<p>{{ injectedData }}</p>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, inject, Ref } from 'vue'
|
||
|
||
export default defineComponent({
|
||
setup() {
|
||
const injectedData = inject<Ref<string>>('ancestorData')
|
||
|
||
if (!injectedData) {
|
||
throw new Error('ancestorData is not provided')
|
||
}
|
||
|
||
return {
|
||
injectedData
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述后代组件中,我们使用了 `inject` 函数来注入名为 `ancestorData` 的数据。请注意,在 TypeScript 中,我们需要使用类型断言 `<Ref<string>>` 来指定注入数据的类型。
|
||
|
||
这种方式特别适用于一些跨级别的状态共享,例如主题颜色、语言偏好等全局性的配置。
|
||
|
||
**使用 event bus 进行任意组件间的通信(Vue 3.0 不再内置,但可以自行实现)**
|
||
|
||
Vue 3.0 不再内置 event bus,但你可以使用一个简单的 EventEmitter 实现自己的 event bus。在这种情况下,任何组件都可以通过 event bus 发送和接收事件,实现跨组件通信。
|
||
|
||
以下是一个使用 event bus 进行任意组件间通信的例子:
|
||
|
||
首先,我们需要创建一个 Event Bus:
|
||
|
||
```ts
|
||
// EventBus.ts
|
||
import { EventEmitter } from 'events'
|
||
|
||
export const EventBus = new EventEmitter()
|
||
```
|
||
|
||
然后,组件可以通过 Event Bus 发送和接收事件:
|
||
|
||
1. **发送事件的组件 (EmitterComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<button @click="emitEvent">Emit Event</button>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent } from 'vue'
|
||
import { EventBus } from './EventBus.ts'
|
||
|
||
export default defineComponent({
|
||
setup() {
|
||
function emitEvent() {
|
||
EventBus.emit('customEvent', 'Data from EmitterComponent')
|
||
}
|
||
|
||
return {
|
||
emitEvent
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述组件中,我们定义了一个方法 `emitEvent`,用于在 `customEvent` 事件上发送数据。
|
||
|
||
2. **接收事件的组件 (ReceiverComponent.vue)**
|
||
|
||
```vue
|
||
<template>
|
||
<p>{{ receivedData }}</p>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
|
||
import { EventBus } from './EventBus.ts'
|
||
|
||
export default defineComponent({
|
||
setup() {
|
||
const receivedData = ref('')
|
||
|
||
function handleCustomEvent(payload: string) {
|
||
receivedData.value = payload
|
||
}
|
||
|
||
onMounted(() => {
|
||
EventBus.on('customEvent', handleCustomEvent)
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
EventBus.off('customEvent', handleCustomEvent)
|
||
})
|
||
|
||
return {
|
||
receivedData
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述组件中,我们使用了 `EventBus.on` 来监听 `customEvent` 事件,并在事件处理器 `handleCustomEvent` 中更新 `receivedData`。同时,我们也使用了 `EventBus.off` 来在组件卸载前移除事件监听器,以避免内存泄漏。
|
||
|
||
请注意,使用 event bus 进行跨组件通信应该谨慎使用,因为它使得数据流难以追踪,增加了代码的复杂性。在大型项目中,你可能会更倾向于使用专门的状态管理库,如 Vuex。
|
||
|
||
**使用 Vue 3.0 的新功能 `teleport` 进行组件内容的传输**
|
||
|
||
在 Vue 3.0 中,新增了 `teleport` 功能,也被称为 "传送门"。`teleport` 能将子组件渲染到 DOM 中的任意位置,而不仅仅是局限于父组件的范围内。它特别适用于需要“破坏性”的 UI 特性,例如全屏覆盖的模态框、通知、对话框等。
|
||
|
||
以下是一个使用 `teleport` 进行组件内容的传输的例子:
|
||
|
||
1. 在 HTML 文件中,添加一个目标元素:
|
||
|
||
```html
|
||
<body>
|
||
...
|
||
<div id="modal-root"></div>
|
||
</body>
|
||
```
|
||
|
||
2. 创建一个使用 `teleport` 的组件 (ModalComponent.vue):
|
||
|
||
```vue
|
||
<template>
|
||
<teleport to="#modal-root">
|
||
<div class="modal">
|
||
<h1>This is a modal</h1>
|
||
<slot></slot>
|
||
<button @click="closeModal">Close Modal</button>
|
||
</div>
|
||
</teleport>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, toRef, ref } from 'vue'
|
||
|
||
export default defineComponent({
|
||
setup(props, { emit }) {
|
||
const showModal = ref(false)
|
||
|
||
function closeModal() {
|
||
showModal.value = false
|
||
emit('update:modelValue', showModal.value)
|
||
}
|
||
|
||
return {
|
||
showModal,
|
||
closeModal
|
||
}
|
||
},
|
||
props: {
|
||
modelValue: {
|
||
type: Boolean,
|
||
required: true
|
||
}
|
||
},
|
||
emits: ['update:modelValue']
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.modal {
|
||
/* styles for your modal */
|
||
}
|
||
</style>
|
||
```
|
||
|
||
在上述组件中,我们定义了一个名为 `modal-root` 的目标元素,这就是我们要将模态框渲染的地方。注意,`teleport` 的 `to` 属性用于指定目标元素。
|
||
|
||
然后,在其他任何需要使用这个模态框的组件中,只需如下使用:
|
||
|
||
```vue
|
||
<template>
|
||
<ModalComponent v-model="showModal">
|
||
<p>This is some content inside the modal</p>
|
||
</ModalComponent>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
import ModalComponent from './ModalComponent.vue'
|
||
|
||
export default defineComponent({
|
||
components: {
|
||
ModalComponent
|
||
},
|
||
setup() {
|
||
const showModal = ref(false)
|
||
return {
|
||
showModal
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在使用 `teleport` 的过程中,父组件和被 teleport 的子组件之间的通信仍然保持原样。也就是说,你可以像平常一样使用 props、事件、插槽等。
|
||
|
||
**5. Vue 3.0 中的 Composition API**
|
||
|
||
Vue 3.0 引入了一种新的组件编写方式,名为 Composition API。它是一个基于函数的 API,让你能更灵活地组织和重用逻辑代码。对比于 Vue 2 中的 Options API,Composition API 更容易处理复杂的组件和逻辑,使代码组织更加清晰。
|
||
|
||
以下是一个基本的 Composition API 组件示例:
|
||
|
||
```vue
|
||
<template>
|
||
<button @click="increment">{{ count }}</button>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref } from 'vue'
|
||
|
||
export default defineComponent({
|
||
setup() {
|
||
const count = ref(0)
|
||
function increment() {
|
||
count.value++
|
||
}
|
||
|
||
return {
|
||
count,
|
||
increment
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
在上述示例中,我们在 `setup` 函数中定义了 `count` 数据和 `increment` 方法。请注意,`ref` 是一个函数,用于创建响应式的数据。在模板中,我们可以像平常一样使用 `count` 和 `increment`。
|
||
|
||
Composition API 还提供了一系列其它的函数,如 `reactive`、`computed`、`watch`、`onMounted`、`onUnmounted` 等,可以帮助我们更好地编写组件逻辑。
|
||
|
||
值得注意的是,使用 Composition API 不意味着必须放弃 Options API。实际上,两者可以在同一个组件中并用。你可以根据项目的具体需求和团队的编程习惯,选择最适合你的 API 风格。
|
||
|
||
- **以下是 Vue 3.0 中的 Composition API 函数的基本说明,以及与 Options API 的对比**
|
||
|
||
| Composition API | 说明 | 对应的 Options API |
|
||
| --------------- | ---- | ------------------ |
|
||
| `setup` | `setup` 是一个新引入的组件选项,用于使用 Composition API。它是组件内部使用 Composition API 的入口。| 无 |
|
||
| `ref` | `ref` 函数用于创建一个响应式的数据。它接收一个参数,返回一个响应式的 Ref 对象。| `data` |
|
||
| `reactive` | `reactive` 函数用于创建一个响应式的对象。它接收一个普通对象,返回一个响应式的对象。| `data` |
|
||
| `computed` | `computed` 函数用于创建一个计算属性。它接收一个 getter 函数或者一个具有 getter 和 setter 的对象,返回一个响应式的 Ref 对象。| `computed` |
|
||
| `watch` | `watch` 函数用于响应式地跟踪和触发副作用。它接收一个响应式的源和一个执行副作用的回调函数。| `watch` |
|
||
| `watchEffect` | `watchEffect` 函数用于立即执行传入的一个函数,并响应式地追踪其依赖,并在其依赖变更时重新运行该函数。 | 无 |
|
||
| `onMounted` | `onMounted` 函数在组件被挂载时调用。它接收一个在组件挂载后执行的回调函数。 | `mounted` |
|
||
| `onUnmounted` | `onUnmounted` 函数在组件被卸载时调用。它接收一个在组件卸载后执行的回调函数。 | `beforeDestroy`/`unmounted` |
|
||
| `onUpdated` | `onUpdated` 在组件更新后调用。它接收一个在组件更新后执行的回调函数。| `updated` |
|
||
| `provide` | `provide` 函数用于在组件上定义一个可以被后代组件注入的值。它接收一个提供的键和值。 | 有,与 `provide/inject` 相似但是属性而不是函数 |
|
||
| `inject` | `inject` 函数用于在组件中注入一个由祖先组件提供的值。它接收一个注入的键。 | 有,但与 `provide/inject` 相似但是属性而不是函数 |
|
||
|
||
值得注意的是,虽然一些 Composition API 函数与 Options API 的某些选项有相似之处,但它们的工作方式和使用方式可能有所不同。在实际使用中,你需要根据具体的使用场景和需求选择合适的 API。
|
||
|
||
- ** 使用 `ref`, `reactive`, `computed`, `watch`, `watchEffect`**
|
||
|
||
这些函数是 Vue 3.0 Composition API 的一部分,使得我们可以更加灵活和高效地编写组件逻辑。
|
||
|
||
以下是这些函数的使用示例:
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<p>{{ count }}</p>
|
||
<p>{{ double }}</p>
|
||
<button @click="increment">Increment</button>
|
||
<button @click="decrement">Decrement</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { ref, reactive, computed, watch, watchEffect } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
// 使用 ref 创建响应式数据
|
||
const count = ref(0)
|
||
|
||
// 使用 reactive 创建响应式对象
|
||
const state = reactive({
|
||
message: 'Hello Vue 3!'
|
||
})
|
||
|
||
// 使用 computed 创建计算属性
|
||
const double = computed(() => count.value * 2)
|
||
|
||
// 定义方法
|
||
const increment = () => {
|
||
count.value++
|
||
state.message = `Count is ${count.value}`
|
||
}
|
||
|
||
const decrement = () => {
|
||
count.value--
|
||
state.message = `Count is ${count.value}`
|
||
}
|
||
|
||
// 使用 watch 监听 count 的变化
|
||
watch(count, (newValue, oldValue) => {
|
||
console.log(`Count changed from ${oldValue} to ${newValue}`)
|
||
})
|
||
|
||
// 使用 watchEffect 监听响应式依赖项的变化
|
||
watchEffect(() => {
|
||
console.log(`State message is "${state.message}"`)
|
||
})
|
||
|
||
return {
|
||
count,
|
||
state,
|
||
double,
|
||
increment,
|
||
decrement
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
在上述示例中,我们使用 `ref` 和 `reactive` 创建了响应式数据,使用 `computed` 创建了计算属性,使用 `watch` 和 `watchEffect` 监听了数据的变化。并且在数据变化时更新了相关数据,并打印了日志信息。
|
||
|
||
- ** 创建和使用 composable 函数**
|
||
|
||
在 Vue 3.0 的 Composition API 中,我们可以将复用的逻辑提取出来,创建一个 composable 函数。composable 函数就是一个普通的 JavaScript 函数,这个函数内部使用了 Composition API 的功能,并返回一些响应式数据或函数。
|
||
|
||
以下是一个创建和使用 composable 函数的示例:
|
||
|
||
首先,我们创建一个名为 `useCounter` 的 composable 函数,这个函数提供了计数器的功能:
|
||
|
||
```javascript
|
||
import { ref } from 'vue'
|
||
|
||
export function useCounter() {
|
||
const count = ref(0)
|
||
|
||
const increment = () => {
|
||
count.value++
|
||
}
|
||
|
||
const decrement = () => {
|
||
count.value--
|
||
}
|
||
|
||
return {
|
||
count,
|
||
increment,
|
||
decrement
|
||
}
|
||
}
|
||
```
|
||
|
||
然后,在组件中使用 `useCounter` 函数:
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<p>{{ count }}</p>
|
||
<button @click="increment">Increment</button>
|
||
<button @click="decrement">Decrement</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { useCounter } from './composables/useCounter'
|
||
|
||
export default {
|
||
setup() {
|
||
const { count, increment, decrement } = useCounter()
|
||
|
||
return {
|
||
count,
|
||
increment,
|
||
decrement
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
在上述示例中,我们首先创建了一个 `useCounter` composable 函数,这个函数提供了计数器的功能。然后,在组件中使用了 `useCounter` 函数,从而复用了计数器的功能。通过这种方式,我们可以将复杂的逻辑提取出来,创建 composable 函数,然后在多个组件中复用这些函数。
|
||
|
||
7. **与外部交互**
|
||
|
||
与外部系统(如服务器端 API、本地存储或其他 JavaScript 库)交互是常见的需求。下面介绍一些 Vue 3 和 TypeScript 中的常用方法。
|
||
|
||
**使用 Axios 进行 HTTP 请求**
|
||
在处理与外部交互时,我们通常会遇到异步操作。以下是使用 Vue 3 和 TypeScript 进行异步处理的例子。
|
||
|
||
**使用 Axios 进行 HTTP 请求**
|
||
|
||
首先,我们可以使用 `async/await` 语法改写 Axios 的 HTTP 请求:
|
||
|
||
```typescript
|
||
import axios from 'axios'
|
||
import { ref, onMounted } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const data = ref(null)
|
||
|
||
onMounted(async () => {
|
||
const response = await axios.get('https://api.example.com/data')
|
||
data.value = response.data
|
||
})
|
||
|
||
return {
|
||
data
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
在这个例子中,我们使用 `async/await` 语法来处理异步的 HTTP 请求。注意,因为 `await` 只能在 `async` 函数中使用,所以我们需要在 `onMounted` 钩子中使用 `async`。
|
||
|
||
**使用 LocalStorage 进行本地存储**
|
||
|
||
虽然 LocalStorage 的操作是同步的,但如果你需要模拟异步操作,可以使用 Promise:
|
||
|
||
```typescript
|
||
import { ref, onMounted } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const count = ref(0)
|
||
|
||
onMounted(async () => {
|
||
const savedCount = await new Promise(resolve => {
|
||
setTimeout(() => {
|
||
resolve(localStorage.getItem('count'))
|
||
}, 1000)
|
||
})
|
||
|
||
count.value = savedCount || 0
|
||
})
|
||
|
||
return {
|
||
count
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
在这个例子中,我们使用 `setTimeout` 模拟异步操作,并使用 `Promise` 来处理这个异步操作。在异步操作完成后,我们更新 `count` 的值。
|
||
|
||
**使用 LocalStorage 进行本地存储**
|
||
|
||
在浏览器中,我们可以使用 LocalStorage 对象进行本地存储。以下是一个简单的例子:
|
||
|
||
```javascript
|
||
import { ref, watch } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const count = ref(localStorage.getItem('count') || 0)
|
||
|
||
watch(count, newValue => {
|
||
localStorage.setItem('count', newValue)
|
||
})
|
||
|
||
return {
|
||
count
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
在这个例子中,我们首先从 LocalStorage 中获取 `count` 的值。然后,我们使用 `watch()` 函数监听 `count` 的变化,并在 `count` 变化时更新 LocalStorage 中的值。
|
||
|
||
**使用 Vue 3 提供的生命周期钩子**
|
||
|
||
Vue 3 提供了一些生命周期钩子,如 `onMounted()`、`onUpdated()` 和 `onUnmounted()`,可以在组件的生命周期的不同阶段进行特定的操作。以下是一个简单的例子:
|
||
|
||
```javascript
|
||
import { onMounted, ref } from 'vue'
|
||
|
||
export default {
|
||
setup() {
|
||
const data = ref(null)
|
||
|
||
onMounted(() => {
|
||
data.value = 'Hello, Vue 3'
|
||
})
|
||
|
||
return {
|
||
data
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
在这个例子中,我们在组件挂载后更新 `data` 的值。
|
||
|
||
**7. 使用 Vue 3.0 的新功能 `Suspense` 进行异步依赖处理**
|
||
|
||
Vue 3.0 引入了一个新的内置组件 `Suspense`,它可以帮助我们处理组件中的异步依赖。
|
||
|
||
当一个组件的 `setup` 函数返回一个 Promise 时(这通常意味着该组件有异步操作),我们可以用 `Suspense` 组件包裹这个组件。`Suspense` 组件有两个插槽:`default` 插槽和 `fallback` 插槽。当异步组件处于等待状态时,`fallback` 插槽的内容会被渲染;当异步组件的 Promise 解析完成时,`default` 插槽的内容会被渲染。
|
||
|
||
下面是一个使用 `Suspense` 处理异步组件的例子:
|
||
|
||
```html
|
||
<template>
|
||
<Suspense>
|
||
<template #default>
|
||
<AsyncComponent />
|
||
</template>
|
||
<template #fallback>
|
||
<div>Loading...</div>
|
||
</template>
|
||
</Suspense>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent, ref, onMounted } from 'vue'
|
||
import axios from 'axios'
|
||
|
||
const AsyncComponent = defineComponent({
|
||
async setup() {
|
||
const data = ref(null)
|
||
|
||
// 这里模拟一个异步操作
|
||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||
|
||
const response = await axios.get('https://api.example.com/data')
|
||
data.value = response.data
|
||
|
||
return {
|
||
data
|
||
}
|
||
},
|
||
|
||
template: `
|
||
<div>
|
||
<p>Data: {{ data }}</p>
|
||
</div>
|
||
`,
|
||
})
|
||
|
||
export default {
|
||
components: {
|
||
AsyncComponent
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
在这个例子中,我们创建了一个异步组件 `AsyncComponent`,它的 `setup` 函数返回一个 Promise。我们使用 `Suspense` 组件包裹 `AsyncComponent`,并提供了 `fallback` 插槽,当 `AsyncComponent` 的数据正在加载时,`fallback` 插槽的内容会被渲染。
|