Vue高级笔记

Vue高级笔记

大鱼 303 2023-05-22

1. Props 声明

  1. 声明类型
// 使用 <script setup>
defineProps({
  title: String,
  likes: Number
})
  1. 传递 prop 的细节
    如果一个 prop 的名字很长,应使用 camelCase 形式, 传递时为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式
defineProps({
  greetingMessage: String
})

<MyComponent greeting-message="hello" />
  1. 任何类型的值都可以作为 props 的值被传递。
  2. 使用一个对象绑定多个 prop
const post = {
  id: 1,
  title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />

# 实际上等价于
<BlogPost :id="post.id" :title="post.title" />
  1. 单向数据流
    不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告,
  2. Boolean 类型转换
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />

<!-- 等同于传入 :disabled="false" -->
<MyComponent />

2. 事件

  1. 触发与监听事件, 直接使用 $emit 方法触发自定义事件
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
<MyComponent @some-event="callback" />
# 事件监听器也支持 .once 修饰符:
<MyComponent @some-event.once="callback" />
  1. 事件参数, 直接使用 $emit 方法触发自定义事件, 传递参数
<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>
# 内联方法
<MyButton @increase-by="(n) => count += n" />
#或者调用独立的方法
<MyButton @increase-by="increaseCount" />
function increaseCount(n) {
  count.value += n
}
  1. 事件校验
<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>

3. 组件v-model

  1. 多个 v-model 双向绑定
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

<script setup>
defineProps({
  firstName: String,
  lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>
  1. v-model 修饰符
    .trim,.number 和 .lazy,或者自定义的修饰符

  2. 自定义修饰符

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>


# 绑定
<MyComponent v-model:title.capitalize="myText">

4. 透传 Attributes

  1. Attributes 继承
    当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。
<!-- <MyButton> 的模板 -->
<button>click me</button>

<MyButton class="large" />

# 最后渲染的结果是
<button class="large">click me</button>
  1. 对 class 和 style 的合并
    如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并。
  2. v-on 监听器继承
<MyButton @click="onClick" />

button 被点击,会触发父组件的 onClick 方法。同样的,如果原生 button 元素自身也通过 v-on 绑定了一个事件监听器,则这个监听器和从父组件继承的监听器都会被触发。
4. 深层组件继承
一个组件会在根节点上渲染另一个组件, 接收的透传 attribute 会直接继续传给下一层子节点
5. 禁用 Attributes 继承
不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
6. 这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。

  1. 在 JavaScript 中访问透传 Attributes
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

5. 插槽 Slots

  1. 基础使用
# 定义FancyButton
<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

<FancyButton>
  <span style="color:red">Click me!</span>
  <AwesomeIcon name="plus" />
</FancyButton>
  1. 作用域
    插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
  2. 具名插槽
# BaseLayout.vue
<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<style>
  footer {
    border-top: 1px solid #ccc;
    color: #666;
    font-size: 0.8em;
  }
</style>


# App.vue
<script setup>
import BaseLayout from './BaseLayout.vue'
</script>

<template>
  <BaseLayout>
    <template #header>
      <h1>Here might be a page title</h1>
    </template>

    <template #default>
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    </template>

    <template #footer>
      <p>Here's some contact info</p>
    </template>
  </BaseLayout>
</template>


  1. 动态插槽名
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>
  1. 插槽作用域, 访问子组件数据
<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
  1. 实用列表组件实例
    https://cn.vuejs.org/guide/components/slots.html#fancy-list-example
  2. 无渲染组件
#定义组件
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
  
const x = ref(0)
const y = ref(0)

const update = e => {
  x.value = e.pageX
  y.value = e.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>
  <slot :x="x" :y="y"/>
</template>


# 渲染
<template>
	<MouseTracker v-slot="{ x, y }">
  	Mouse is at: {{ x }}, {{ y }}
	</MouseTracker>
</template>

6. 依赖注入

  1. 从父组件向子组件传递数据时,会使用 props, 为了解决多层组件传递数据链路较长的问题, 引入依赖注入
  2. Provide (提供)
<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>


# 我们还可以在整个应用层面提供依赖
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
  1. Inject (注入)
<script setup>
import { inject } from 'vue'

const message = inject('message',  '这是默认值')
</script>
  1. 和响应式数据配合使用
    当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
    如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。

7. 异步组件

  1. 基本用法
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)
  1. 加载与错误状态
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

8.组合式函数

  1. 实际上就是封装一个方法, 暴露给外部使用
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}
  1. 异步状态返回
// fetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))

  return { data, error }
}

# 使用方
<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>

9. 自定义指令

  1. 自定义指令由一个包含类似组件生命周期钩子的对象来定义。
<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
  1. 常用指令钩子
const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}
  1. 钩子参数
el:指令绑定到的元素。这可以用于直接操作 DOM。

binding:一个对象,包含以下属性。
- value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
- oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
- arg:传递给指令的参数 (如果有的话)。例如在 v-my--directive:foo 中,参数是 "foo"。
- modifiers:一个包含修饰符的对象 (如果有的话)。例如在 - v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
- instance:使用该指令的组件实例。
- dir:指令的定义对象。

vnode:代表绑定元素的底层 VNode。

prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
  1. 对象字面量
<div v-demo="{ color: 'white', text: 'hello!' }"></div>

app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

10. 插件

  1. 插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码
import { createApp } from 'vue'

const app = createApp({})

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


const myPlugin = {
  install(app, options) {
    // 配置此应用
  }
}
  1. 插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:
  • 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令。
  • 通过 app.provide() 使一个资源可被注入进整个应用。
  • 向 app.config.globalProperties 中添加一些全局实例属性或方法
  • 一个可能上述三种都包含了的功能库 (例如 vue-router)。
  1. 编写一个插件
    实在没看懂

11. 内置组件

  1. Transition / TransitionGroup 动画, 详见地址
  2. KeepAlive, 在被“切走”的时候保留它们的状态
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>
  1. KeepAlive 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
  <component :is="view" />
</KeepAlive>

<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
  <component :is="view" />
</KeepAlive>

<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
  <component :is="view" />
</KeepAlive>
  1. 最大缓存实例数
<KeepAlive :max="10">
  <component :is="activeComponent" />
</KeepAlive>
  1. 缓存实例的生命周期