前端自定义Vue组件开发
视频下载
通读官方文档 - 组件基础
// Vue.component('组件名', {/* 定义 */})
// 组件名,推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)
定义一个名为 button-counter 的新组件
es5
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
es6
Vue.component('button-counter', {
data() {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Webpack,单文件组件
- @/components/button-counter.vue
<template>
<button v-on:click="count++">You clicked me {{ count }} times.</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
}
</script>
- @/main.js
//...
// 全局注册
import ButtonCounter from '@/components/button-counter.vue'
Vue.component('button-counter', ButtonCounter)
//...
使用
@/App.vue
<template>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
</template>
<script>
// 局部注册
// import ButtonCounter from '@/components/button-counter.vue'
export default {
// components: { ButtonCounter }, // 相当于 components: { ButtonCounter: ButtonCounter },<button-counter>
// 或定义别名
// components: { 'my-counter': ButtonCounter },
data() {
return {
count: 0
}
},
}
</script>
vue.min.js完整版和vue.runtime.min.js运行时版的区别:
- 完整版有complier,运行时版无complier,且完整版代码要多余运行时版40%;
- 完整版视图写在HTML里或者template选项,运行时版视图写在render函数里用h来创建标签;
- webpack引入,默认使用运行时版,完整版需配置alias;
- @vue/li引入,默认使用运行时版,完整版需额外配置。
组件常用API的使用
- props
- 事件
- 修饰符.prop、.sync、.camel(不常用)
- v-model
- slot
- is(动态组件)
子组件 Child.vue
<template>
<div class="child-comp">
<header>
<!-- 命名插槽 -->
<slot name="header"></slot>
</header>
<!-- <span> {{ title }} </span> -->
<!-- 尽量用v-text -->
<span v-text="title"></span>
<br>
<span v-text="parseNum"></span>
<br>
<span v-if="disabled">disabled</span>
<br>
<button v-on:click="onClick">测试按钮</button>
<br>
<section>
<!-- 默认插槽 -->
<slot v-bind:value1="value1" :value2="value2"></slot>
</section>
<br>
<footer>
<!-- 命名插槽 -->
<slot name="footer" :value1="value1"></slot>
</footer>
</div>
</template>
<script>
export default {
// 定义v-model
// v-model默认使用input事件,在自定义使用场景中可以使用其它事件名称,如change。
model: {
prop: 'value',
event: 'change',
},
// 不推荐,不限定类型,也不提供默认值
// props: ['title', 'num'],
// 推荐
props: {
// 与model搭配使用
value: {
type: String,
default: ''
},
//
syncValue: {
type: String,
default: ''
},
//
title: {
type: String,
required: true, // 必填,一般是没有default值的时候使用
},
num: {
type: [Number, String], // 限定多个类型
// 不提供默认值,默认值即为undefined
// 校验方法(组件实例创建之前进行验证,之后的无效),验证失败,控制台会出现一条warning
validator(value) {
return value > 10
}
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
value1: '123',
value2: 'abc'
}
},
computed: {
parseNum() {
let num = parseInt(this.num)
return isNaN(num)? 0 : num
}
},
methods: {
onClick() {
// 事件,推荐你始终使用 kebab-case 的事件名
this.$emit('on-test', 'abc')
// 更新v-model的value值
this.$emit('change', '123')
// 更新syncValue的值
this.$emit('update:syncValue', 'efg')
// click事件
this.$emit('click', '456')
}
},
// 注入接收
inject: ['value1', 'value2']
}
</script>
<!-- 一般项目内的组件,都应该添加scoped,避免样式交叉冲突 -->
<!-- 但如果在实现一个可复用的组件库,则建议通过定义一个自定前缀,如el-xxx的方式管理组件库里组件的样式 -->
<style lang="less" scoped>
.child-comp {
// 使用scoped的时候,如果组件内含其它第三方组件,若需要修改、覆盖内含组件的样式,需要使用::v-deep,因为内部组件不受该组件的scoped影响
::v-deep .el-button{
// ...
}
}
</style>
父组件 Parent.vue
<template>
<div>
<!-- :xxx="value1" 相当于 v-bind:xxx="value1" -->
<!-- @xxx="func1" 相当于 v-on:xxx="func1" -->
<!-- xxx 后面不带等号和值,针对type为Boolean的prop,相当于:xxx="true" -->
<!-- 针对没有定义prop的传入的值,作为dom property。
<!-- 加修饰符.prop, 如 :title.prop="dom property" ,仅作为dom property。 -->
<!-- 推荐使用kebab-case,不过使用字符串模板时,使用驼峰式也可以。 -->
<!-- 传入class和style会自动合并,是vue提供的特殊处理。 -->
<!-- 加修饰符.native后,如@click.native,则触发click的是基于原生的click事件,而非子组件的$emit('click') -->
<child-comp
v-model="value1"
:sync-value.sync="value2"
@update:sync-value="onValue2Change"
:title="title"
num="123"
@on-test="onTest"
@click.native="onNativeClick"
disabled>
<!-- slot-scope 已经被废弃,Vue3中不再支持。建议使用v-slot -->
<!-- 默认插槽 -->
<div slot-scope="{ value1 = '默认值', value2: myValue }">
<span v-text="value1"></span>
<br>
<span v-text="myValue"></span>
</div>
<!-- 等效于 -->
<template v-slot:default="{ value1 = '默认值', value2: myValue }">
<div>
<span v-text="value1"></span>
<br>
<span v-text="myValue"></span>
</div>
</template>
<!-- 只有默认插槽的时候,可以缩写为 -->
<template v-slot="{ value1 = '默认值', value2: myValue }">
<div>
<span v-text="value1"></span>
<br>
<span v-text="myValue"></span>
</div>
</template>
<!-- 命名插槽 -->
<div slot="header"></div>
<!-- 等效于 -->
<template v-slot:header>
<div></div>
</template>
<!-- 假装动态插槽名 -->
<template v-slot:['header']>
<div></div>
</template>
<!-- 命名插槽 -->
<div #footer="{ value1 }"></div>
<!-- 等效于 -->
<div slot="footer" slot-scope="{ value1 }"></div>
</child-comp>
<!-- 一次传入所有property,场景如使用第三方组件时,统一部分prop的配置值。 -->
<child-comp v-bind="{ title: 'xxx', num: 123 }"></child-comp>
<br>
<span v-text="value1"></span>
<br>
<span v-text="value2"></span>
<br>
<!-- 动态组件 -->
<!-- 绑定组件选项对象,而不是已注册组件名的示例 -->
<component :is="currentComponent"></component>
</div>
</template>
<script>
import ChildComp from './Child.vue'
// 假装有另外一个组件
import OtherChildComp from './OtherChildComp.vue'
export default {
components: { ChildComp },
data() {
return {
title: '我的标题',
value1: '',
value2: '',
currentComponent: OtherChildComp
}
},
methods: {
onValue2Change() {},
onTest(value) {
// ...
},
onNativeClick() {}
},
// 依赖注入
// 依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
// 同时所提供的 property 是非响应式的!!!
provide() {
// 返回对象
return {
value1: '1',
value2: this.value2
}
}
}
</script>
关于keep-alive
主要用于保留组件状态或避免重新渲染,要求被切换到的组件都有自己的名字,不论是通过组件的
name
选项还是局部/全局注册。
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
<keep-alive>
<component-a v-if="a > 1"></component-a>
<component-b v-else></component-b>
</keep-alive>
- 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
- 当组件在
<keep-alive>
内被切换,它的activated
和deactivated
这两个生命周期钩子函数将会被对应执行。
Props
- include:字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude:字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max:数字。最多可以缓存多少组件实例。
异步组件
只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
// 或
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,你也可以直接提供一个返回 Promise
的函数:
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
以上用法,可以配置Vue Router使用。
处理边界情况
- 访问根实例 - this.$root
- 访问父级组件实例 - this.$parent
- 访问子组件实例或子元素 - this.$refs.xxx
<template>
<div>
<child-comp ref="xxx">
</child-comp>
</div>
</template>
<script>
import ChildComp from './Child.vue'
export default {
components: { ChildComp },
data() {
return {
}
},
methods: {
},
mounted() {
// 访问子组件实例或子元素
console.log(this.$refs.xxx)
// 访问子组件实例所挂载的dom element
console.log(this.$refs.xxx.$el)
}
}
</script>
程序化的事件侦听器
<template>
<div>
<child-comp ref="xxx">
</child-comp>
</div>
</template>
<script>
import ChildComp from './Child.vue'
export default {
components: { ChildComp },
data() {
return {
}
},
methods: {
onTest(value) {}
},
mounted() {
// 侦听一个事件
this.$refs.xxx.$on('on-test', this.onTest)
// 一次性侦听一个事件
this.$refs.xxx.$once('hook:beforeDestroy', this.onTest)
// 停止侦听一个事件
this.$refs.xxx.$off('on-test', this.onTest)
}
}
</script>
递归组件
- 确保递归调用的条件性,否则会导致“max stack size exceeded”错误。
组件之间的循环引用
- 全局注册的组件,组件之间的循环引用,是可以的。不过同样要确保调用的条件性。
- 通过webpack,组件之间存在循环引用会出错,构建过程中的相互依赖关系会导致构建出错。解决办法是通过异步组件的方式实现。
通过 v-once 创建低开销的静态组件
渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once
attribute 以确保这些内容只计算一次然后缓存起来,就像这样:
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})
再说一次,试着不要过度使用这个模式。当你需要渲染大量静态内容时,极少数的情况下它会给你带来便利,除非你非常留意渲染变慢了,不然它完全是没有必要的——再加上它在后期会带来很多困惑。
Vue.use
一般来说,一组相关的组件的初始化,可以通过插件的方式安装。
以webpack下为例:
- 定义(@/my/index.js)
import xxx from '@/my/components/xxx/index.vue'
import yyy from '@/my/components/yyy/index.vue'
import zzz from '@/my/components/zzz/index.vue'
// other
// 全局资源、全局方法、指令等等,均可以在这里引入,并在install处初始化。
export default {
install(Vue) {
// do something maybe
// 假设'my-'为前缀
Vue.component('my-xxx', xxx)
Vue.component('my-yyy', yyy)
Vue.component('my-zzz', zzz)
}
}
- 安装
import Vue from 'vue'
import My from '@/my/index.js'
Vue.use(My)
// ...
文档更新时间: 2024-04-25 14:58 作者:管理员