alioth/before/cha/14=vue3/docs/学习vue3/vue3.md
2025-05-30 09:18:01 +08:00

911 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 基础
```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')
```
---
## 模板语法
> 如果偏好使用JavaScriptJSX 支持直接**手写渲染函数**而不采用模板。但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>
```