一、前言
分享一下在学习vue2时封装的最后的一个组件,table组件,在这个这次分享中例外说一下自定义指令
以及jsx语法的基本写法
。然后以前说过,自己封装的组件如果需要使用use去注册的话需要给这个组件写一个install的方法,这里就不再说了。
二、table组件封装
<template>
<table class="co-table" cellspacing="0" cellpadding="0" >
<thead>
<!-- 渲染表头 -->
<tr>
<th
v-for="col in columns"
:key="col.label"
:style="{ textAlign : col.align }"
>
{{ col.label }}
</th>
</tr>
</thead>
<tbody>
<!-- 看表格存在几行就是看传递过来的数据的data数组的长度 -->
<tr v-for="(row, index) in data" :key="index">
<!-- 每一行通过获取到的属性来确定有几列 -->
<td
v-for="col in columns"
:key="col.label"
:style="{ 'text-align': col.align }"
>
<!-- 每一个格子的内容就是遍历data数据的每一个对象[获取的属性值] -->
{{ row[col.prop] }}
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
// 1.对于每个自己封装的组件,name属性推荐写上
name: 'co-table',
// 2.在使用的时候table的数据是用绑定的data属性传递的,按照规则,
// 需要在table组件中定义一个data的props的属性
props: {
data: {
type: Array,
require: true,
},
},
computed: {
// 3.在使用的时候,因为写在table组件里面的内容是用抽象组件进行约束的,
// 它没有模板,所以不能使用slot插槽将它接住,以前分享过,这些内容会
// 默认存放在this.$slots.default中,但是它也会将换行符也收集起来,
// 所以在使用这个数据的时候需要进行筛选,将换行符给过滤掉。
columns() {
return this.$slots.default
.filter(
// 为了避免过长的引用,这里使用结构将componentOptions拿出来,
// 如果不结构的话呢这里需要使用item.componentOptions
({ componentOptions }) =>
// &&表示如果&&前面的语句为true才会执行后面的语句
// componentOptions是判断它是否是换行符节点,
// componentOptions.tag === 'co-table-column'
// 是用来确定标签名是否是我需要的
componentOptions && componentOptions.tag === 'co-table-column'
)
// 过滤完毕之后就获取里面的数据并返回,然后通过循环的方式就可以将
// table表格的表头渲染出来了。
.map(({ componentOptions }) => componentOptions.propsData);
},
},
};
</script>
<style lang="scss">
.co-table {
th,
td {
border-bottom: 1px solid #ebeef5;
}
}
</style>
三、tableColumn组件的封装
<script>
export default {
// 1.对于每个自己封装的组件,name属性推荐写上
name: 'co-table-column',
// 3.对于之前说过的组件的分类,如果不需要模板,那就使用抽象组件,
// 将abstract设置为true就可以了
abstract: true,
// 2.对于用户传递的数据组件并不知道它的顺序是怎样的,所以这里使用了
// tableColumn这个子组件对传递的数据进行约束,只是起到一个约束数据
// 的作用,所以它不需要模板,其约束的方式通过绑定的属性来完成。
props: {
// 日期
prop: {
type: String,
},
// 内容
label: {
type: String,
required: true
},
// 宽度
width: {
type: [String, Number],
},
// 对齐
align: {
type: String,
default: 'left'
}
}
}
</script>
四、自定义指令
说明: 自定义指令也是一个组件,一般会将自定义指令存放在src目录下面的一个directives目录中,在这个目录里面每一个文件夹都是一个指令,当然,在写完一个指令之后,也需要为它写一个install的方法,方便它被use使用。
用途: 可以在vue中使用dom操作的地方
指令的完整格式: v-指令名:参数.修饰符 = 表达式
内容:
export default {
// 在定义指令的时候,指令的名字不需要加上v,v会默认加上,
// 只不过自己使用的时候需要v-指令名
name: 'loading'
// 指令初始化的时候,只会被调用一次,会在指令第一次绑定到元素时调用
bind(el, binding) {
// el: 指令所绑定的元素
// binding: {
// name: 指令名
// value: 指令绑定的值
// oldValue: 指令绑定的前一个值
// expression:字符串形式的指令表达式
// arg: 传递给指令的参数
// modifiers:指令的修饰符对象
// }: 指令的配置对象
// 每一个声明周期函数都存在el和binding这两个参数(用的最多)
}
// 指令绑定的元素插入到父元素的时候才会被调用
inserted() {}
// 指令绑定的值发生变化的时候
update() {}
// 更新完毕时调用
componentUpdated() {}
// 解绑的时候调用
unbind() {}
}
注册: 全局注册使用Vue.directive(指令名,指令配置项)
举例:
element-ul的loading组件的实现(仿写)
// 书写loading指令主体逻辑的index.js文件
// 引入一个圈圈的svg图片
import loadingSvg from './loading.svg';
// 引入loading指令实现的效果的css样式
import './loading.scss';
// 这个函数用来书写创造节点的流程
function createLoadingMask() {
// 最外层的蒙版
const coLoadingMask = document.createElement('div');
// 添加类名为了好去书写样式
coLoadingMask.className = 'co-loading-mask';
// 装图片的盒子
const coLodingSpinner = document.createElement('div');
coLodingSpinner.className = 'co-loading-spinner';
// 创建图片标签
const coLoadingCircular = document.createElement('img');
// 给空的图片标签进行赋值
coLoadingCircular.src = loadingSvg;
coLoadingCircular.className = 'co-loading-circular';
// 将创建的节点合并为一个节点
coLodingSpinner.appendChild(coLoadingCircular);
coLoadingMask.appendChild(coLodingSpinner);
// 将合并后的节点返回出来
return coLoadingMask;
}
// 用变量接住上面函数执行所返回的节点的嵌套结果
const coLoadingMask = createLoadingMask();
const loading = {
name: 'loading',
bind(el, binding) {
// 通过绑定的值来确定是否插入这个生成的节点
if (binding.value) {
// 给el节点添加定位,让loading的圈圈覆盖在需要的位置上面,而不是body
el.style.position = 'relative';
// 将合并生成的节点插入到指令的el中
el.appendChild(coLoadingMask);
}
},
// 当绑定的值变化的时候,需要根据这个值来确认是否插入
update(el, binding) {
if (binding.value) {
el.style.position = 'relative';
el.appendChild(coLoadingMask);
} else {
el.removeChild(coLoadingMask);
}
},
};
export default {
install(Vue) {
Vue.directive(loading.name, loading);
},
};
// 圈圈的svg图片的loading.svg文件
<svg t="1681267906984" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2668" width="200" height="200">
<path d="M515.698303 969.127499c-97.187972 0-191.279691-31.134554-270.406182-90.479422-96.67193-72.245926-159.45708-178.206619-176.658492-297.928439s13.245087-238.9276 85.663027-335.59953C304.120947 45.239711 588.288258 4.644381 787.99664 154.124643c83.770872 62.78515 143.459768 153.092558 168.2298 254.580884 4.300353 17.373425-6.364522 34.918864-23.737947 39.047203-17.373425 4.128339-34.918864-6.364522-39.047203-23.737947-21.157736-86.867126-72.417941-164.44549-144.147825-218.285906C578.139425 77.750378 334.395431 112.669242 206.244919 283.823282c-62.097094 82.910801-88.243239 185.087183-73.450025 287.607593s68.461616 193.34386 151.372417 255.440954c171.326054 128.322526 414.898035 93.403662 543.220561-77.922392 33.542752-44.895683 56.592642-95.123803 68.289602-149.308248 3.78431-17.373425 21.157736-28.554342 38.359147-24.770032 17.373425 3.78431 28.554342 20.985721 24.770032 38.359147-13.761129 63.473207-40.59533 122.130018-79.814547 174.422308-72.417941 96.67193-178.378633 159.45708-298.100454 176.658492C559.217873 967.579372 537.372081 969.127499 515.698303 969.127499z" fill="#575B66" p-id="2669"></path></svg>
// loading指令实现的效果的css样式的loading.scss文件
.co-loading {
@keyframes loading-rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
@at-root &-mask {
position: absolute;
z-index: 2000;
background-color: hsla(0,0%,100%,.9);
margin: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: opacity .3s;
}
@at-root &-spinner {
top: 50%;
margin-top: -21px;
width: 100%;
text-align: center;
position: absolute;
}
@at-root &-circular {
width: 42px;
height: 42px;
animation: loading-rotate 2s infinite;
}
}
五、svg图片
如果需要解析:
// 1.先下载一个url-loader
npm i url-loader
// 2.在webpack.config.js文件中配置一下解析的规则
module.exports = {
module: {
reles: [
{
test: /\.svg$/,
use: ['url-loader']
}
]
}
}
如果需要自定义svg图标:
// 1.先安装一个loader,这个loader没有被vue官方所收集。
npm i vue-svg-loader -D
// 2.在vue.config.js中书写文件
const path = require("path")
module.exports = {
chainWebpack: (config) => {
// 先锁定处理svg文件
const svgRule = config.module.rule("svg");
// vue-svg-loader只处理指定的文件目录
// (include是指定文件夹,exclude是除了这个文件夹)
svgRule.include.add(path.resolve(__dirname, "目录名称"))
// 清空处理svg图片的规则
svgRule.uses.clear();
// 然后自己添加规则去解析
svgRule.use("vue-svg-loader").loader("vue-svg-loader");
}
}
// 3.然后导入svg格式的文件并注册(举例)
import loadingSvg from './loading.svg';
export default {
components: {
loadingSvg
}
}
六、jsx语法
jsx语法: 在vue中书写template的时候,它会根据模板先生成render函数,由函数的执行去生成虚拟节点树,最后再转换为真实的dom树,但是render函数的书写并不是很容易,所以便出现了jsx语法,这个语法它保留了template的简单的写法,也继承了render函数的灵活性。
render函数与template的对比: template语法理解起来较为容易,语法比较简单,对js要求很低,也就导致了它使用起来不够灵活,不能够发挥js的全部能力,但是前者却恰恰相反。
注意: render函数返回什么就会渲染什么,并且它只能返回虚拟dom节点
render函数:
// 1.要返回虚拟的节点,那么就需要生成虚拟节点,可以使用createElement()函数
// 将真实的节点转换为虚拟节点,以此提供给render函数来渲染,由于这个函数比
// 较长,所以将它俗称为h函数,render函数的形参自己会携带这个h。
render(h) {
return h(App) // 使用
}
render(createElement) {
// vue在初始化的过程中,会主动把createElement这个函数传递给render函数
// 作为参数,所以这里也可以使用createElement,
return createElement(App)
}
// 2.render函数中标签的嵌套
// 格式:标签名、标签所带的属性(用对象表示)、子标签的内容(用数组表示)
// 注意:如果子标签还存在子标签,嵌套下去就可以了
// 举例:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</div>
<router-view />
</div>
</template>
// 转换
<script>
render(h) {
return h("div", { attrs: { id: "app" } }, [
h("div", { attrs: { id: "nav" } }, [
h("router-link", { attrs: { to: "/" } }, "Home"),
h("router-link", { attrs: { to: "/about" } }, "About"),
]),
h("router-view"),
// 事件绑定的写法
h("button", { on: { click: this.fn } }, "事件的绑定" )
]);
},
</script>
render函数与jsx语法:
// jsx语法写法:一切跟动态绑定有关的东西都使用{}就可以了
render() {
return (
<div
class="home"
id={this.idName}
title="home"
style={{ color: "#fff", fontSize: "40px", lineHeight: 2 }}
onMousemove={function () {
console.log(123);
}}
>
<co-button type="danger" onClick={() => (this.loading = !this.loading)}>
{![]()this.name}
</co-button>
<h1>
<span>span</span>
</h1>
<h1>
<span>span</span>
</h1>
<ul>
{this.list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{this.visible ? <p>测试v-if</p> : null}
<p style={{ display: this.show ? "block" : "none" }}>测试v-show</p>
</div>
);
},
// render函数改写:
render(h) {
return h(
"div",
{
class: { home: true },
style: { color: "#fff", fontSize: "40px", lineHeight: 2 },
attrs: { id: this.idName, title: "home" }, // v-bind:id="idName"
on: {
// v-on
mousemove: function () {
console.log(123);
},
},
directives: [
{
name: "loading",
value: this.loading,
},
],
},
[
h(
"co-button",
{
props: {
type: "danger",
},
on: {
click: () => {
this.loading = !this.loading;
},
},
},
![]()this.name // => {{name}}
),
h("h1", {
domProps: {
innerHTML: "<span>span</span>", // v-html
},
}),
h("h1", [
h("span", {
domProps: {
textContent: "span", // => v-text
},
}),
]),
h(
"ul",
// v-for :key
this.list.map((item) => h("li", { key: item }, item))
),
this.visible ? h("p", "测试v-if") : null,
h(
"p",
{ attrs: { style: { display: this.show ? "block" : "none" } } },
"测试v-show"
),
]
);
},
// 上面做注释的地方就是自己实现指令系统的地方,因为在render函数和jsx语法中,
// vue的指令系统不能够使用了,需要自己去使用自己学过的方法去实现它,上面简单
// 实现了一下。