VUE家族系列:
01、component组件
1.1、component基础知识
组件是可以复用的Vue模块,是一个独立的,有自己的视图、样式CSS、ViewMoel业务逻辑,结构的完整模块。当成自定义元素,可以在任意Vue中、模板、其他组件中使用。一个复杂的页面、系统可以拆分为多个组件,独立开发和维护,可复用、更清晰。
🔸注册组件:Vue.component( id, [definition] )
注册全局组件,id
为组件的名称,也作为元素名,参数和Vue参数选项对象基本一致。
data
必须是函数:通过函数返回对象,避免不同组件实例共享data
数据。template
模板,字符串模板,或者<template>
选择器,不能用真实Dom元素。- 必须有根元素:组件的模板必须有一个有效的根元素。
🔸props 参数:通过props
定义组件参数(Array<string> | Object
),参数都会作为组件元素的attribute
特性使用,通过vm.$props
获取组件参数。
🔸自定义事件:在子组件中,申明并触发自定义事件 $emit( event-name, […args] )
,通过vm.$listeners
获取组件的所有监听事件。(emit /iˈmɪt/ 发射,发出 )
- 响应事件(父组件):
v-on:event-name
,这里的处理函数建议只绑定函数名,便于接收参数。 - 修饰符
.native
绑定组件根元素的原生事件,v-on:change.native="func"
- 绑定事件监听器,在组件内特定元素上绑定所有监听事件:
v-on="$listeners"
,vm.$listeners
可获取组件上的所有监听事件。
🔸<slot>
插槽</slot>
,在组件中设定一个插槽,来接收组件元素的标签内的内容,通过vm.$slots
获取组件的插槽。
<div id="app9">
<uinfo v-for="(u,i) in users" v-bind:u="u" type="钻石会员" v-on:remove="users.splice(i,1)"></uinfo>
</div>
<script>
// 注册一个全局组件
Vue.component("uinfo", {
data: function () {
return { count: 0 }
}, //组件的data必须是函数返回对象
props: ['u', 'type'],
template: `<div class="uinfo">
<span>{{u.name}}</span> 📅<input v-model.number="u.age" type="number"> <i>{{type}}</i>
<span>“{{u.summary}}”</span>
<button v-on:click="$emit('remove')">删除</button>
</div>` //通过模板字符的方式实现多行文本,IE是不支持的。
});
let app9 = new Vue({
el: "#app9",
data: {
users: [
{ name: "张三", age: 14, summary: "垂死病中惊坐起,赶快下楼做核酸" },
{ name: "李四", age: 30, summary: "仰天大笑出门去,下楼排队做核酸" }]
}
})
</script>
1.2、注册组件
- 全局注册:
Vue.component( id, {选项})
,全局组件,任何地方都可以使用。 - 局部注册:
components:{id:{选型}}
选项注册组件,只能在当前注册的Vue环境内使用,不可继承。 - 扩展Vue类:
Vue.extend({选项})
,创建一个Vue的扩展类,通过new()
创建组件实例,也是一种组件复用方式。
<div id="app10">
<div>
<sexbox v-bind:data="uinfo" age="99" required>性别({{uinfo.sex}}):</sexbox>
<user-box :data="uinfo"></user-box>
</div>
</div>
<template id="userSexTemplate">
<div>
<slot></slot>
<label v-for="(value,name) in dic">
<!-- v-bind="$attrs" 用来绑定组件上设置的属性:age="99" required -->
<input type="radio" v-model="data.sex" name="sex" :value="name" v-bind="$attrs">
{{value}}
</label>
</div>
</template>
<template id="userBoxTemplate">
<div>姓名:<input type="text" v-model="data.name">
<sexbox :data="data">性别:</sexbox>
</div>
</template>
<script>
// 性别选择组件
let comSex = {
data: function () { return { dic: { male: '男', female: "女", other: '其他' } } },
props: ['data'],
template: '#userSexTemplate'
}
// 用户信息组件,引入了性别组件
let UserVue = Vue.extend({
props: ['data'],
components: { 'sexbox': comSex },
template: `#userBoxTemplate`,
})
//vue 应用
let app10 = new Vue({
el: "#app10",
data: { uinfo: { name: '核算', sex: 'male' } },
components: { 'user-box': UserVue, 'sexbox': comSex }
})
// app10 = new UserVue({el:'#app10'})
</script>
1.3、is 动态组件
通过is
特性设置(或v-bind:is
绑定)组件名称,动态的申明一个组件。
组件使用申明方式:
<user-info></user-info>
:常规自定义元素方式申明。<component is="user-info"></component>
:component组件元素申明,通过is
设置组件名称,:is
就可以动态绑定组件了。<tr is="user-info"></tr>
:其他HTML元素+is
申明组件,该元素会被组件内容替换掉。这主要是为了解决有些HTML元素中只能包含特定的子元素,如<ul>
、<table>
。
<keep-alive>
<component is="user-info" v-bind:u="users[0]"></component>
</keep-alive>
<table>
<tr is="user-info" v-bind:u="users[0]"></tr>
</table>
🔸keep-alive 缓存组件:用<keep-alive>
包裹动态组件,配合组件特性:is
使用,用来缓存失活的动态组件,避免组件失活后状态丢失。
1.4、Props参数
通过props
定义参数(Array<string> | Object )
,参数都会作为组件元素的attribute
特性使用,通过vm.$props
获取组件参数。
🔸Prop对象验证:除了使用数组设置多个参数,还可以用一个对象来申明多个参数及参数规则,用于参数合法性验证。⚠️注意验证的执行是在组件实例创建之前进行的,此时data
、computed
都还不可用。
- 每个参数可以指定多个验证类型。
- 每个参数可以定义类型
type
、必填require
、默认值default
,以及验证器函数validator
。
<div id="app11">
<sex-box v-bind:data="uinfo" type="vip" age="99" required class="form-item">性别:</sex-box>
</div>
<script>
function User(name = "", sex = "") {
this.name = name; this.sex = sex;
}
// 一个性别选择组件
let sexBox = {
data: function () {
return {
dic: { male: '男', female: "女", other: '其他' },
utype: this.type, //使用prop参数为初始值
}
},
inheritAttrs: false, //组件根元素不继承Attribute(不含style、class)
props: {
data: [Object, User], //User为自定义构造器
type: String,
age: {
type: [String, Number],
required: true,
default: 18,
validator: function (value) { return value > 0 && value < 100 } /* 自定义验证器 */
}
},
// props: ['data', 'type', 'age'],
template: `<div><slot></slot>
<label v-for="(value,name) in dic">
<input type="radio" v-model="data.sex" name="sex" :value="name" v-bind="$attrs">
{{value}}</label> //{{type}}</div>`,
}
let app11 = new Vue({
el: "#app11",
data: {uinfo: new User("张三", 'male')},
components: {'sex-box': sexBox,}
})
</script>
🔸参数绑定:参数作为自定义组件元素的attribute
使用,推荐v-bind:prop
绑定赋值,除是非字符串值,绑定是支持表达式的。
- 如果要传入一个对象的所有 property,直接
v-bind="obj"
即可。
🔸参数的单向传递:参数的值是单向的向下传递的,子组件不可更改。
- 父组件的数据变更会触发子组件所有参数
prop
的刷新。 - 如果传入的是一个引用类型(数组、对象),这个是共享的,会影响父组件状态。
🔸非Props参数的Attribute:对于非Prop参数的Attribute,默认会都应用到组件根元素上,如上面示例中的age="99" required class="form-item"
- 替换:除了
class
、style
会合并,其他如果冲突会替换掉组件内部的Attribute
。 inheritAttrs
不继承:可在组件选项里设置inheritAttrs:false
取消根元素的Attribute
继承。- 主动继承:组件内部其他元素可以用
v-bind="$attrs"
,来主动继承非Prop的Attribute特性(不含class、style)。
1.5、#slot插槽
插槽可以用来插入任何内容,包括文本、HTML、其他组件。结合具名插槽(name)、prop绑定,可以实现更为开放、灵活的组件。
- :在组件内部通过
<slot></slot>
申明一个插槽,整个<slot></slot>
会被替换为组件元素标签的内容(InnerHTML)。 - 后备内容/默认值:
<slot>默认显示内容</slot>
,在没有提供内容时使用后备内容。
<div id="app13">
<com-search-box>
<p>{{search.title[0]}}</p>
<template v-slot:default> <!--效果同上-->
<p>{{search.title[0]}}</p>
</template>
</com-search-box>
</div>
<script>
let comSearchBox = {
template: `<div><slot>搜索</slot> <input><button><b>🔍</b><span>搜索</span></button></div>`,
}
let app13 = new Vue({
el: "#app13",
data: { search: { title: ['百度搜索', '学术搜索', '图片搜索'] } },
components: { 'com-search-box': comSearchBox }
})
</script>
🔸具名插槽,有名字的VIP插槽,name
特性取名,用于需要多个插槽的场景。没命名的插槽<slot>
默认名称为"default
"--默认插槽。
- 必须通过一个模板
<template>
来使用,用slot参数v-slot:name
=#name
指定插槽的名字。无<template>
、不指定名字的内容用于默认插槽。 - 缩写
#
=v-slot:
,只能用于带参数的v-slot
:v-slot:default
。 - 插槽名可以用动态参数,
[data-name]
:<template v-slot:['default']>
🔸作用域插槽:插槽内容访问子组件内部数据
父级不能访问子组件内部的数据,为了可以让插槽内容可以访问到子组件内部的数据,于是有了作用域插槽,主要就是2个步骤。
❶ 组件内部把数据绑定在插槽<slot>
的特性Attribute上 <slot v-bind:data="btnKey">
,称为插槽的Prop,在父级作用域可以访问。
❷ 在组件<template>
上引用插槽prop :<template v-slot="soldData">
,可取一个新名字。当然这里也可以用当前作用域的绑定数据。
<div id="app13">
<com-search-box>
<template #header v-slot:header> <p>{{search.title[1]}}</p> </template>
<template v-slot="soldData">
<b>🔍</b><span>{{soldData.data[1]}}</span>
</template>
</com-search-box>
</div>
<script>
let comSearchBox = {
data: function () { return { 'btnKey': ['搜索', 'search'] } },
template: `<div>
<slot name="header"></slot> <input>
<button> <slot v-bind:data="btnKey">{{btnKey[0]}}</slot> </button>
</div>`,
}
let app13 = new Vue({
el: "#app13",
data: { search: { title: ['百度搜索', '学术搜索', '图片搜索'] } },
components: { 'com-search-box': comSearchBox }
})
</script>
🌰todo-list(代办列表)的示例:马上掘金 | codepen
1.6、组件树关系
当我们构建一颗树形结构(如文件目录树)的组件时,会递归循环引用,造成组件的循环依赖。
- 全局注册组件。
- 在
beforeCreate
手动注入子组件。 - 设置webpack异步
import
引入组件。
1.7、总结:组件通信
通过组件关系树也是可以的,不过不推荐,耦合性太高。
02、可复用性 & 组合
2.1、[mixins]混入(CtrlV)组件代码
混入(mixins)可以灵活复用组件中的代码,可惜只能用于组建内,也仅支持Vue的选项。步骤:
🔸代码混入(合并)规则:
- 选项合并:如果混入时存在代码冲突,则会先递归合并,然后本地优先的策略。
- 混入的钩子,合并为一个数组,依次调用。
- 全局混入
Vue.mixin( mixin )
,应用于后面所有的Vue对象。 - 混入优先级:本地代码优先 > 局部混入 > 全局混入。
- 自定义混入合并策略:
Vue.config.optionMergeStrategies
let cmixin = {
data: { message: "hello world!" },
methods: { say: function () { console.log(this.message) } }
}
let app1 = new Vue({
el: "#app1",
data:{name:'sam'},
mixins: [cmixin],
//...cmixin, //不支持合并,会覆盖已有属性值
created: function () { this.say() }
})
2.2、v-自定义指令
指令是对Dom元素的扩展,具有一定的行为特征(钩子函数)。注册的自定义指令,需要用指令的钩子函数来触发指令行为,一个指令支持多个钩子函数,不同钩子函数(触发点)可设置不同的行为。如果不指定钩子函数,就是全都要,都会触发调用。
🔸注册方式:全局、局部
- 全局注册:
Vue.directive( id, [definition] )
,注意顺序,先定义后使用。 - 局部注册-选项:
directives:{ id: { } }
。
🔸指令命名:参考HTML命名规范,小写+连字符,正式指令名称会加上v-
。
🔸指令函数参数:(el, binding, vnode, oldVnode)
- el:绑定的Dom元素。
- binding:指令对象,包含指令名称
name
、参数arg
、修饰符modifiers
、属性值value
、上一次的属性值oldValue
、指令表达式expression
。这里的值可用来做变化判断,以优化指令性能。 - vnode:虚拟Dom元素
- oldVnode:上一个虚拟Dom元素
🔸使用:<input v-id:arg.ky='value'>
🌰自定义指令-验证器表单输入:值为需要验证的数据,其他验证参数check-reg、check-error用自定属性申明,指令用在表单元素后面的一个用于提示错误的元素上。
<div id="app1">
<p>
<label>用户名:<input type="text" v-model="userName" maxlength="40" size="40" placeholder="请输入用户名"></label>
<span v-check.required="userName" check-reg="^\w{4,6}$" check-error="用户名必须是4-6位的字母数字"></span>
</p>
<p>
<label>用户名:<input type="text" v-model="email" maxlength="40" size="40" placeholder="请输入邮箱"></label>
<span v-check="email" check-reg="^\w+@\w+\.\w+$" check-error="邮箱格式不合法"></span>
</p>
</div>
<script>
// 验证器(必填修饰符),正则,错误信息,check,check-reg,check-error
let mixinDirectivesValidator = {
directives: {
check: { //check指令:v-check
//绑定指令时触发
bind(el, bind) {
//先读取check的配置属性信息,暂存备用
el.text = el.getAttribute('check-error');
el.reg = new RegExp(el.getAttribute('check-reg'));
el.style.color = 'red';
el.style.fontSize = '0.8em';
if (bind.modifiers.required) el.innerText = ' * 必填';
},
//更新视图时触发
update: function (el, bind) {
if (bind.value === bind.oldValue) return;
el.innerText = '';
if (bind.modifiers.required) el.innerText = ' * 必填';
if (bind.value && !el.reg.test(bind.value)) el.innerText = el.text;
}
}
}
}
let app1 = new Vue({
el: "#app1",
data: { userName: '', email: '' },
mixins: [mixinDirectivesValidator]
})
</script>
2.3、| 插值过滤器
过滤器就是一个函数,在{{文本插值}}
、v-bind="表达式"
绑定表达式后面使用,对绑定的值进行过滤(再加工)处理。支持链式的过滤使用,支持参数,常用于格式化显示。在V2、V3中,过滤器被逐渐弱化,更推荐用表达式或计算属性。
- 全局过滤器:
Vue.filter( id, func(value,...arg )
,第一个参数为管道符号前面的绑定值。 - 局部过滤器-
filters{id:func(value,...arg)}
选项,(❗多了个s
) - 使用:用管道符号连接,
{{文本插值 | filter1 | filter2(arg)}}
<style>
#app2 *{
font-family:'Courier New', Courier, monospace;
}
</style>
<div id="app2">
<ul>
<li v-for="a in arr">{{a | fixedLength(3,' ')}}: {{a|fixedLength(4)| money|money|money}}</li>
</ul>
</div>
<script>
//全局过滤器,数字固定长度
Vue.filter("fixedLength", function (value, length,char='0') {
return (Array(length).join(char) + value).slice(-length);
})
let app2 = new Vue({
el: "#app2",
data: { arr: [1, 2, 3, 44, 55, '5K'] },
//具备过滤器定义
filters: {
money: function (v) { return '¥' + v; }
}
})
</script>
2.4、Vue插件开发
插件就是一个函数或者包含install()
方法的对象,目的是实现封装复用。通过Vue.use()
来安装,给Vue提供全局的扩展能力,如添加全局的指令、方法、组件,混入选项等,很多Vue的UI组件都是这么处理的。
install(Vue,...args)
:插件应该提供的安装函数,第一个参数就是Vue构造器,就可以调用Vue的静态方法做一些处理。后面的参数就是插件自己用的参数了。Vue.use(插件, ...args)
:注册插件,第一个参数为插件,后面就是插件需要的参数选项了。
// 封装插件
export let SuperPlugin = {
install(Vue, ...args) {
console.log('安装插件SuperPlugin', args);
//安装一个UI组件
Vue.component('super-btn', { template: '<button>Super<slot></slot></button>' });
//添加全局静态方法
Vue.$get = function (url) { /*ajax get*/ };
Vue.$post = function (url, data) { /*ajax post*/ };
//添加实例方法
Vue.prototype.$myMethod = function () { };
//混入全局的选项
Vue.mixin({ created: function () {console.log('创建组件') } });
}
}
// 注册插件
import { SuperPlugin } from './super-plugin.js'
Vue.use(SuperPlugin, 1, 2, 3);