You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
911 lines
16 KiB
911 lines
16 KiB
4 months ago
|
# 基础
|
||
|
|
||
|
```bash
|
||
|
npm create vue@latest
|
||
|
```
|
||
|
|
||
|
## 创建应用
|
||
|
|
||
|
### 应用配置
|
||
|
|
||
|
> **确保在挂载应用实例之前完成所有应用配置!**
|
||
|
|
||
|
- 应用级的错误处理器,用来捕获所有子组件上的错误
|
||
|
|
||
|
```javascript
|
||
|
app.config.errorHandler = (err) => {
|
||
|
/* 处理错误 */
|
||
|
}
|
||
|
```
|
||
|
|
||
|
- 注册全局资源,**注册一个组件**
|
||
|
|
||
|
```javascript
|
||
|
app.component('TodoDeleteButton', TodoDeleteButton)
|
||
|
```
|
||
|
|
||
|
### 多实例
|
||
|
|
||
|
```javascript
|
||
|
const app1 = createApp({
|
||
|
/* ... */
|
||
|
})
|
||
|
app1.mount('#container-1')
|
||
|
|
||
|
const app2 = createApp({
|
||
|
/* ... */
|
||
|
})
|
||
|
app2.mount('#container-2')
|
||
|
```
|
||
|
|
||
|
---
|
||
|
|
||
|
## 模板语法
|
||
|
|
||
|
> 如果偏好使用JavaScript,JSX 支持直接**手写渲染函数**而不采用模板。但JSX不会享受到和模板同等级别的**编译时优化**。
|
||
|
|
||
|
### 文本插值
|
||
|
|
||
|
```vue
|
||
|
<span>Message: {{ msg }}</span>
|
||
|
```
|
||
|
|
||
|
### 使用原始HTML,不被vue编译
|
||
|
|
||
|
```vue
|
||
|
<p>Using text interpolation: {{ rawHtml }}</p>
|
||
|
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
|
||
|
```
|
||
|
|
||
|
![原始HTML](./vue3.assets/c0eed55cafc0418c813d4a6e77ed2835.png)
|
||
|
|
||
|
![安全警告](./vue3.assets/3b3bd03e11464112b94ed3afd66e7fbf.png)
|
||
|
|
||
|
### Attribute 绑定
|
||
|
|
||
|
```vue
|
||
|
<div v-bind:id="dynamicId"></div>
|
||
|
<!--简写-->
|
||
|
<div :id="dynamicId"></div>
|
||
|
|
||
|
<!--同名简写 版本>=3.4-->
|
||
|
<!-- 与 :id="id" 相同 -->
|
||
|
<div :id></div>
|
||
|
|
||
|
<!-- 这也同样有效 -->
|
||
|
<div v-bind:id></div>
|
||
|
```
|
||
|
|
||
|
### _布尔型 Attribute ???_
|
||
|
|
||
|
当 isButtonDisabled 为真值或一个空字符串 (即 `<button disabled="">`) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。
|
||
|
|
||
|
### 动态绑定多个值
|
||
|
|
||
|
```vue
|
||
|
const objectOfAttrs = {
|
||
|
id: 'container',
|
||
|
class: 'wrapper'
|
||
|
}
|
||
|
<div v-bind="objectOfAttrs"></div>
|
||
|
```
|
||
|
|
||
|
### 使用 JavaScript 表达式
|
||
|
|
||
|
```vue
|
||
|
{{ number + 1 }}
|
||
|
|
||
|
{{ ok ? 'YES' : 'NO' }}
|
||
|
|
||
|
{{ message.split('').reverse().join('') }}
|
||
|
|
||
|
<div :id="`list-${id}`"></div>
|
||
|
```
|
||
|
|
||
|
### 调用函数
|
||
|
|
||
|
```vue
|
||
|
<time :title="toTitleDate(date)" :datetime="date">
|
||
|
{{ formatDate(date) }}
|
||
|
</time>
|
||
|
```
|
||
|
|
||
|
### 受限的全局访问
|
||
|
|
||
|
- Math 和 Date可以在标签中使用,其他的需要在配置中注册,`app.config.globalProperties`
|
||
|
|
||
|
```js
|
||
|
app.config.globalProperties.msg = 'hello'
|
||
|
|
||
|
export default {
|
||
|
mounted() {
|
||
|
console.log(this.msg) // 'hello'
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 动态参数
|
||
|
|
||
|
```vue
|
||
|
<!--
|
||
|
注意,参数表达式有一些约束,
|
||
|
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
|
||
|
-->
|
||
|
<a v-bind:[attributeName]="url"> ... </a>
|
||
|
|
||
|
<!-- 简写 -->
|
||
|
<a :[attributeName]="url"> ... </a>
|
||
|
```
|
||
|
|
||
|
### 动态事件
|
||
|
|
||
|
```vue
|
||
|
<a v-on:[eventName]="doSomething"> ... </a>
|
||
|
|
||
|
<!-- 简写 -->
|
||
|
<a @[eventName]="doSomething"> ... </a>
|
||
|
```
|
||
|
|
||
|
- 不要这样写
|
||
|
- 动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
|
||
|
```vue
|
||
|
<!-- 这会触发一个编译器警告 -->
|
||
|
<template>
|
||
|
<!-- <a :['foo' + bar]="value"> ... </a>-->
|
||
|
</template>
|
||
|
|
||
|
```
|
||
|
|
||
|
### _修饰符 Modifiers ?_
|
||
|
|
||
|
---
|
||
|
|
||
|
## 响应式基础
|
||
|
|
||
|
### 声明响应式状态
|
||
|
|
||
|
### ref的实际样子
|
||
|
|
||
|
```js
|
||
|
const count = ref(0)
|
||
|
|
||
|
console.log(count) // { value: 0 }
|
||
|
console.log(count.value) // 0
|
||
|
|
||
|
count.value++
|
||
|
console.log(count.value) // 1
|
||
|
```
|
||
|
|
||
|
### 使用ref
|
||
|
|
||
|
```js
|
||
|
import { ref } from 'vue'
|
||
|
|
||
|
export default {
|
||
|
setup() {
|
||
|
const count = ref(0)
|
||
|
|
||
|
function increment() {
|
||
|
// 在 JavaScript 中需要 .value
|
||
|
count.value++
|
||
|
}
|
||
|
|
||
|
// 不要忘记同时暴露 increment 函数
|
||
|
return {
|
||
|
count,
|
||
|
increment
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```vue
|
||
|
<div>{{ count }}</div>
|
||
|
<button @click="count++">
|
||
|
{{ count }}
|
||
|
</button>
|
||
|
```
|
||
|
|
||
|
### `<script setup>`
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref } from 'vue'
|
||
|
|
||
|
const count = ref(0)
|
||
|
|
||
|
function increment() {
|
||
|
count.value++
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<button @click="increment">
|
||
|
{{ count }}
|
||
|
</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### DOM 更新时机
|
||
|
|
||
|
```js
|
||
|
import { nextTick } from 'vue'
|
||
|
|
||
|
async function increment() {
|
||
|
count.value++
|
||
|
await nextTick()
|
||
|
// 现在 DOM 已经更新了
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### reactive()
|
||
|
|
||
|
- `reactive()` 将使对象本身具有响应性
|
||
|
|
||
|
```vue
|
||
|
|
||
|
<script setup>
|
||
|
import { reactive } from 'vue'
|
||
|
const state = reactive({ count: 0 })
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<button @click="state.count++">
|
||
|
{{ state.count }}
|
||
|
</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
- `reactive()` 返回的是一个原始对象的 Proxy
|
||
|
|
||
|
```js
|
||
|
const raw = {}
|
||
|
const proxy = reactive(raw)
|
||
|
|
||
|
// 代理对象和原始对象不是全等的
|
||
|
console.log(proxy === raw) // false
|
||
|
|
||
|
// 在同一个对象上调用 reactive() 会返回相同的代理
|
||
|
console.log(reactive(raw) === proxy) // true
|
||
|
|
||
|
// 在一个代理上调用 reactive() 会返回它自己
|
||
|
console.log(reactive(proxy) === proxy) // true
|
||
|
```
|
||
|
|
||
|
- **这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:**
|
||
|
|
||
|
```js
|
||
|
const proxy = reactive({})
|
||
|
|
||
|
const raw = {}
|
||
|
proxy.nested = raw
|
||
|
|
||
|
console.log(proxy.nested === raw) // false
|
||
|
```
|
||
|
|
||
|
### `reactive()` 的局限性
|
||
|
|
||
|
- 有限的值类型:不能持有如 string、number 或 boolean 这样的原始类型。
|
||
|
|
||
|
- 不能替换整个对象:
|
||
|
|
||
|
```js
|
||
|
let state = reactive({ count: 0 })
|
||
|
|
||
|
// 上面的 ({ count: 0 }) 引用将不再被追踪
|
||
|
// (响应性连接已丢失!)
|
||
|
state = reactive({ count: 1 })
|
||
|
```
|
||
|
|
||
|
- 对解构操作不友好
|
||
|
|
||
|
```js
|
||
|
const state = reactive({ count: 0 })
|
||
|
|
||
|
// 当解构时,count 已经与 state.count 断开连接
|
||
|
let { count } = state
|
||
|
// 不会影响原始的 state
|
||
|
count++
|
||
|
|
||
|
// 该函数接收到的是一个普通的数字
|
||
|
// 并且无法追踪 state.count 的变化
|
||
|
// 我们必须传入整个对象以保持响应性
|
||
|
callSomeFunction(state.count)
|
||
|
```
|
||
|
|
||
|
## 计算属性
|
||
|
|
||
|
### 基础示例
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { reactive, computed } from 'vue'
|
||
|
|
||
|
const author = reactive({
|
||
|
name: 'John Doe',
|
||
|
books: [
|
||
|
'Vue 2 - Advanced Guide',
|
||
|
'Vue 3 - Basic Guide',
|
||
|
'Vue 4 - The Mystery'
|
||
|
]
|
||
|
})
|
||
|
|
||
|
// 一个计算属性 ref
|
||
|
const publishedBooksMessage = computed(() => {
|
||
|
return author.books.length > 0 ? 'Yes' : 'No'
|
||
|
})
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<p>Has published books:</p>
|
||
|
<span>{{ publishedBooksMessage }}</span>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
- 计算属性有缓存,当响应值没变化时,计算属性不会执行,如果用函数的话,函数每次都会执行
|
||
|
|
||
|
```js
|
||
|
// now永远不会更新,因为他没有用到响应值
|
||
|
const now = computed(() => Date.now())
|
||
|
```
|
||
|
|
||
|
### 可写计算属性
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref, computed } from 'vue'
|
||
|
|
||
|
const firstName = ref('John')
|
||
|
const lastName = ref('Doe')
|
||
|
|
||
|
const fullName = computed({
|
||
|
// getter
|
||
|
get() {
|
||
|
return firstName.value + ' ' + lastName.value
|
||
|
},
|
||
|
// setter
|
||
|
set(newValue) {
|
||
|
// 注意:我们这里使用的是解构赋值语法
|
||
|
[firstName.value, lastName.value] = newValue.split(' ')
|
||
|
}
|
||
|
})
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
## 类与样式绑定
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
const isActive = ref(true)
|
||
|
const hasError = ref(false)
|
||
|
</script>
|
||
|
<template>
|
||
|
<div
|
||
|
class="static"
|
||
|
:class="{ active: isActive, 'text-danger': hasError }"
|
||
|
></div>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
渲染结果:
|
||
|
|
||
|
`<div class="static active"></div>`
|
||
|
|
||
|
### 组件的类会叠加
|
||
|
|
||
|
```vue
|
||
|
<!-- 子组件模板 -->
|
||
|
<p class="foo bar">Hi!</p>
|
||
|
|
||
|
<!-- 在使用组件时 -->
|
||
|
<MyComponent class="baz boo" />
|
||
|
|
||
|
<!--渲染出的 HTML 为:-->
|
||
|
<p class="foo bar baz boo">Hi!</p>
|
||
|
```
|
||
|
|
||
|
## 条件渲染基本没变
|
||
|
|
||
|
```vue
|
||
|
<template v-if="ok">
|
||
|
<h1>Title</h1>
|
||
|
<p>Paragraph 1</p>
|
||
|
<p>Paragraph 2</p>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
## 列表渲染
|
||
|
|
||
|
### `v-for`可以遍历对象,顺序基于Object.keys()
|
||
|
|
||
|
### 使用范围值 【整数值】
|
||
|
|
||
|
```vue
|
||
|
<!--n从1开始-->
|
||
|
<span v-for="n in 10">{{ n }}</span>
|
||
|
```
|
||
|
|
||
|
### `<template>` 上也可以用 v-for
|
||
|
|
||
|
## 事件处理
|
||
|
|
||
|
- `v-on` 指令 (简写为 `@`)
|
||
|
|
||
|
### 内联事件处理器
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
const count = ref(0)
|
||
|
</script>
|
||
|
<template>
|
||
|
<button @click="count++">Add 1</button>
|
||
|
<p>Count is: {{ count }}</p>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### 方法事件处理器
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
const name = ref('Vue.js')
|
||
|
|
||
|
function greet(event) {
|
||
|
alert(`Hello ${name.value}!`)
|
||
|
// `event` 是 DOM 原生事件
|
||
|
if (event) {
|
||
|
alert(event.target.tagName)
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
<template>
|
||
|
<!-- `greet` 是上面定义过的方法名 -->
|
||
|
<button @click="greet">Greet</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### 在内联事件处理器中访问事件参数
|
||
|
|
||
|
- `$event`
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
function warn(message, event) {
|
||
|
// 这里可以访问原生事件
|
||
|
if (event) {
|
||
|
event.preventDefault()
|
||
|
}
|
||
|
alert(message)
|
||
|
}
|
||
|
</script>
|
||
|
<template>
|
||
|
<!-- 使用特殊的 $event 变量 -->
|
||
|
<button @click="warn('Form cannot be submitted yet.', $event)">
|
||
|
Submit
|
||
|
</button>
|
||
|
|
||
|
<!-- 使用内联箭头函数 -->
|
||
|
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
|
||
|
Submit
|
||
|
</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### 事件修饰符
|
||
|
|
||
|
- .stop
|
||
|
- .prevent
|
||
|
- .self
|
||
|
- .capture
|
||
|
- .once
|
||
|
- .passive
|
||
|
|
||
|
```vue
|
||
|
<!-- 单击事件将停止传递 -->
|
||
|
<a @click.stop="doThis"></a>
|
||
|
|
||
|
<!-- 提交事件将不再重新加载页面 -->
|
||
|
<form @submit.prevent="onSubmit"></form>
|
||
|
|
||
|
<!-- 修饰语可以使用链式书写 -->
|
||
|
<a @click.stop.prevent="doThat"></a>
|
||
|
|
||
|
<!-- 也可以只有修饰符 -->
|
||
|
<form @submit.prevent></form>
|
||
|
|
||
|
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
|
||
|
<!-- 例如:事件处理器不来自子元素 -->
|
||
|
<div @click.self="doThat">...</div>
|
||
|
```
|
||
|
|
||
|
![](./vue3.assets/8761f9becf244a3e93973c3a03858dc6.png)
|
||
|
|
||
|
`.capture`、`.once` 和 `.passive` 修饰符与原生 addEventListener 事件相对应:
|
||
|
|
||
|
```vue
|
||
|
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
|
||
|
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
|
||
|
<div @click.capture="doThis">...</div>
|
||
|
|
||
|
<!-- 点击事件最多被触发一次 -->
|
||
|
<a @click.once="doThis"></a>
|
||
|
|
||
|
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
|
||
|
<!-- 以防其中包含 `event.preventDefault()` -->
|
||
|
<div @scroll.passive="onScroll">...</div>
|
||
|
```
|
||
|
|
||
|
`.passive` 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
|
||
|
|
||
|
![](./vue3.assets/48618779e6cc4d9c82bacbc11a59dcae.png)
|
||
|
|
||
|
|
||
|
### 按键修饰符
|
||
|
|
||
|
- **$event.key**
|
||
|
|
||
|
- 按键别名
|
||
|
- .enter
|
||
|
- .tab
|
||
|
- .delete (捕获“Delete”和“Backspace”两个按键)
|
||
|
- .esc
|
||
|
- .space
|
||
|
- .up
|
||
|
- .down
|
||
|
- .left
|
||
|
- .right
|
||
|
- 系统按键修饰符
|
||
|
- .ctrl
|
||
|
- .alt
|
||
|
- .shift
|
||
|
- .meta
|
||
|
|
||
|
```vue
|
||
|
<!-- Alt + Enter -->
|
||
|
<input @keyup.alt.enter="clear" />
|
||
|
|
||
|
<!-- Ctrl + 点击 -->
|
||
|
<div @click.ctrl="doSomething">Do something</div>
|
||
|
```
|
||
|
|
||
|
### `.exact` 修饰符允许精确控制触发事件所需的系统修饰符的组合。
|
||
|
|
||
|
```vue
|
||
|
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
|
||
|
<button @click.ctrl="onClick">A</button>
|
||
|
|
||
|
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
|
||
|
<button @click.ctrl.exact="onCtrlClick">A</button>
|
||
|
|
||
|
<!-- 仅当没有按下任何系统按键时触发 -->
|
||
|
<button @click.exact="onClick">A</button>
|
||
|
```
|
||
|
|
||
|
### 鼠标按键修饰符
|
||
|
|
||
|
- .left
|
||
|
- .right
|
||
|
- .middle
|
||
|
|
||
|
## 生命周期
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { onMounted } from 'vue'
|
||
|
|
||
|
onMounted(() => {
|
||
|
console.log(`the component is now mounted.`)
|
||
|
})
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
![生命周期](./vue3.assets/ecdb8a0d231140ab8584b83992c41b62.png)
|
||
|
|
||
|
## 侦听器
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref, watch } from 'vue'
|
||
|
const x = ref(0)
|
||
|
const y = ref(0)
|
||
|
|
||
|
// 单个 ref
|
||
|
watch(x, (newX, oldX) => {
|
||
|
console.log(`x is ${newX}`)
|
||
|
})
|
||
|
|
||
|
// getter 函数
|
||
|
watch(
|
||
|
() => x.value + y.value,
|
||
|
(sum) => {
|
||
|
console.log(`sum of x + y is: ${sum}`)
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// 多个来源组成的数组
|
||
|
watch([x, () => y.value], ([newX, newY]) => {
|
||
|
console.log(`x is ${newX} and y is ${newY}`)
|
||
|
})
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
- 不能直接侦听响应式对象的属性值
|
||
|
|
||
|
```js
|
||
|
const obj = reactive({ count: 0 })
|
||
|
|
||
|
// 错误,因为 watch() 得到的参数是一个 number
|
||
|
watch(obj.count, (count) => {
|
||
|
console.log(`count is: ${count}`)
|
||
|
})
|
||
|
|
||
|
// 提供一个 getter 函数
|
||
|
watch(
|
||
|
() => obj.count,
|
||
|
(count) => {
|
||
|
console.log(`count is: ${count}`)
|
||
|
}
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### 深层侦听器 `{ deep: true }`
|
||
|
|
||
|
```js
|
||
|
const obj = reactive({ count: 0 })
|
||
|
|
||
|
watch(obj, (newValue, oldValue) => {
|
||
|
// 在嵌套的属性变更时触发
|
||
|
// 注意:`newValue` 此处和 `oldValue` 是相等的
|
||
|
// 因为它们是同一个对象!
|
||
|
})
|
||
|
|
||
|
watch(
|
||
|
() => state.someObject,
|
||
|
(newValue, oldValue) => {
|
||
|
// 注意:`newValue` 此处和 `oldValue` 是相等的
|
||
|
// *除非* state.someObject 被整个替换了
|
||
|
},
|
||
|
{ deep: true }
|
||
|
)
|
||
|
|
||
|
obj.count++
|
||
|
```
|
||
|
|
||
|
![](./vue3.assets/d791b0e7431b492b9d5dafe186d884b0.png)
|
||
|
|
||
|
### 即时回调的侦听器,**在创建侦听器时,立即执行一遍回调** `{ immediate: true }`
|
||
|
|
||
|
```js
|
||
|
watch(
|
||
|
source,
|
||
|
(newValue, oldValue) => {
|
||
|
// 立即执行,且当 `source` 改变时再次执行
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### 一次性侦听器 (版本>=3.4)
|
||
|
|
||
|
```js
|
||
|
watch(
|
||
|
source,
|
||
|
(newValue, oldValue) => {
|
||
|
// 当 `source` 变化时,仅触发一次
|
||
|
},
|
||
|
{ once: true }
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### `watchEffect()`, 如果函数体内有响应式参数被调用,则会自动执行,且会立即执行一次
|
||
|
|
||
|
```js
|
||
|
watchEffect(async () => {
|
||
|
const response = await fetch(
|
||
|
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
|
||
|
)
|
||
|
data.value = await response.json()
|
||
|
})
|
||
|
```
|
||
|
|
||
|
### _回调的触发时机 ?_
|
||
|
|
||
|
### 停止侦听器
|
||
|
|
||
|
```js
|
||
|
const unwatch = watchEffect(() => {})
|
||
|
|
||
|
// ...当该侦听器不再需要时
|
||
|
unwatch()
|
||
|
```
|
||
|
|
||
|
## 模板引用
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref, onMounted } from 'vue'
|
||
|
|
||
|
// 声明一个 ref 来存放该元素的引用
|
||
|
// 必须和模板里的 ref 同名
|
||
|
const input = ref(null)
|
||
|
|
||
|
onMounted(() => {
|
||
|
input.value.focus()
|
||
|
})
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<input ref="input" />
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### `v-for` 中的模板引用
|
||
|
|
||
|
- 应该注意的是,ref 数组并不保证与源数组相同的顺序。
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref, onMounted } from 'vue'
|
||
|
|
||
|
const list = ref([
|
||
|
/* ... */
|
||
|
])
|
||
|
|
||
|
const itemRefs = ref([])
|
||
|
|
||
|
onMounted(() => console.log(itemRefs.value))
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<ul>
|
||
|
<li v-for="item in list" ref="itemRefs">
|
||
|
{{ item }}
|
||
|
</li>
|
||
|
</ul>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### 函数模板引用
|
||
|
|
||
|
```vue
|
||
|
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
|
||
|
```
|
||
|
|
||
|
### 组件上的 ref
|
||
|
|
||
|
- `setup`组合式组件,需要手动暴露子组件的变量
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref } from 'vue'
|
||
|
|
||
|
const a = 1
|
||
|
const b = ref(2)
|
||
|
|
||
|
// 像 defineExpose 这样的编译器宏不需要导入
|
||
|
defineExpose({
|
||
|
a,
|
||
|
b
|
||
|
})
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
## 组件基础
|
||
|
|
||
|
### 单文件组件 (简称 SFC) `.vue`
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
import { ref } from 'vue'
|
||
|
|
||
|
const count = ref(0)
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<button @click="count++">You clicked me {{ count }} times.</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### `.js`
|
||
|
|
||
|
```js
|
||
|
import { ref } from 'vue'
|
||
|
|
||
|
export default {
|
||
|
setup() {
|
||
|
const count = ref(0)
|
||
|
return { count }
|
||
|
},
|
||
|
template: `
|
||
|
<button @click="count++">
|
||
|
You clicked me {{ count }} times.
|
||
|
</button>`
|
||
|
// 也可以针对一个 DOM 内联模板:
|
||
|
// template: '#my-template-element'
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 传递 props
|
||
|
|
||
|
```vue
|
||
|
<!-- BlogPost.vue -->
|
||
|
<script setup>
|
||
|
defineProps(['title'])
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<h4>{{ title }}</h4>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
- 如果你没有使用 <script setup>,props 必须以 props 选项的方式声明,props 对象会作为 setup() 函数的第一个参数被传入:
|
||
|
|
||
|
```js
|
||
|
export default {
|
||
|
props: ['title'],
|
||
|
setup(props) {
|
||
|
console.log(props.title)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 自定义监听事件
|
||
|
|
||
|
```vue
|
||
|
<script setup>
|
||
|
defineProps(['title'])
|
||
|
defineEmits(['enlarge-text'])
|
||
|
|
||
|
// 在setp中执行
|
||
|
// SFC组合式
|
||
|
const emit = defineEmits(['enlarge-text'])
|
||
|
|
||
|
emit('enlarge-text')
|
||
|
// 选项式
|
||
|
export default {
|
||
|
emits: ['enlarge-text'],
|
||
|
setup(props, ctx) {
|
||
|
ctx.emit('enlarge-text')
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
<template>
|
||
|
<button @click="$emit('enlarge-text')">Enlarge text</button>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
```vue
|
||
|
<!--父组件-->
|
||
|
<script setup>
|
||
|
function oneMethod(event){
|
||
|
|
||
|
}
|
||
|
</script>
|
||
|
<template>
|
||
|
<BlogPost @enlarge-text="oneMethod"/>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
### 插槽
|
||
|
|
||
|
```vue
|
||
|
<template>
|
||
|
<div class="alert-box">
|
||
|
<strong>This is an Error for Demo Purposes</strong>
|
||
|
<slot />
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<style scoped>
|
||
|
.alert-box {
|
||
|
/* ... */
|
||
|
}
|
||
|
</style>
|
||
|
```
|
||
|
|
||
|
### 动态组件
|
||
|
|
||
|
```vue
|
||
|
<!-- currentTab 改变时组件也改变 -->
|
||
|
<component :is="tabs[currentTab]"></component>
|
||
|
```
|