iOS

vue3 语法拾遗

在不断填坑中前进。。

Posted by 三十一 on July 30, 2024

vue3 语法拾遗

[TOC]

还没有完整的看过 官方文档,决定速读一下。如果碰到不知道的知识点在这里记录下。

模板语法相关

v-bind

完整写法

<div v-bind:id="dynamicId"></div> 简写 <div :id="dynamicId"></div>

如果key和value一致可以简写如下(v3.4+) <!-- same as :id="id" --> <div :id></div> <!-- this also works --> <div v-bind:id></div>

一次绑定多个值

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

<div v-bind="objectOfAttrs"></div>

使用解构

<li v-for="{ message } in items">
  
</li>

<!--  index 索引时 -->
<li v-for="({ message }, index) in items">
   
</li>

动态参数

<a v-bind:[attributeName]="url"> ... </a> <a :[attributeName]="url"> ... </a>

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 “href”,那么这个绑定就等价于 v-bind:href

同理,也可以绑定到事件上: <a v-on:[eventName]="doSomething"> ... </a> <a @[eventName]="doSomething">

在此示例中,当 eventName 的值是 “focus” 时,v-on:[eventName] 就等价于 v-on:focus

修饰符 Modifiers

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

<form @submit.prevent="onSubmit">...</form>

DOM 更新时机

当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。

import { nextTick } from 'vue'

async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}

可写的计算属性

<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>

类与样式绑定

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>

也可以是一个对象

const classObject = reactive({
  active: true,
  'text-danger': false
})

<div :class="classObject"></div>

也可以是一个计算属性

const isActive = ref(true)
const error = ref(null)

const classObject = computed(() => ({
  active: isActive.value && !error.value,
  'text-danger': error.value && error.value.type === 'fatal'
}))
<div :class="classObject"></div>

在组件中使用时

对于只有一个根元素的组件,当你使用了 class attribute 时,这些 class 会被添加到根元素上并与该元素上已有的 class 合并。

譬如:

<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>

<!-- 在使用组件时 -->
<MyComponent class="baz boo" />

最终会被渲染成

<p class="foo bar baz boo">Hi!</p>

内联样式

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})

<div :style="styleObject"></div>

$event

在内联事件处理器中访问原生 DOM 事件。

<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

事件修饰符

<!-- 单击事件将停止传递 -->
<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>

按键修饰符

<!-- 仅在 `key`  `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
<!--仅会在 $event.key  'PageDown' 时调用事件处理-->
<input @keyup.page-down="onPageDown" />

<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>

鼠标修饰键
  • .left
  • .right
  • .middle

值绑定

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 ‘yes’,取消选择时设为 ‘no’。你同样可以通过 v-bind 将其绑定为其他动态值:

<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

值限制

如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入: <input v-model.number="age" />

自动去除用户输入内容中两端的空格 <input v-model.trim="msg" />

模板引用

v-for 中的模板引用

当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:

<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">
      
    </li>
  </ul>
</template>

函数模板引用

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

组件

子组件通知父组件

父组件

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

子组件 @click="$emit('enlarge-text')"直接抛出一个事件

<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4></h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

动态组件

<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

TS 中定义 $emit

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

$emit 事件校验

<script setup>
const emit = defineEmits({
  // 没有校验
  click: null,

  // 校验 submit 事件
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

v-model

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:

<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>parent bound v-model is: </div>
</template>
<!-- Parent.vue -->
<Child v-model="count" />

defineModel() 返回的值是一个 ref。 它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:

  • 它的 .value 和父组件的 v-model 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新。
底层机制

defineModel 是一个便利宏。 编译器将其展开为以下内容:

  1. 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
  2. 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。

3.4 版本之前是这样实现

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

// 使 v-model 必填 const model = defineModel({ required: true })

// 提供一个默认值 const model = defineModel({ default: 0 })

v-model 参数
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
  
const title = ref('11111')
</script>

<template>
  <h1></h1>
  <MyComponent v-model:name="title" />
</template>
<!--MyComponent-->
<script setup>
const model = defineModel('name')
</script>

<template>
  <input type="text" v-model="model" />
</template>
多个model
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>


<!-- UserName.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>
带修饰符的model可以根据修饰符自定义操作

<MyComponent v-model.capitalize="myText" />

<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

透传 Attributes

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。

<!-- <MyButton> 的模板 -->
<button>click me</button>

<!--父组件调用-->
<MyButton class="large" />

最终渲染 <button class="large">click me</button>

禁用 Attributes 继承

3.3 开始你也可以直接在 <script setup> 中使用 defineOptions:

<script setup>
defineOptions({
  inheritAttrs: false
})
// ...setup 逻辑
</script>
在模板中使用 $attrs 访问属性

<span>Fallthrough attribute: </span>

slot 插槽

理解类似于

// 传入不同的内容给不同名字的插槽
BaseLayout({
  header: `...`,
  default: `...`,
  footer: `...`
})

// <BaseLayout> 渲染插槽内容到对应位置
function BaseLayout(slots) {
  return `<div class="container">
      <header>${slots.header}</header>
      <main>${slots.default}</main>
      <footer>${slots.footer}</footer>
    </div>`
}

```js vue

