vue的自定义
自定义组件
组件是可以被复用的页面的零件,其实就是一个插件,只是在vue里叫组件
先看看别人的组件
vant
element
Mint
iView
去试试上面的组件,都是有脚手架版和直接引入使用的版本的
脚手架的使用需要先去main.js入口文件引入,还有css也是,查看官方文档都有的
这些组件让vue的开发容易得飞起,这也是为什么说是个人就能做前端,封装得太好了,以前自己写几百行几千行的功能,在组件里主要一个标签一句代码就行,如何封装一个组件,就能理解如何使用一个组件
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.2/lib/index.css">
<div id="app">
<van-count-down :time="tt" />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vant@2.2/lib/vant.min.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
tt: 30 * 60 * 60 * 1000
}
})
</script>
上面有个<van-count-down>
标签不是vue的内置标签,而是自定义标签,就像路由的<route-view>
标签一样,这个标签就是为了在所在位置生成自定义的内容,标签上还能看到一个:time
这个自定义的参数
还有就是所有组件都有的弹窗提示功能,这类功能不需要在页面写自定义标签,是怎么实现的
Dialog.alert({
title: '标题',
message: '弹窗内容'
}).then(() => {
// on close
});
除了自定义组件,还有自定义组件的v-model数据双向变化,自定义指令,自定义过滤器,自定义方法,下面会直接上代码,不解释,解释不清楚
标签组件myA
<template>
<div>
<div>{{newNum}}</div>
<div @click="newAdd">点击添加</div>
</div>
</template>
<script>
export default {
name: 'myA',
props: ["num","add"],
data(){
return {
newNum: 0
}
},
watch:{
num(){
this.newNum = this.num
}
},
mounted(){
this.newNum = this.num
setInterval(()=>{
this.newNum--;
},1000)
},
methods:{
newAdd(){
// 因为子组件不能修改来自外来的数据
// 所以应该用this.$emit去执行外来的方法
// 用外来的方法修改外来的数据
this.$emit("add",this.newNum)
}
}
}
</script>
<style scoped>
div{
color: red;
}
</style>
双向绑定数据组件myB
<template>
<div :value="newVal" @input="Input">{{newVal}}{{aa}}</div>
</template>
<script>
export default {
name: 'myB',
props: ["value","aa"],
data(){
return {
newVal: this.value
}
},
mounted(){
setInterval(()=>{
this.Input()
},1000)
},
methods:{
Input(){
this.newVal--;
this.$emit('input',this.newVal)
}
}
}
</script>
<style scoped>
div{
color: blue;
}
</style>
生成型组件myC
<template>
<div @click="bb">{{value}}{{aa}}666</div>
</template>
<script>
export default {
name:'myC'
}
</script>
<style scoped>
div{
color: grey;
}
</style>
封装
import myA from 'myA.vue'
import myB from 'myB.vue'
import myC from 'myC.vue'
export default {
// 固定写法
install: function (Vue, options) {
// 添加的内容写在这个函数里面
console.log(options);
// 注册自定义组件
Vue.component(myA.name,myA)
// 注册自定义组件
Vue.component(myB.name,myB)
// 原型方法生成组件
Vue.prototype.$myC = function(opt){
var cc = Vue.extend(myC)
var component = new cc({
data:{
value:opt.value,
aa:opt.aa
},
methods:{
bb:opt.bb
}
}).$mount()
// 添加到页面上去
document.querySelector('body').appendChild(component.$el)
}
// 自定义过滤器
Vue.filter('formatTime', function (value) {
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
return new Date(value).Format("yyyy-MM-dd hh:mm:ss");
})
// 添加原型方法
Vue.prototype.$service = {
//电话号码合法性检查
telNumberCheck: function (tel) {
var pattern = /123/;
return pattern.test(tel)
}
}
// 自定义指令
// 第一个参数:指令名称
// 第二个参数:配置对象,指定指令的钩子函数
Vue.directive('directiveName', {
// bind中只能对元素自身进行DOM操作,而无法对父级元素操作
// 只调用一次 指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind( el,binding, vnode ) {
// 参数详解
// el:指令所绑定的元素,可以用来直接操作 DOM 。
// binding:一个对象,包含以下属性:
// name:指令名,不包括 v- 前缀。
// value:指令的绑定值,等号后面的值 。
// oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
// expression:字符串形式的指令表达式 等号后面的字符串 形式
// arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
// modifiers:指令修饰符。例如:v-directive.foo.bar中,修饰符对象为 { foo: true, bar: true }。
// vnode:Vue 编译生成的虚拟节点。。
// oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
},
// inserted这个钩子函数调用的时候,当前元素已经插入页面中了,也就是说可以获取到父级节点了
inserted ( el,binding, vnode ) {},
// DOM重新渲染前
update(el,binding, vnode,oldVnode) {},
// DOM重新渲染后
componentUpdated ( el,binding, vnode,oldVnode ) {},
// 只调用一次,指令与元素解绑时调用
unbind ( el ) {
// 指令所在的元素在页面中消失,触发
}
})
}
}
插件引入
在main.js里
import my from 'my'
Vue.use(my)
使用
<template>
<div>
<my-a :num="num" @add="add"/>
<my-b v-model="value"/>
<div my-directive="{color:'white',text:'hello!'}"></div>
<div>{{ time | formatTime }}</div>
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: 'ceshi',
data(){
return {
num:111,
value:222,
time:123153123
}
},
mounted(){
this.$myC({
value:"1111",
aa:"aa",
bb(){
this.$service.telNumberCheck(123456789)
}
})
}
}
</script>
组件的递归,在组件调用自己就行,但是一定要防止死循环
递归组件最常见到就是tree结构
<template>
<div>
{{num}}
<my-num :num="num-1" v-if="num>0"/>
</div>
</template>
<script>
export default {
// name一定要
name: 'myNum',
props: ["num"],
}
</script>
// 使用
<my-num num="3"/>
// 然后会显示
3
2
1
0
组件里的props上面都是用数组去接受,如果写成对象格式,可以做数据校验,能要求必传,还能添加默认值
props:{
title:String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise,
items: { // 必须提供字段
required: true
},
unit: { // 可选字段,有默认值
default: 3
}
}
事件修饰符native
用来注册元素的原生事件而不是组件自定义事件的
<vButton @click="clickHandler" @vclick="vClickHandler">按钮</vButton>
export default {
methods: {
clickHandler () {
alert('onclick') // 此处不会执行 因为组件中未定义 `click` 事件
},
vClickHandler () {
alert('onvclick') // 触发 `vclick` 自定义事件
}
}
}
//如果将上面模版改成如下方式,那么两个事件都会执行。
<vButton @click.native="clickHandler" @vclick="vClickHandler">按钮</vButton>
有个问题
如果组件的渲染报错说运行时什么什么的
在vue.config.js里加上下面这个配置
module.exports = {
runtimeCompiler: true
}
补充一个图片懒加载的自定义指令
// 引入Vue构造函数
import Vue from 'vue'
var lazyload = {
// Vue.use() 默认加载install,并且将Vue当做第一个参数传递过来
install(vue,payload) {
// 数组扩展移除元素
if(!Array.prototype.remove){
Array.prototype.remove = function(item){
if(!this.length) return
var index = this.indexOf(item);
if( index > -1){
this.splice(index,1);
return this
}
}
}
// 默认值图片
var defaultImage = payload.defaultImage || 'xxx.png';
var errorImage = payload.errorImage || 'xxx.png';
// 默认离可视区10px时加载图片
var distanece = payload.scrollDistance || 10;
// 收集未加载的图片元素和资源
var listenList = [];
// 收集已加载的图片元素和资源
var imageCacheList = [];
// 是否已经加载完成的图片
const isAlredyLoad = (imageSrc) => {
if(imageCacheList.indexOf(imageSrc) > -1){
return true;
}else{
return false;
}
}
//检测图片是否可以加载,如果可以则进行加载
const isCanShow = (item) =>{
var ele = item.ele;
var src = item.src;
//图片距离页面顶部的距离
var top = ele.getBoundingClientRect().top;
//页面可视区域的高度
var windowHeight = window.innerHight;
// top - distance 距离可视区域还有distance像素
if(top - distanece < window.innerHeight){
var image = new Image();
image.src = src;
image.onload = function() {
ele.src = src;
imageCacheList.push(src);
listenList.remove(item);
}
image.onerror = function() {
ele.src = errorImage;
imageCacheList.push(src);
listenList.remove(item);
}
return true;
}else{
return false;
}
};
const onListenScroll = () => {
window.addEventListener('scroll',function(){
var length = listenList.length;
for(let i = 0;i<length;i++ ){
isCanShow(listenList[i]);
}
})
}
//Vue 指令最终的方法
const addListener = (ele,binding) =>{
//绑定的图片地址
var imageSrc = binding.value;
// 防止重复加载。如果已经加载过,则无需重新加载,直接将src赋值
if(isAlredyLoad(imageSrc)){
ele.src = imageSrc;
return false;
}
var item = {
ele: ele,
src: imageSrc
}
//图片显示默认的图片
ele.src = defaultImage;
//再看看是否可以显示此图片
if(isCanShow(item)){
return
}
//否则将图片地址和元素均放入监听的lisenList里
listenList.push(item);
//然后开始监听页面scroll事件
onListenScroll();
}
Vue.directive('lazyload',{
inserted: addListener,
updated: addListener
})
}
}
export default lazyload;
// 使用
// 参数均为可选
Vue.use(Lazyload,{
scrollDistance: 15, // 距离可视区还有15px时开发加载资源
defaultImage: '', // 资源图片未加载前的默认图片(绝对路径)
errorImage:'' // 资源图片加载失败时要加载的资源(绝对路径)
})