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.
 
 
 
 

910 lines
16 KiB

# 基础
```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>
```