💡简介
来自官网 https://cn.vuejs.org
1. 注册
在使用一个 Vue 组件之前,要先注册。
1.1 全局注册
在全局注册的组件,可以在此应用的任意组件的模板中使用。
使用应用实例的 app.component() 方法:
app
  .component("ComponentA", ComponentA)
  .component("ComponentB", ComponentB)
  .component("ComponentC", ComponentC);全局注册的问题:
- 在打包的时候不能被 tree-shaking 
- 在大型项目中让项目的依赖关系变得不那么明确 
- 可能会影响项目的后续维护,类似使用过多的全局变量一样 
1.2 局部注册
在使用它的父组件中显式导入,只能在父组件中使用,不能在后代组件中使用。
- 在使用 - <script setup>的单文件组件中,导入的组件可以直接在模板中使用,无需注册
- 否则就需要使用 - components选项来显式注册
<script setup>
  // 导入后可直接使用,不用注册
  import ComponentA from "./ComponentA.vue";
</script>
<template>
  <ComponentA />
</template>import ComponentA from "./ComponentA.js"  // 导入
export default {
  components: {  // 显式注册
    ComponentA,
    ComponentA: ComponentA // 写法等价
  },
  setup() {
    // ...
  }
};局部注册的优点:
- 使组件之间的依赖关系更加明确 
- 并且对 tree-shaking 更加友好 
1.3 组件名
建议使用 PascalCase(驼峰式)<PascalCase /> :
- 可在模板中区分原生 HTML 元素和 web components(单文件组件和内联字符串模板) 
- 在 DOM 模板中 PascalCase 标签名是不可用的 
2. Props 声明
一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute。
2.1 方式
- 使用 - defineProps()宏
- 使用 - props选项
这声明方式的背后使用的都是 prop 选项,它们都分别支持数组格式和对象格式。比如:
<!-- 比如:defineProps() 和数组格式 -->
<script setup>
  const props = defineProps(["foo"]);
  console.log(props.foo);
</script>// 比如:props 选项和对象格式
export default {
  props: {
    title: String,
    likes: Number
  },
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.title);
  }
};其中,对象格式的 prop,包括了名称+预期类型的构造函数(方便 prop 校验)。构造函数可以是原生构造函数,也可以是自定义的类或构造函数。
2.2 名字和值
- prop 的名字 - camelCase 式(DOM 模板除外)的好处(但优势不明显) - 是合法的 JavaScript 标识符,可以直接在模板的表达式中用 
- 可以避免在作为属性 key 名时必须加引号 
 
- 推荐用 kebab-case 形式,可以和 HTML attribute 对齐 - <BlogPost title="My journey with Vue" />与默认 attribute 对齐了更舒服些
 
 
- prop 的值:任何类型的值 
2.3 单向数据流
props 只能从父到子组件,而不会逆向传递。
如果想要更改一个 prop,可以用以下方式:
- prop 用来接收初始值,子组件再定义个局部数据属性 
- 基于 prop 的计算属性 
- 子组件抛出个事件来通知父组件更改数据(尤其是对引用型的 prop) 
3. 事件
3.1 触发和监听
- 触发和监听事件:在 - v-on(简写为- @)上- 子组件:触发自定义事件,通过 - $emit()方法
- 父组件:监听事件 
 
- 关于组件自己触发的事件的说明 - 格式支持自动转换,用 camelCase 触发,用 kebab-case 来监听(同组件的 prop) 
- 支持事件修饰符、可以带参数 
- 重名:如果和原生事件重名,则监听器只会监听组件触发的那个,而不再响应原生事件 
- 没有冒泡:只能监听直接子组件触发的事件 - 如果要在平级组件或是跨越多层嵌套的组件间通信,可以 - 使用一个外部的事件总线 
- 使用一个全局状态管理方案 
 
 
 
