模板语法
v-bind
<div v-bind:id="dynamicId"></div>
或
<div :id="dynamicId"></div>
<div v-bind:id="dynamicId"></div>
或
<div :id="dynamicId"></div>
如果是vue在3.4版本以上,若 html attribute 的名称与绑定的 JavaScript 值的名称相同,可以省略等号,例如:
<div v-bind:id="id"></div>
或
<div :id></div>
<div v-bind:id="id"></div>
或
<div :id></div>
动态绑定
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
相似地,你还可以将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
响应式基础
ref
<script setup>
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
</script>
<template>
<h1>{{ count }}</h1>
<button @click="count++"> Click Me</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
</script>
<template>
<h1>{{ count }}</h1>
<button @click="count++"> Click Me</button>
</template>
1). <script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,以上代码相当于:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
2). 在组合式 API 中,推荐使用 ref()
函数来声明响应式状态。ref()
接收参数,并将其包裹在一个带有 .value
属性的 ref 对象中返回。
3). 在模板中使用 ref 时,我们不需要附加 .value
。
ref 深层
Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map
。
非原始值将通过 reactive()
转换为响应式代理。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
<script setup>
import { ref} from 'vue';
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
const addCount = () => {
obj.value.nested.count++;
}
</script>
<template>
<p>{{ obj.arr }}</p>
<p>{{ obj.nested.count }}</p>
<button @click="addCount">click to add count</button>
</template>
<script setup>
import { ref} from 'vue';
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
const addCount = () => {
obj.value.nested.count++;
}
</script>
<template>
<p>{{ obj.arr }}</p>
<p>{{ obj.nested.count }}</p>
<button @click="addCount">click to add count</button>
</template>
reactive()
还有另一种声明响应式状态的方式,即使用 reactive()
API。与将内部值包装在特殊对象中的 ref 不同,reactive()
将使对象本身具有响应性:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
import { reactive } from 'vue'
const state = reactive({ count: 0 })
获取原始值
toRaw()
可以返回由 reactive()
、 readonly()
、 shallowReactive()
或者 shallowReadonly()
创建的代理对应的原始对象。
const result = toRaw(xx);
const result = toRaw(xx);
使用JSON序列化也可以: JSON.parse(JSON.stringify(xxx))
双向绑定的几种写法
自定义双向绑定(vue3.4)
单个绑定
示例:
// 子组件
const model = defineModel<string>();
<el-input class="verifyCode" maxlength="6"
placeholder="请输入验证码" v-model="model">
</el-input>
// 父组件
<subView v-model="pmodel.xx" />
// 子组件
const model = defineModel<string>();
<el-input class="verifyCode" maxlength="6"
placeholder="请输入验证码" v-model="model">
</el-input>
// 父组件
<subView v-model="pmodel.xx" />
多个绑定
定义多个v-model绑定:
const model1 = defineModel("count1");
const model2 = defineModel("count2");
// 父组件多个
<CommonInput v-model:count1="inputValue1" />
<CommonInput v-model:count2="inputValue2" />
const model1 = defineModel("count1");
const model2 = defineModel("count2");
// 父组件多个
<CommonInput v-model:count1="inputValue1" />
<CommonInput v-model:count2="inputValue2" />
我们也可以在多个v-model
中定义type
、default
等:
const model1 = defineModel("count1", {
type: String,
default: "aaa",
});
const model1 = defineModel("count1", {
type: String,
default: "aaa",
});
参考:一文搞懂 Vue3 defineModel 双向绑定:告别繁琐代码
自定义双向绑定(vue3.4以下)
vue3.4版本以下的需要使用defineProps、defineEmits:
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup lang="ts">
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
// ts写法示例:
const emit = defineEmits<{
(event: 'update:modelValue', payload: string): void;
}>();
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup lang="ts">
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
// ts写法示例:
const emit = defineEmits<{
(event: 'update:modelValue', payload: string): void;
}>();
</script>
计算属性
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books
不改变,无论多少次访问 publishedBooksMessage
都会立即返回先前的计算结果,而不用重复执行 getter 函数。
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
计算属性的getter、setter
<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>
<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>
watch
需要注意的如果监听的是object需要使用deep,或者对被监听对象整体赋值。
const dateRange = ref<DateRangeType>({
start: null,
end: null
})
watch(dateRange, (newVal) => {
debugger
}, {deep: true})
const dateRange = ref<DateRangeType>({
start: null,
end: null
})
watch(dateRange, (newVal) => {
debugger
}, {deep: true})
defineExpos
使用
<script setup>
的组件是默认关闭的——即通过模板引用或者$parent
链获取到的组件的公开实例,不会暴露任何在<script setup>
中声明的绑定。
例如外部需要调用某个子组件的方法,需要将方法暴露出来
defineExpose({yourFuncName})
defineExpose({yourFuncName})
vue route
如何在组件外使用:
组件内使用:
事件修饰符
和vue2基本相同
.stop
.prevent
.self
.capture
.once
.passive
<!-- 单击事件将停止传递 -->
<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>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
<!-- 单击事件将停止传递 -->
<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>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
回顾事件冒泡和捕获
事件冒泡
EM
, the handler on DIV
runs.这是不是有点奇怪?如果实际上点击的是
<em>
,为什么在<div>
上的处理程序会运行?
冒泡(bubbling)原理很简单。
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
因此,如果我们点击 <p>
,那么我们将看到 3 个 alert:p
→ div
→ form
。
这个过程被称为“冒泡(bubbling)”,因为事件从内部元素“冒泡”到所有父级,就像在水里的气泡一样。
事件捕获
事件捕获是DOM事件传播的一个阶段,其中事件从根元素(如 window)向传递到目标元素。
可以通过设置事件监听器的参数,选择在捕获阶段还是在冒泡阶段处理事件。
• 事件的传播机制使得我们可以灵活地处理用户交互,构建动态和响应式的Web应用。
也就是说:点击 <td>
,事件首先通过祖先链向下到达元素(捕获阶段),然后到达目标(目标阶段),最后上升(冒泡阶段),在途中调用处理程序。
之前,我们只讨论了冒泡,因为捕获阶段很少被使用。通常我们看不到它。
使用 on<event>
属性或使用 HTML 特性(attribute)或使用两个参数的 addEventListener(event, handler)
添加的处理程序,对捕获一无所知,它们仅在第二阶段和第三阶段运行。
为了在捕获阶段捕获事件,我们需要将处理程序的 capture
选项设置为 true
:
elem.addEventListener(..., {capture: true})
// 或者,用 {capture: true} 的别名 "true"
elem.addEventListener(..., true)
elem.addEventListener(..., {capture: true})
// 或者,用 {capture: true} 的别名 "true"
elem.addEventListener(..., true)
Q&A
如何进行动态图片引入
https://stackoverflow.com/questions/66419471/vue-3-vite-dynamic-image-src
- 静态路径
<script setup>
import imageUrl from '@/assets/images/logo.svg' // => or relative path
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
<script setup>
import imageUrl from '@/assets/images/logo.svg' // => or relative path
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
- 动态路径
<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
为什么可以这么处理:
i
mport.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL:
对于URL: 如果url
参数是相对 URL,则构造函数将使用url
参数和可选的base
参数作为基础, 例如:
new URL('../cats', 'http://www.example.com/dogs').href;
// 得到: http://www.example.com/cats
new URL('../cats', 'http://www.example.com/dogs').href;
// 得到: http://www.example.com/cats
- 具有动态URL和绝对路径。 必须将别名
@/
替换为/src
<script setup>
const imageUrl = new URL('/src/assets/images/logo.svg', import.meta.url)
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
<script setup>
const imageUrl = new URL('/src/assets/images/logo.svg', import.meta.url)
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
vite资源引用带不带?url
的区别:
在 Vite 中,查询参数 ?url
的存在与否会影响如何处理导入的文件。以下是详细的解释和它的作用,以便您理解带与不带 ?url
的区别。
带 ?url
的导入:
当你使用 import logoText2 from "@/assets/layouts/logo-text-2.png?url"
时,这表示你希望 Vite 处理这个图像文件并返回它的 URL。这个 URL 会指向这个图像文件的位置,可以用于 src
属性等地方。
示例:
import logoText2 from "@/assets/layouts/logo-text-2.png?url";
// logoText2 是图像的 URL,可以直接用于 <img> 标签
import logoText2 from "@/assets/layouts/logo-text-2.png?url";
// logoText2 是图像的 URL,可以直接用于 <img> 标签
不带 ?url
的导入
如果你在导入时没有加任何查询参数,例如:
import logoText2 from "@/assets/layouts/logo-text-2.png";
import logoText2 from "@/assets/layouts/logo-text-2.png";
默认情况下,图片等静态资源的处理可能会将其作为模块处理。这意味着 Vite 可能会返回这个模块的内容,或者如果有其他的 loader 配置,它可能返回不同的形式(比如图像的字符串,或其他类型的处理)。
常用代码段
onMounted
onMounted(() => {
})
onMounted(() => {
})
其他问题
vue3 路由跳转空白,刷新页面不会空白
没有给route-view动态添加key。 参考: https://juejin.cn/post/7200935271683407930
vue3 router keepalive问题
.