3.模块化
3.1.组件
可复用的页面元素可以封装成组件
3.1.1.私有组件步骤
3.1.1.1.定义格式
定义一个可以配置常用信息的文本框
let MyPass = Vue.extend({
name: 'MyInput',
template: `
<div>
<label>{{ title + ' : ' }}</label>
<input type="text" v-model="val" :placeholder="placeholder" @blur="checkVal" />
</div>
`,
props: {
title : {
type: String,
default: '姓名'
}
},
computed: {
placeholder(){
return '请输入' + this.title
}
},
data(){
return {
val: 'abc'
}
},
methods: {
checkVal(){
console.log( this.val )
}
}
})
使用 Vue.extend()
以继承Vue的方式 定义变量 MyPass
, 这样就可以使用Vue定义的属性( el 属性不能使用 )
通常也可以省略直接定义成 JSON对象
name
: 组件的名称
template
: 定义 组件模版, 必须只有一个根结点
props
: 定义组件属性, 属性的 值 是在使用组件时赋值, 相当于 接收 父组件传来的值
data
: 定义组件数据, 必须是一个函数, 不能是一个对象 (对象是单例的, 函数每次调用生成新的)
methods
: 定义 组件事件
3.1.1.2.声明组件
const vm = new Vue({
el: '#app',
data: {
},
components: {
MyInput
}
})
在 vue 对象中增加 components
属性来声明 要使用的组件
3.1.1.3.使用组件
<div id="app">
<my-input title="账号" ></my-input>
<my-input title="姓名" ></my-input>
</div>
使用时 通过 title 属性将 信息传入到 组件中,
组件可以重复使用
3.1.2.公共组件
直接通过 Vue.component()
创建的组件, 不用在vue对象中声明, 可以在多个 vue实例中使用的
这个案例 没有什么实际意义, 单纯为了说明 Vue.component()
功能
Vue.component('my-title',{
template: '<h1>{{msg}}</h1>',
props: {
msg: {
type: String,
default: '标题'
}
}
})
页面使用
<my-title msg="测试标题"></my-title>
3.1.3.组件接值案例
3.1.3.1.案例一
vue 对象 data 属性, 增加 val 为 姓名
data: {
val : '姓名'
},
页面增加 单选框 元素 与 val 双向绑定
<my-input :title="val" ></my-input>
<br>
<input type="radio" v-model="val" :value="'姓名'" /> 姓名
<input type="radio" v-model="val" :value="'学号'" /> 学号
<input type="radio" v-model="val" :value="'账号'" /> 账号
这样 选择 单选框 修改 vue中 data中的 val值, 再通过 组件的 :title="val"
向组件传值
3.1.3.2.案例二
vue 对象 data 属性, 增加 types 数组
data: {
types : ['姓名','学号','账号']
},
页面使用 v-for 进行组件 赋值
<my-input v-for="(val,index) in types" :key="index" :title="val" ></my-input>
3.1.4.组件回传值
子组件可以使用 $emit() 触发父组件的自定义事件。
vm.$emit( event, arg )
//触发当前实例上的事件
父组件通过自定义事件接值
3.1.4.1.修改子组件
在函数中增加 代码 , 其中 retval 对应父组件自定义事件, this.val 是传入事件函数的值
methods: {
checkVal(){
console.log( this.val )
this.$emit('retval', this.val)
}
}
3.1.4.2.修改组件使用代码
在组件上 增加 @retval 自定义事件, 触发 调用 showChildValue 函数
<my-input title="账号" @retval="showChildValue" ></my-input>
在 methods 中 定义函数, 可以接收传回来的参数
methods: {
showChildValue(val){
console.log(val)
}
}
3.1.5.父子嵌套组件
注意, 使用 其它的组件 要先声明组件, 并且声明的组件要 在前面已经定义
而公共组件可以直接使用
let LoginPage = Vue.extend({
template: `
<div style="width: 300px;text-align: center;">
<my-title msg="登录系统"></my-title>
<my-input title="账号" ></my-input> <br>
<br>
<button @click="login" >登录</button>
</div>
`,
components: {
MyInput
},
methods: {
login() {
alert('登录成功')
}
}
})
3.1.6.sync修饰符
Vue.js 中的 .sync
修饰符用于简化子组件向父组件更新 props 数据的流程,尤其是在需要实现类似于双向数据绑定的情形下。
尽管 Vue.js 严格遵循单向数据流原则,即 Props向下传递,事件向上冒泡,但在某些场景下,子组件确实需要更改父组件的状态。
在 Vue 2.x 版本中,.sync
修饰符提供了一种便捷的方式来实现这种行为。
当在子组件的 prop 上使用 .sync
修饰符时,实际上是告诉 Vue 在子组件内部变更该 prop 值时,自动通过自定义事件机制将更新传递给父组件。
例如,在 Vue 2 中,如果我们有一个父组件向子组件传递 value
prop,并希望子组件能同步修改这个值,我们可以这样配置:
<!-- 父组件 -->
<template>
<child-component :value.sync="parentValue"></child-component>
</template>
<script>
export default {
data() {
return {
parentValue: '初始值'
};
}
};
</script>
<!-- 子组件 -->
<template>
<input type="text" v-model="localValue" />
</template>
<script>
export default {
props: ['value'],
data() {
return {
localValue: this.value
};
},
watch: {
localValue(newVal) {
this.$emit('update:value', newVal);
}
}
};
</script>
在这个例子中,子组件通过 $emit('update:value', 新值)
触发自定义事件将新值传递给父组件。.sync
修饰符让父组件无需手动监听这个事件并更新自身的 parentValue
,而是自动完成了这一过程。
3.1.7.props属性
在Vue 2中,组件的props主要有以下几种配置方式和类型:
3.1.7.1.配置方式:
-
简单声明:
Vue.component('my-component', { props: ['propA', 'propB'], // 使用字符串数组声明props名称 // ... });
这种方式下,Vue允许父组件传递任何类型的数据到子组件,并且不做任何验证。
-
对象形式:
Vue.component('my-component', { props: { propA: String, // 类型声明 propB: Number, propC: { type: Boolean, default: false, required: true } }, // ... });
通过对象的方式可以更加细致地配置props,包括但不限于以下属性:
type
: 指定prop接受的数据类型,如String, Number, Boolean, Array, Object, Function, Symbol, 或自定义构造函数等。default
: 设置prop的默认值。required
: 标记该prop是否必填,如果是必需的prop并且在父组件中未传递,则Vue会发出警告。validator
: 自定义验证函数,用于验证传入prop的值是否满足某些额外的条件。
-
Prop验证:
除了基础类型之外,还可以使用复合类型,例如:propD: { type: Object, default: () => ({ value: '' }), validator: function(value) { // 自定义验证逻辑 return typeof value === 'object' && !Array.isArray(value); } }
3.1.7.2.Prop类型列表:
String
Number
Boolean
Array
Object
Function
Symbol
Date
- Vue组件实例
- 自定义构造函数(如自定义的类)
此外,Vue还支持枚举类型(Enum),可通过数组定义允许的值:
propE: {
type: String,
validator: value => ['option1', 'option2'].includes(value)
}
也可以简化为:
propE: {
type: ['option1', 'option2']
}
Vue 2并不直接支持枚举类型作为type
选项,但可以通过validator
实现类似效果。不过在Vue 3中引入了type
可以为枚举数组的功能。
3.2.模版<template>
在 Vue.js 中,<template>
标签用于定义一个包裹多个元素的容器,它本身不会被渲染成 DOM 元素,而是作为一个逻辑分组来帮助组织模板结构。
3.2.1.包裹元素标签
通常情况下,使用 <template>
的主要原因包括:
3.2.1.1.条件渲染
当你需要基于条件来决定一组元素是否渲染时,可以将这一组元素放在 <template>
标签内,并配合 v-if
或 v-show
指令来控制整体渲染。
<template v-if="isVisible">
<div>当 isVisible 为true 时 才会渲染</div>
<p>可以是多个html 元素</p>
</template>
3.2.1.2.循环渲染
在列表渲染 v-for
中,为了更好地组织循环体内的结构,可以使用 <template>
来包裹多层或多个元素。
<ul>
<template v-for="item in items">
<li :key="item.id">{{ item.name }}</li>
<template v-if="item.hasSubItems">
<ul>
<li v-for="subItem in item.subItems" :key="subItem.id">{{ subItem.name }}</li>
</ul>
</template>
</template>
</ul>
3.2.2.简化自定义组件
组件 的 template 模版 可以独立成 html 的< template > 结点
注意这时
<template>
结点 不要写在<div id= "app" ></div>
里因为这个div 会被 vue 解析
同样要有 唯一 根结点
<div id="app">
<my-input title="账号" ></my-input>
</div>
<template id="myInput">
<div>
<label>{{ title + ' : ' }}</label>
<input type="text" v-model="val" :placeholder="placeholder" @blur="checkVal" />
</div>
</template>
而 组件 template 属性 根据 id 关联 <template>
结点
let MyInput = {
name: 'MyInput',
template: "#myInput",
props: {
title : {
type: String,
default: '姓名'
}
},
computed: {
placeholder(){
return '请输入' + this.title
}
},
data(){
return {
val: 'abc'
}
},
methods: {
checkVal(){
console.log( this.val )
}
}
}
3.3. 插槽
Vue.js 中的插槽(Slots)是一种用于组件间内容分发的强大机制,它允许开发者在父组件中定义内容,然后将这些内容插入到子组件的特定位置。
这样可以极大地提高组件的复用性和灵活性。
Vue.js 中有两种主要类型的插槽:
3.3.1.默认插槽
默认插槽是组件中没有指定名称的插槽。
在子组件中,使用 <slot>
标签作为占位符,父组件可以在这个位置插入内容。
<!-- 子组件 -->
<template id="child-card" >
<div class="card">
<h3>标题</h3>
<slot>默认内容,当父组件没有插入内容时显示</slot>
</div>
</template>
<!-- 父组件 -->
<template id="parent-card" >
<custom-card>
<p>这是来自父组件的自定义内容,将替换子组件的默认插槽。</p>
<p>这些也会一起替换子组件的插槽。</p>
</custom-card>
</template>
定义组件
// 子组件
Vue.component('custom-card', {
template: '#child-card'
})
// 父组件
Vue.component('parent-card', {
template: '#parent-card'
})
const vm = new Vue({
el: '#app'
})
页面使用
<div id="app">
<parent-card></parent-card>
</div>
3.3.2.具名插槽
具名插槽允许组件拥有多个插槽,每个插槽都有一个独特的名称,从而允许父组件插入到子组件的不同位置。
<!-- 子组件 -->
<template id="child-card" >
<div class="dialog">
<header>
<slot name="header">默认头部</slot>
</header>
<main>
<slot>默认主体内容</slot>
</main>
<footer>
<slot name="footer">默认底部</slot>
</footer>
</div>
</template>
<!-- 父组件 -->
<template id="parent-card" >
<custom-dialog>
<h2 slot="header">自定义头部</h2>
<p>这是自定义主体内容</p>
<button slot="footer">自定义底部按钮</button>
</custom-dialog>
</template>
3.3.3.作用域插槽
作用域插槽允许子组件在插槽中传递数据给父组件插入的内容。在 Vue 2 中,使用 slot-scope
特性来接收作用域插槽的数据;
<!-- Vue 2 子组件 -->
<template id="child-card" >
<div class="list">
<slot v-for="item in items" :tm="item">
<!-- 默认内容, 当父组件没有插入内容时显示 -->
{{ item.text }}
</slot>
</div>
</template>
<!-- Vue 2 父组件 -->
<template id="parent-card" >
<custom-list :items="items">
<template slot-scope="{ tm }">
<div>
<strong>{{ tm.text }}</strong>
<em>{{ tm.detail }}</em>
</div>
</template>
</custom-list>
</template>
在子组件中 :
- 子组件有一个名为 “items” 的 prop(属性),期望从父组件接收一个数组。
- 使用
v-for
指令遍历 “items” 数组,每次迭代都会生成一个新的 slot 元素。 - slot 元素包含了
:tm
特性,用于将当前遍历到的数组项(item)传递给父组件。
在父组件中 :
- 父组件模板中引用了名为 “custom-list” 的组件,并向其传递一个名为 “items” 的 prop,假设它是一个包含多个对象的数组,每个对象至少包含 “text” 和 “detail” 属性。
- 在 “custom-list” 组件内,使用
<template>
标签配合slot-scope="{ tm }"
来接收子组件传递过来的每个数组项(item)。 - 父组件定义了自己的内容模版,用于替换子组件中
v-for
所生成的每一个 slot。这意味着当遍历 “items” 数组时,会根据父组件提供的模版来渲染每一项内容。
两个组件的定义
// 子组件
Vue.component('custom-list', {
template: '#child-card',
props: {
items: {
type: Array,
required: true
}
}
})
// 父组件
Vue.component('parent-card', {
template: '#parent-card',
data() {
return {
items: [
{ text: '王', detail: '王小二' },
{ text: '李', detail: '李小三' },
{ text: '赵', detail: '赵小四' }
]
}
}
})
3.3.3.1.实例: 格式化时间
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<now-date></now-date>
</div>
<template id="child-temp">
<div >
<slot :fdate="formattedDate">
{{ date }} : {{ formattedDate }}
</slot>
</div>
</template>
<template id="parent-temp">
<my-date :date="date" >
<!-- 这部分会替换 子组件的 slot部分, 并通过 slot-scope 得到 回传值 -->
<template slot-scope="{ fdate }" >
<div>{{ fdate }}</div>
</template>
</my-date>
</template>
</body>
<script type="text/javascript">
// 子组件 , 得到时间, 格式化后 通过 :fdate 回传
Vue.component('my-date', {
template: "#child-temp",
props: {
date: {
type: Date,
required: true
}
},
computed: {
formattedDate() {
return this.date.toLocaleString()
}
}
})
// 父组件 , 传入当前时间
Vue.component('now-date', {
template: "#parent-temp",
data() {
return {
date: new Date()
}
}
})
const vm = new Vue({
el: '#app',
})
</script>
</html>