<!-- 子组件触发 -->
<button @click="$emit('someEvent')">click me</button>
<button @click="$emit('increaseBy', 1)">+1</button>
<!-- 父组件监听 -->
<MyComponent @some-event="callback" />
<MyButton @increase-by="(n) => count += n" />
<MyButton @increase-by="increaseCount" />3.2 声明要触发的事件
推荐总是显式地声明组件要触发的事件:
- 若是 - <script setup>则用- defineEmits()宏
- 若是 - setup()则用- emits选项
- 同样,支持数组、对象(对触发事件的参数进行验证) 
<script setup>
  const emit = defineEmits(["inFocus", "submit"]);
  function buttonClick() {
    emit("submit");
  }
</script>export default {
  emits: ["inFocus", "submit"],
  setup(props, ctx) {
    ctx.emit("submit");
  }
};
// setup() 参数解构
export default {
  emits: ["inFocus", "submit"],
  setup(props, { emit }) {
    emit("submit");
  }
};3.3 让组件支持 v-model 
v-model 两种方法:
- 子组件内部要做的两件事 - 将内部原生 - input元素的- valueattribute 绑定到- modelValueprop
- 输入新的值时在 - input元素上触发- update:modelValue事件
 
- 子组件使用一个可写的,同时具有 getter 和 setter 的计算属性 - get方法需返回- modelValueprop
- set方法需触发相应的事件
 
<!-- 父组件 -->
<CustomInput v-model="message" />eg. 方法一
<!-- 子组件 -->
<script setup>
  defineProps(["modelValue"]);
  defineEmits(["update:modelValue"]);
</script>
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>eg. 方法二
<!-- 子组件 -->
<script setup>
  import { computed } from "vue";
  const props = defineProps(["modelValue"]);
  const emit = defineEmits(["update:modelValue"]);
  const value = computed({
    get() {
      return props.modelValue;
    },
    set(value) {
      emit("update:modelValue", value);
    }
  });
</script>
<template>
  <input v-model="value" />
</template>默认情况,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字,这样就能轻松实现多个 v-model 绑定了。
<CustomInput v-model:title="message" />此外,我们还可以让一个自定义组件的 v-model 支持自定义的修饰符。
4. 透传 attributes
- 自动透传 + 自动连续透传 + 自动合并 - 不包括组件自己声明为 - props和- emits的 attribute
- 自动合并:从父元素上继承的和子元素根元素自己的 
 
- 禁用 Attributes 继承: - inheritAttrs: false- 适用场景:需应用在根节点以外的其他元素上 
- 访问方法: - 模板:可用 - $attrs访问到
- 在 - <script setup>中使用- useAttrs()API
- 在 - setup()函数中是上下文对象的一个属性- ctx.attrs
 
 
- 注意事项: - 透传 attributes 在 JavaScript 中保留了它们原始的大小写,不同于 props 
- v-on事件- @click在数据上会显示为- $attrs.onClick
- 多个根节点的组件:没有自动 attribute 透传行为,所以需要我们显式绑定 
- attr对象不是响应式的(考虑到性能因素)。若想要响应式,可用 prop 或- onUpdated()
 
<!-- 没有参数的 `v-bind` 会将一个对象的所有属性都作为 attribute 应用到目标元素上 -->
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div><!-- 多根节点模板 -->
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>5. 插槽
插槽 <slot> 可以让组件接收模板内容。
- slot outlet,插槽出口,子组件里收 
- slot content,插槽内容,父组件里传 
插槽内容可以是任意合法的模板内容,比如文本、多个元素、其它组件等,插槽让组件更加灵活和更具有可复用性。
5.1 slot outlet 和 slot content
- 默认内容 
- 具名插槽:静态名字 + 动态名字 - 收: - <slot name="header"></slot>- 插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽 
 
- 传: - <template v-slot:header></template>,简写为- <template #header>
- 传: - <template #[dynamicSlotName]></template>动态名字
 
- 其它情况 - 没有提供 name 的 - <slot>出口会隐式地命名为 'default'
- 所有位于顶级的非 - <template>节点都被隐式地视为默认插槽的内容
 
