Skip to content

0x00a-Vue3用法拾遗

模板语法

v-bind

html
<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 值的名称相同,可以省略等号,例如:

html
<div v-bind:id="id"></div>

<div :id></div>
<div v-bind:id="id"></div>

<div :id></div>

动态绑定

html
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

相似地,你还可以将一个函数绑定到动态的事件名称上:

html
<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>

响应式基础

ref

js
<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 的编译时语法糖,以上代码相当于:

js
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 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到

js
<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() 将使对象本身具有响应性:

js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
import { reactive } from 'vue'
const state = reactive({ count: 0 })

获取原始值

toRaw() 可以返回由 reactive()readonly()shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

js
const result = toRaw(xx);
const result = toRaw(xx);

使用JSON序列化也可以: JSON.parse(JSON.stringify(xxx))

双向绑定的几种写法

自定义双向绑定(vue3.4)

单个绑定

示例:

js
// 子组件
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绑定:

js
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中定义typedefault等:

js
const model1 = defineModel("count1", {
  type: String,
  default: "aaa",
});
const model1 = defineModel("count1", {
  type: String,
  default: "aaa",
});

参考:一文搞懂 Vue3 defineModel 双向绑定:告别繁琐代码

自定义双向绑定(vue3.4以下)

vue3.4版本以下的需要使用defineProps、defineEmits:

js
<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 函数。

js
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

js
<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,或者对被监听对象整体赋值。

ts
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> 中声明的绑定。

例如外部需要调用某个子组件的方法,需要将方法暴露出来

js
defineExpose({yourFuncName})
defineExpose({yourFuncName})

vue route

如何在组件外使用:

组件内使用:

事件修饰符

和vue2基本相同

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
html
<!-- 单击事件将停止传递 -->
<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>

回顾事件冒泡和捕获

冒泡和捕获: https://zh.javascript.info/bubbling-and-capturing

事件冒泡

If you click on 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

js
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

  1. 静态路径
js
<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>
  1. 动态路径
js
<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>

为什么可以这么处理:

import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL:
对于URL: 如果url参数是相对 URL,则构造函数将使用url参数和可选的base参数作为基础, 例如:

js
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
  1. 具有动态URL和绝对路径。 必须将别名@/替换为/src
js
<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 属性等地方。

示例:

javascript
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 的导入

如果你在导入时没有加任何查询参数,例如:

javascript
import logoText2 from "@/assets/layouts/logo-text-2.png";
import logoText2 from "@/assets/layouts/logo-text-2.png";

默认情况下,图片等静态资源的处理可能会将其作为模块处理。这意味着 Vite 可能会返回这个模块的内容,或者如果有其他的 loader 配置,它可能返回不同的形式(比如图像的字符串,或其他类型的处理)。

常用代码段

onMounted

js
onMounted(() => {
})
onMounted(() => {
})

其他问题

vue3 路由跳转空白,刷新页面不会空白

没有给route-view动态添加key。 参考: https://juejin.cn/post/7200935271683407930

vue3 router keepalive问题

.