自定义指令directives
对底层dom进行操作的封装,目前封装一个自定义指令,作用是:谁用这个指令就会变成对应的颜色。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
<script type="text/javascript" src="lib/vue.js"></script>
</head>
<body>
<div id="box">
<!-- v-if="isShow" -->
<div v-hello="'red'">11111111</div>
<div v-hello="'yellow'">22222222</div>
<div v-hello="color">333333333333333</div>
</div>
<script type="text/javascript">
// Vue.compoennt("aa",{})
// directive指令 - dom 操作
//
Vue.directive("hello",{
//指令的生命周期-第一次插入节点调用
inserted(el,binding,vnode){
// vnode ,vdom, virtual node, 虚拟节点 虚拟dom
console.log("此时dom节点创建",vnode)
el.style.backgroundColor = binding.value;
},
update(el,binding){
console.log("此时绑定的状态改变时会执行")
el.style.backgroundColor = binding.value;
}
})
var vm = new Vue({
el:"#box",
data:{
title:"1111111111111111",
color:'blue'
},
// directives
})
</script>
</body>
</html>
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:卸载的时候会执行。
指令钩子函数会被传入以下参数:
(inserted(el,bind,newVnode,oldVnode)
)
-
el
:指令所绑定的元素,可以用来直接操作 DOM。 -
binding
:一个对象,包含以下 property:-
name
:指令名,不包括v-
前缀。 -
value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。 -
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。 -
expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。 -
arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。 -
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
-
-
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
指令函数的简写
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
<script type="text/javascript" src="lib/vue.js"></script>
</head>
<body>
<div id="box">
<!-- v-if="isShow" -->
<div v-hello="'red'">11111111</div>
<div v-hello="'yellow'">22222222</div>
<div v-hello="color">333333333333333</div>
</div>
<script type="text/javascript">
// Vue.compoennt("aa",{})
// directive指令 - dom 操作
//
Vue.directive("hello",function(el,binding,vnode){
el.style.backgroundColor = binding.value
})
var vm = new Vue({
el:"#box",
data:{
title:"1111111111111111",
color:'red'
},
// directives
})
</script>
</body>
</html>
再举一个例子:
<script>
// 自定义指令 directive
const app = Vue.createApp({
data() {
return {
distance: 110
}
},
template: `
<div>
<div v-pos:right="distance" class="header">
<input />
</div>
</div>
`
});
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px');
})
const vm = app.mount('#root');
</script>
也可以如下编写:
image.png
指令轮播
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<script src="lib/swiper/js/swiper.js"></script>
<script type="text/javascript" src="lib/vue.js"></script>
<style>
.swiper-container {
width: 600px;
height: 300px;
}
</style>
</head>
<body>
<div id="box">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="data,index in list" v-swipe="index">
{{data}}
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<!-- <div class="swiper-button-prev"></div> -->
<!-- <div class="swiper-button-next"></div> -->
<!-- 如果需要滚动条 -->
<!-- <div class="swiper-scrollbar"></div> -->
</div>
</div>
<script type="text/javascript">
Vue.directive("swipe",{
inserted(el,binding,vnode){
console.log(binding.value);
if(binding.value === vnode.context.list.length-1){
var mySwiper = new Swiper ('.swiper-container', {
// direction: 'vertical', // 垂直切换选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
}
})
}
}
})
new Vue({
el:"#box",
data:{
list:[]
},
mounted(){
setTimeout(() => {
this.list = ["111","2222","3333"];
console.log("节点创建完了????","没有,异步渲染")
}, 2000)
},
updated(){
// console.log("节点创建完了????","更新阶段渲染")
// var mySwiper = new Swiper ('.swiper-container', {
// // direction: 'vertical', // 垂直切换选项
// // 如果需要分页器
// pagination: {
// el: '.swiper-pagination',
// }
// })
}
})
</script>
</body>
</html>
轮播-nextTick
直接解决(避免)了swiper初始化过早的问题。并且防止了多次反复执行指令勾子函数更新的问题。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<script src="lib/swiper/js/swiper.js"></script>
<script type="text/javascript" src="lib/vue.js"></script>
<style>
.swiper-container {
width: 600px;
height: 300px;
}
</style>
</head>
<body>
<div id="box">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="data,index in list">
{{data}}
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
</div>
<script type="text/javascript">
new Vue({
el:"#box",
data:{
list:[]
},
mounted(){
setTimeout(() => {
this.list = ["111","2222","3333"];
// 黑魔法
this.$nextTick(()=>{
console.log("我待会才会执行")
var mySwiper = new Swiper ('.swiper-container', {
// direction: 'vertical', // 垂直切换选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
}
})
})
console.log("节点创建完了????","没有,异步渲染")
}, 2000)
},
updated(){
console.log("updated");
}
})
</script>
</body>
</html>
过滤器
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。
首选准备一个加载数据json文件。
{
"coming": [],
"movieIds": [248172, 1218727, 346629, 1228776, 1239544, 672279, 1211727, 1230152, 1211412, 672379, 1205909, 1234116,
883196, 1243904, 1207260, 1263355, 1239281, 476263, 1212492, 1217701, 1237437, 1162868, 346765, 1229702,
1239844, 337896, 1216383, 507792, 1167831, 1206939, 248123, 245881, 1206415
],
"stid": "576591972453269000",
"stids": [{
"movieId": 248172,
"stid": "576591972453269000_a248172_c0"
}, {
"movieId": 1218727,
"stid": "576591972453269000_a1218727_c1"
}, {
"movieId": 346629,
"stid": "576591972453269000_a346629_c2"
}, {
"movieId": 1228776,
"stid": "576591972453269000_a1228776_c3"
}, {
"movieId": 1239544,
"stid": "576591972453269000_a1239544_c4"
}, {
"movieId": 672279,
"stid": "576591972453269000_a672279_c5"
}, {
"movieId": 1211727,
"stid": "576591972453269000_a1211727_c6"
}, {
"movieId": 1230152,
"stid": "576591972453269000_a1230152_c7"
}, {
"movieId": 1211412,
"stid": "576591972453269000_a1211412_c8"
}, {
"movieId": 672379,
"stid": "576591972453269000_a672379_c9"
}, {
"movieId": 1205909,
"stid": "576591972453269000_a1205909_c10"
}, {
"movieId": 1234116,
"stid": "576591972453269000_a1234116_c11"
}],
"total": 33,
"movieList": [{
"id": 248172,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "v3d imax",
"nm": "复仇者联盟4:终局之战",
"preShow": false,
"sc": 9.1,
"globalReleased": true,
"wish": 1849927,
"star":"王麻子",
"rt": "2019-04-24",
"showInfo": "今天107家影院放映3177场",
"showst": 3,
"wishst": 0
}, {
"id": 1218727,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "何以为家",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 94818,
"star":"王麻子",
"rt": "2019-04-29",
"showInfo": "2019-04-29 下周一上映",
"showst": 4,
"wishst": 0
}, {
"id": 346629,
"haspromotionTag": false,
"img":"http://w.h/xxxxxx",
"version": "v3d imax",
"nm": "大侦探皮卡丘",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 181370,
"star":"王麻子",
"rt": "2019-05-10",
"showInfo": "2019-05-10上映",
"showst": 4,
"wishst": 0
}, {
"id": 1228776,
"haspromotionTag": false,
"img":"http://w.h/xxxxxx",
"version": "",
"nm": "下一任:前任",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 284385,
"star":"王麻子",
"rt": "2019-05-01",
"showInfo": "2019-05-01 下周三上映",
"showst": 4,
"wishst": 0
}, {
"id": 1239544,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "调音师",
"preShow": false,
"sc": 9.1,
"globalReleased": true,
"wish": 14608,
"star":"王麻子",
"rt": "2019-04-03",
"showInfo": "今天60家影院放映155场",
"showst": 3,
"wishst": 0
}, {
"id": 672279,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "雪暴",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 24301,
"star":"王麻子",
"rt": "2019-04-30",
"showInfo": "2019-04-30 下周二上映",
"showst": 4,
"wishst": 0
}, {
"id": 1211727,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "反贪风暴4",
"preShow": false,
"sc": 9.1,
"globalReleased": true,
"wish": 161080,
"star":"王麻子",
"rt": "2019-04-04",
"showInfo": "今天55家影院放映129场",
"showst": 3,
"wishst": 0
}, {
"id": 1230152,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "撞死了一只羊",
"preShow": false,
"sc": 8,
"globalReleased": true,
"wish": 8422,
"star":"王麻子",
"rt": "2019-04-26",
"showInfo": "今天38家影院放映81场",
"showst": 3,
"wishst": 0
}, {
"id": 1211412,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "v3d",
"nm": "神奇乐园历险记",
"preShow": false,
"sc": 8.8,
"globalReleased": true,
"wish": 4779,
"star":"王麻子",
"rt": "2019-04-19",
"showInfo": "今天39家影院放映63场",
"showst": 3,
"wishst": 0
}, {
"id": 672379,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "v3d",
"nm": "悟空奇遇记",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 11472,
"star":"王麻子",
"rt": "2019-05-01",
"showInfo": "2019-05-01 下周三上映",
"showst": 4,
"wishst": 0
}, {
"id": 1205909,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "祈祷落幕时",
"preShow": false,
"sc": 9,
"globalReleased": true,
"wish": 11057,
"star":"王麻子",
"rt": "2019-04-12",
"showInfo": "今天16家影院放映40场",
"showst": 3,
"wishst": 0
}, {
"id": 1234116,
"haspromotionTag": false,
"img":"http://128.180/xxxxxx",
"version": "",
"nm": "猫公主苏菲",
"preShow": false,
"sc": 0,
"globalReleased": false,
"wish": 6533,
"star":"王麻子",
"rt": "2019-05-01",
"showInfo": "2019-05-01 下周三上映",
"showst": 4,
"wishst": 0
}]
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
<style>
</style>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" src="lib/vue.js"></script>
</head>
<body>
<!-- <h1 class="animated hinge infinite">1111111111111</h1> -->
<div id="box">
<ul>
<li v-for="data in datalist">
{{data.nm}}
<!-- <img :src="handlePath(data.img)"/> -->
<img :src="data.img | kerwinpath"/>
{{ data.img | kerwinpath }}
</li>
</ul>
</div>
<script>
//过滤器 ---- 数据格式化
Vue.filter("kerwinpath",function(path){
return path.replace('w.h','128.180')
})
var vm = new Vue({
el:"#box",
data:{
isShow:true,
datalist:[]
},
mounted(){
axios.get("test.json").then(res=>{
console.log(res.data);
this.datalist = res.data.movieList
})
},
methods:{
handlePath(path){
console.log(path);
return path.replace('w.h','128.180');
}
}
})
</script>
</body>
</html>
过滤器其实就是一种函数写法,把第一个参数写到|
符号前面,如果还有其他参数,就写到函数名的后面。只不过这个函数方法必须写到filers
中。
过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。
过滤器可以串联:
{{ message | filterA | filterB }}
在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。
相当于:
function filterB (function filterA (message ))
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
相当于:
function filterA(message ,'arg1',arg2)
传送门Teleport
如下例子,比如想点击按钮给页面增加一个蒙层。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 30</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 300px;
background: green;
}
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
opacity: 0.5;
color: #fff;
font-size: 100px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// teleport 传送门
const app = Vue.createApp({
data() {
return {
show: false,
}
},
methods: {
handleBtnClick() {
this.show = !this.show;
}
},
template: `
<div class="area">
<button @click="handleBtnClick">按钮</button>
<div class="mask" v-show="show"></div>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
点击按钮出现如下情况:
因为在css样式
mask
中,absolute
绝对定位是相对父元素的,所以遮罩和上下左右移动都不会超出area
标签的范围。如果想要在组件内实现全遮罩的效果,这时候就需要用到传送门Teleport:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 30</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 300px;
background: green;
}
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
opacity: 0.5;
color: #fff;
font-size: 100px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
<div id="hello"></div>
</body>
<script>
// teleport 传送门
const app = Vue.createApp({
data() {
return {
show: false,
message: 'hello'
}
},
methods: {
handleBtnClick() {
this.show = !this.show;
}
},
template: `
<div class="area">
<button @click="handleBtnClick">按钮</button>
<teleport to="#hello">
<div class="mask" v-show="show">{{message}}</div>
</teleport>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
效果如下:
image.png
render函数
如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 31</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// render function
// template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
const app = Vue.createApp({
template: `
<my-title :level="2">
hello dell
</my-title>
`
});
app.component('my-title', {
props: ['level'],
template: `
<h1 v-if="level===1"><slot/></h1>
<h2 v-if="level===2"><slot/></h2>
<h3 v-if="level===3"><slot/></h3>
<h4 v-if="level===4"><slot/></h4>
`
})
const vm = app.mount('#root');
</script>
</html>
如何把如上代码写得更加优雅,修改组件代码:
render function
template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
app.component('my-title', {
props: ['level'],
render() {
//创建一个vue的h函数
const { h } = Vue;
//这是一个虚拟DOM。返回一个h函数的返回值,传递的第一个参数是标签名,第二个参数为标签的自定义或者自带的属性和属性值,第三个参数为具体标签内的内容。
//这里通过` this.$slot `获得各式各样的插槽,这里我们没有具体指向,想使用默认插槽,语法就是` this.$slots.default()`。
//这里第三个参数可以写成一个数组,相当于往下无限地嵌套的js对象。
return h('h' + this.level, {}, [
this.$slots.default(),
h('h4', {}, 'dell')
])
}
})
我们在组件中写的template 都会被编译成render。
插件
我们都知道vue有很多第三方插件,那么插件的基本原理是什么?
mixin可以对代码进行很好的封装,但是使用插件的功能,能进行更好的封装。
比如定义一个myPlugin的插件:
一定要定义一个install方法,这是插件走的一个必备逻辑。
const myPlugin = {
//支持2个参数:app:根实例,有这个可扩展任何想要的实例。
//options:额外的参数会传到options中
install(app, options) {
})
使用插件:
// plugin 插件, 也是把通用性的功能封装起来
const myPlugin = {
//支持2个参数:app,跟实例,有这个可扩展任何想要的实例。
//options:额外的参数会传到options中
install(app, options) {
})
})
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
template: `<div>hello world</div>`
})
app.use(myPlugin, { name: 'dell'});
既然得到app的实例,那么可以进行很多操作,比如设置全局变量:
// plugin 插件, 也是把通用性的功能封装起来
const myPlugin = {
//支持2个参数:app,跟实例,有这个可扩展任何想要的实例。
//options:额外的参数会传到options中
install(app, options) {
app.provide('name', 'Dell Lee');
})
})
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
inject: ['name'],
template: `<div>{{name}}</div>`
})
app.use(myPlugin, { name: 'dell'});
也可以扩展自定义指令:
// plugin 插件, 也是把通用性的功能封装起来
const myPlugin = {
//支持2个参数:app,跟实例,有这个可扩展任何想要的实例。
//options:额外的参数会传到options中
install(app, options) {
app.provide('name', 'Dell Lee');
})
app.directive('focus', {
mounted(el) {
el.focus();
}
})
})
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
inject: ['name'],
template: `<div>{{name}}<input v-focus /></div>`
})
app.use(myPlugin, { name: 'dell'});
也可以扩展其他内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 32</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// plugin 插件, 也是把通用性的功能封装起来
const myPlugin = {
//支持2个参数:app,跟实例,有这个可扩展任何想要的实例。
//options:额外的参数会传到options中
install(app, options) {
app.provide('name', 'Dell Lee');
app.directive('focus', {
mounted(el) {
el.focus();
}
})
app.mixin({
mounted(){
console.log('mixin')
}
})
app.config.globalProperties.$sayHello = 'hello world';
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
inject: ['name'],
mounted() {
console.log(this.$sayHello);
},
template: `<div>{{name}}<input v-focus /></div>`
})
app.use(myPlugin, { name: 'dell'});
const vm = app.mount('#root');
</script>
</html>
对数据做校验的插件编写
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 33</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 对数据做校验的插件
const app = Vue.createApp({
data() {
return { name: 'dell', age: 23}
},
rules: {
age: {
validate: age => age > 25,
message: 'too young, to simple'
},
name: {
validate: name => name.length >= 4,
message: 'name too short'
}
},
template: `
<div>name:{{name}}, age:{{age}}</div>
`
});
const validatorPlugin = (app, options) => {
app.mixin({
created() {
for(let key in this.$options.rules) {
const item = this.$options.rules[key];
this.$watch(key, (value) => {
const result = item.validate(value);
if(!result) console.log(item.message);
})
}
}
})
}
app.use(validatorPlugin);
const vm = app.mount('#root');
</script>
</html>
网友评论