5.2 插槽数据的作用域
可以访问到父组件的数据作用域,但无法访问子组件的数据。
Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的,也就是:父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
5.3 插槽数据也想访问子组件的
如果想要插槽的内容同时使用父组件域内和子组件域内的数据,可以在插槽出口(子组件)对插槽传递 props,详见作用域插槽(接收的参数只在该插槽作用域内有效)。
- 默认插槽传递和接收 props(下方示例) 
- 具名插槽传递和接收 props 
<slot :text="greetingMessage" :count="1"></slot>```html
<!-- 默认插槽:接收子组件的 props -->
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<!-- or 解构写法 -->
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>
```6. 依赖注入
父组件给子组件传递数据,两种方式:
- props:当层次太深时不方便
- provide和- inject- 依赖提供者:父组件,也可以是整个应用实例层 - app
- 任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖 
 
6.1 provide 提供
使用 provide() 函数,向后代组件提供数据。
- 注入名:可以是字符串、Symbol(可以避免潜在的冲突) 
- 注入值:任意类型,包括响应式的状态 
<script setup>
  import { provide } from "vue";
  // 可多次调用,以注册多个
  provide("message", "hello!"); // 注入名: 值
  provide("title", "Hi");
</script>import { provide } from "vue";
export default {
  setup() {
    provide("message", "hello!"); // 在 setup() 同步调用的
  }
};// 在应用层
import { createApp } from "vue";
const app = createApp({});
app.provide("message", "hello!");6.2 inject 注入
使用 inject() 函数,来注入上层组件提供的数据:
<script setup>
  import { inject } from "vue";
  const message = inject("message");
</script>7. 异步组件
- 作用:仅在需要时再从服务器加载相关组件 
- 方法: - defineAsyncComponent(),接收一个返回 Promise 的加载函数
eg1. ES 模块动态导入也会返回一个 Promise
import { defineAsyncComponent } from "vue";
// 仅在页面需要它渲染时才会调用加载内部实际组件的函数
const AsyncComp = defineAsyncComponent(() =>
  import("./components/MyComponent.vue")
);eg2. 异步组件 + 内置的 <Suspense> 组件
8. 状态管理
每一个 Vue 组件实例都已经在“管理”它自己的响应式状态了:
- 状态:驱动整个应用的数据源 
- 视图:对状态的一种声明式映射 
- 交互:状态根据用户在视图中的输入而作出相应变更的可能方式 

然而,当有多个组件共享一个共同的状态时,就没有这么简单了,比如多个视图可能都依赖于同一份状态,比如来自不同视图的交互也可能需要更改同一份状态。
可选的办法:
- 将共享状态“提升”到共同的祖先组件上去,再通过 props 传递下来 - 问题:prop 逐级透传问题,尤其是在深层次的组件树结构中的时候 
 
- 抽取出组件间的共享状态,放在一个全局单例中来管理:用响应式 API 做简单状态管理,多个组件共用了单一的数据源 - store对象,代码示例如下。- 随之而来的问题:任意一个导入了 - store的组件都可以随意修改它的状态,不利于维护
- 解决:在 store 上定义方法,方法名应该要能表达出行动的意图 
- 可作为 store 的:单个响应式对象, 其它响应式 API: - ref(),- computed(), 通过一个组合式函数来返回一个全局状态
 
- 利用服务端渲染 (SSR) 的应用 - 特殊之处:store 是跨多个请求共享的单例,详见服务端渲染-跨请求状态污染 
 
- 官方的状态管理库 Pinia - Vue 之前的官方状态管理库是 Vuex,现在处于维护模式,但不再接受新的功能 
- 相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导 
 
eg. 用响应式 API 做简单状态管理(手动管理)
// store.js
import { reactive } from "vue";
export const store = reactive({
  count: 0,
  increment() {
    this.count++;
  }
});<!-- ComponentA.vue -->
<script setup>
  import { store } from "./store.js";
</script>
<template>From A: {{ store.count }}</template><!-- ComponentB.vue -->
<script setup>
  import { store } from "./store.js";
</script>
<template>From B: {{ store.count }}</template>9. 更多
- 组件的封装原理 
<!-- 组件的 key 属性 -->
<div v-for="child in children" :key="child.id" >{{ child.name }}</div>Last updated