Vue.createApp({...}).component('my-component-name', {
// ... 选项 ...
})
或者像下面这样写
const app = Vue.createApp({})
app.component('component-a', {
/* ... */
})
app.component('component-b', {
/* ... */
})
通过一个普通的 JavaScript 对象来定义组件
const ComponentA = {
/* ... */
}
const ComponentB = {
/* ... */
}
然后在 components 选项中定义你想要使用的组件:
const app = Vue.createApp({
components: {
'component-a': ComponentA, // component-a 为对应的组件名
'component-b': ComponentB
}
})
局部注册的组件在其子组件中不可用
例如,上面的组件 ComponentA 和 ComponentB 都是在 app 中局部注册的
但在 ComponentB 中是无法使用 ComponentA 的
除非你这样写:
const ComponentA = {
/* ... */
}
const ComponentB = {
components: {
'component-a': ComponentA
}
// ...
}
在 ComponentB 中注册 ComponentA 之后,才能使用 ComponentA
父子组件通过两种不同的方式来相互通信
先来讲一下父传子,也就是 props
Prop 是你可以在组件上注册的一些自定义 attribute
Props 有两种常见的用法:
直接用代码来解释用法
Vue.component('blog-post', {
props: ['title'],
template: '{{ title }}
'
})
在上述代码中,我们局部注册了一个组件 blog-post
并且在 props 中自定义了一个 attribute,并且在 template 中使用了 title
在父组件中使用 blog-post 组件,并且给 title 赋值
这样就实现了父组件给子组件传值
页面效果如下:
// 简单语法
app.component('props-demo-simple', {
props: ['size', 'myMessage']
})
type 的类型可以是下面这些:
补充:对象类型的其它写法
app.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
default() {
return 'Default function'
}
}
}
})
注意:使用 DOM 中的模板时,驼峰命名的 prop 名需要使用其等价的短横线分隔符命名来替换
一个非 prop 的 attribute 是指传向一个组件
但是该组件并没有相应 props 或 emits 定义的 attribute
如果我们不希望根元素继承 attribute
可以在组件的选项中设置
inheritAttrs: false
如下所示:
首先,我们需要 在子组件中定义好某些情况下触发的事件名称
其次,在父组件中以 v-on 的方式传入要监听的事件名称,并绑定对应的方法
最后,在子组件中发生某个事件的时候,根据事件名触发对应的事件
先看一个计数器的例子,如下图所示:
大致过程就是:
const app = createApp({})
// 数组语法
app.component('todo-item', {
emits: ['check'],
created() {
this.$emit('check')
}
})
// 对象语法
app.component('reply-form', {
emits: {
// 没有验证函数
click: null,
// 带有验证函数
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
})
使用 provide 和 inject
无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者
这个特性有两个部分:
还是继续用前面那个例子进行讲解
provide 组件实例的 property
但如果 todos 中的内容发生了变化,也就是 todos.length 改变时,不会反映在 inject 中
全局事件总库 mitt 库 ?
使用插槽时,如果没有提供内容,则会显示插槽默认的内容
如果我们提供内容,则这个提供的内容将会被渲染从而取代默认内容
v-slot: 可以写成 #
因此上面的代码可以这样写:
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
...
规则只有一条:
父级模板里的所有内容都是在父级作用域中编译的
子模板里的所有内容都是在子作用域中编译的
有时让插槽内容能够访问子组件中才有的数据是很有用的
下面我们来看一个例子,组件 todo-list 中包含一个 items
我们将其动态的绑定到了 slot 上面
app.component('todo-list', {
data() {
return {
items: ['Feed a cat', 'Buy milk']
}
},
template: `
-
`
})
我们在父组件中使用了 todo-list 组件,要想在父组件中使用 slot 中绑定的 attribute
我们需要先包裹一层 template,然后把之前绑定的 attribute 传过来
也就是给 v-slot 赋一个 slotProps (绑定在 slot 元素上的 attribute 被称为 插槽 prop)
最后通过 slotProps 获取传过来的值
{{ slotProps.item }}
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用
{{ slotProps.item }}
这种写法还可以更简单
当我们的插槽是默认插槽时,也就是说它没有名字,那就可以像这样写:
{{ slotProps.item }}
默认插槽的缩写语法不能和具名插槽混用
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:
{{ slotProps.item }}
...
上述内容可以通过 Vue 的
注:也可以对动态组件传值和监听事件
当在组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复渲染导致的性能问题
用法如下:
keep-live 的属性:
注:对于已经被缓存的组件来说,再次切换回该组件时
我们是不会执行 created 或者 mounted 等生命周期函数
但是可以使用 activated 和 deactivated 这两个生命周期钩子函数来监听 ?
默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的
那么 webpack 在打包时就会将组件模块打包到一起(比如全部打包到 app.js 文件中)
随着项目的不断庞大,app.js 文件的内容过大,会造成 首屏的渲染速度变慢
所以,对于一些不需要立即使用的组件,我们可以对其进行拆分,单独打包
拆分成一个个 chunk.js
这些 chunk.js 会在需要的时候,从服务器上加载下来,并运行对应的代码
Vue 中也有类似的方法
在大型应用中,我们可能需要将应用分割成小一些的代码块
并且只在需要的时候才从服务器加载一个模块
Vue 中有一个 defineAsyncComponent 方法可以实现上述需求
用法如下:
import { defineAsyncComponent } from 'vue' // 导入
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
) // 实现异步加载
在局部注册组件时,也可以使用 defineAsyncComponent
import { createApp, defineAsyncComponent } from 'vue'
createApp({
// ...
components: {
AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
}
})
对于高阶用法,defineAsyncComponent 可以接受一个对象
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue')
// 加载异步组件时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
delay: 200,
// 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
// 默认值:Infinity(即永不超时,单位 ms)
timeout: 3000,
// 定义组件是否可挂起 | 默认值:true
suspensible: false,
/**
*
* @param {*} error 错误信息对象
* @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
* @param {*} fail 一个函数,指示加载程序结束退出
* @param {*} attempts 允许的最大重试次数
*/
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// 请求发生错误时重试,最多可尝试 3 次
retry()
} else {
// 注意,retry/fail 就像 promise 的 resolve/reject 一样:
// 必须调用其中一个才能继续错误处理。
fail()
}
}
})
是一个实验性的新特性
提供两个插槽,fallback 为备用的
尽管存在 prop 和事件,但有时我们可能仍然需要直接访问 JavaScript 中的子组件
这个时候,我们可以使用 $refs
同样,作为子组件,我们可以通过 $parent 和 $root 访问父组件中的元素
所有的生命周期钩子自动绑定 this 上下文到实例中
这也意味着我们 不能使用箭头函数来定义一个生命周期方法
下面来介绍一下什么是生命周期
每个组件都可能会经历从 创建、挂载、更新以及卸载 等一系列的过程
在这个过程中的 某一个阶段,我们可能会想要 添加一些属于自己的代码逻辑
但是我们如果想要知道目前组件正处于哪一个阶段的话
需要借助 Vue 给我们提供的关于组件的 生命周期函数
生命周期函数:
生命周期函数是一些钩子,在某个时间会被 Vue 源码内部进行回调
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段
那么我们就可以在该生命周期中编写属于自己的代码
生命周期图示:
默认情况下,组件上的 v-model 使用 modelValue 作为 prop
update:modelValue 作为事件
不太懂?