这是传入的内容,会替换掉默认内容 <template #default> 这里是向默认插槽填入的内容 </template> <template #footer> 这是具名插槽--footer </template>

##### 作用域插槽


```js
<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
   
</MyComponent>

自定义指令

简化形式

<div v-color="color"></div>


app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

插件

在插件里面处理一系列相关的逻辑,注册一组相关的组件、指令 提供全局的方法、数据

import { createApp } from 'vue'

const app = createApp({})

app.use(myPlugin, {
  /* 可选的选项 */
})


const myPlugin = {
  install(app, options) {
    // 配置此应用
  }
}

挂载全局方法

// plugins/i18n.js
export default {
  install: (app, options) => {
    // 注入一个全局可用的 $translate() 方法
    app.config.globalProperties.$translate = (key) => {
      // 获取 `options` 对象的深层属性
      // 使用 `key` 作为索引
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }
  }
}

提供全局参数 Provide / Inject

// plugins/i18n.js
export default {
  install: (app, options) => {
    app.provide('i18n', options)
  }
}

使用

<script setup>
import { inject } from 'vue'

const i18n = inject('i18n')

console.log(i18n.greetings.hello)
</script>

应用规模化

性能优化

页面加载优化

web.dev 指南

TS

defineProps

<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

withDefaults

默认值

export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

为组件的 emits 标注类型

<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])

// 基于选项
const emit = defineEmits({
  change: (id: number) => {
    // 返回 `true` 或 `false`
    // 表明验证通过或失败
  },
  update: (value: string) => {
    // 返回 `true` 或 `false`
    // 表明验证通过或失败
  }
})

// 基于类型
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()
</script>

为模板引用标注类型

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const el = ref<HTMLInputElement | null>(null)

onMounted(() => {
  el.value?.focus()
})
</script>

<template>
  <input ref="el" />
</template>

组件模板引用标注类型

<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const isContentShown = ref(false)
const open = () => (isContentShown.value = true)

defineExpose({
  open
})
</script>
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)

const openModal = () => {
  modal.value?.open()
}
</script>

const modal = ref<InstanceType<typeof MyModal> | null>(null)

通用的模板类型

import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'

const child = ref<ComponentPublicInstance | null>(null)

渲染函数 & JSX

基本使用

Vue 提供了一个 h() 函数用于创建 vnodes:

// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })

// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })

// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])

声明渲染函数

jsx

vite vue3 使用 tsx vite 需要安装插件

pnpm i @vitejs/plugin-vue-jsx

在vite.config.js加入jsx配置

// vite.config.js
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
})