vue组件基本使用
组件的注册
注册组件有两种注册方式: 分为“全局注册”和“局部注册”两种
- 被全局注册的组件,可以在任意的组件模板范围中使用 通过
Vue.component()
- 被局部注册的组件,只能在当前注册的组件模板范围内使用 通过
components
局部注册
把独立的组件封装一个.vue文件中,推荐放到components文件夹
比如现在 components文件夹中 有一个 HmHeader.vue 组件,现在将他注册到 App.vue 组件中
1.当标签使用 <HmHeader></HmHeader>
2.导入组件 import HmHeader from '@/components/HmHeader'
3.注册组件 components:{ HmHeader }
==局部注册的组件只能在当前组件中使用==
全局注册组件
比如现在 components文件夹中 有一个 HmHeader.vue 组件,现在将他注册为全局的的组件
1.当标签使用 <HmHeader></HmHeader>
2.在main.js中导入组件 import HmHeader from '@/components/HmHeader'
3.注册为全局组件 Vue.component('HmHeader','HmHeader')
如果HmHeader组件的 'name'的名字为 HmHeader 也可以这样注册
Vue.component(HmHeader.nmae,'HmHeader')
==注意:全局注册的组件 可以在任意的组件中去使用==
组件的样式冲突 scoped
- 加:只管当前组件和子组件的最外层,
- 看得到的管得到,看不到的管不到,子组件看得最外层
- 一般都要加
- 不加:全局
默认情况下,写在组件中的样式会全局生效
,因此很容易造成多个组件之间的样式冲突问题。
组件样式默认会作用到全局, 就会影响到整个 index.html 中的 dom 元素
全局样式
: 默认组件中的样式会作用到全局-局部样式
: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
<style lang="less" scoped>
div {
background-color: pink;
}
</style>
原理:
添加scoped后, 会给当前组件中所有元素, 添加上一个自定义属性
添加scoped后, 每个style样式, 也会加上对应的属性选择器
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
组件通信
每个组件都有自己的数据, 提供在data中, 每个组件的数据是独立的, 组件数据无法互相直接访问 (合理的)
但是如果需要跨组件访问数据, 就需要用到组件通信
组件通信的方式有很多: 现在先关注两种, 父传子 子传父
父传子 props 传值
语法:
- 父组件通过给子组件加属性传值
<Son price="100" title="不错" :info="msg"></Son>
- 子组件中, 通过props属性接收
props: ['price', 'title', 'info']
关于 props 的注意点
props 是父传子, 传递给子组件的数据, 为了提高 子组件被使用时 的稳定性, 可以进行props校验, 验证传递的数据是否符合要求
默认的数组形式, 不会进行校验, 如果希望校验, 需要提供对象形式的 props
风格指南定义必要
props 提供了多种数据验证方案,例如:
- 基础的类型检查 Number
- 多个可能的类型 [String, Number]
- 必填项校验 required: true
- 默认值 default: 100
- 自定义验证函数
官网语法: 地址
{
props: {
// 基础的类型检查
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
/* default:不传入时的默认值
基本数据类型:直接定义默认值
复杂数据类型(对象,function,Array):()=>{return 复杂数据类型}*/
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
arr: {
// 默认值是复杂数据类型
type: Array,
default: () => {
return []
}
}
// -------------------------------------------------------------------------
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
注意:
在vue中需要遵循单向数据流原则
父组件的数据发生了改变,子组件会自动跟着变
子组件不能直接修改父组件传递过来的props props是只读的
拓展(了解)
- props:['属性名']
- 单向数据流:本意,一个公共组件所有子组件都可随便修改数据,后面出现问题,不好确定谁改的,找bug会麻烦,定位问题会很麻烦
- 单向数据流(作者是希望什么都不要在子组件内改)
- 实际开发时,经常是使用:堆随便修改,栈不可修改
- 基本数据类型(栈):不可修改
- 复杂数据类型:只要不修改它的引用地址,它的值随便修改
- Object
- Array
- Function
- 什么是修改引用地址,什么是修改值?
- 引用地址存储地堆还是栈中?栈
- 值是存储在哪里的? 堆
- 堆随便修改,栈不可修改
子传父 emit
- 1.子组件可以通过 this.$emit('事件名', 参数1, 参数2, ...) 触发事件的同时传参的
this.$emit('sayPrice', 2)
- 2.父组件给子组件注册一个自定义事件
<my-product
...
@sayPrice="sayPrice">
</my-product>
父组件并提供对应的函数接收参数
methods: {
sayPrice (num) {
console.log(num)
}
}
$event
$event代表方法触发时的默认传参值
- 原生事件中:代表事件对象
<button @click="btnClick($event)">按钮</button>
这里的$event代表事件对象,就是原生事件的e
- 组件标签事件绑定中:代表子组件触发父组件方法时传入的参数值
<组件标签 @xxx方法="fn($event)" />
这里的$event代表组件在触发该方法时传入的实参值
this.$emit('xxx方法',实参值)
还有一种 v-model语法糖的写法
父组件提供一个数据给子组件使用(父传子)
子组件又需要修改父组件传过来的这个数据,所以需要子传父把值传给父组件。
这种场景可以使用v-model进行简写。
但要注意:
1.定义组件的时候,注意接收的值叫value, 子传父触发的事件叫 input
具体用法 在笔记 v-mode指令 用法里面记录了
ref 和 $refs
利用 ref 和 $refs 可以用于获取 dom 元素, 或者组件实例
每个 vue 的组件实例上,都包含一个$refs 对象,里面存储着对应的DOM 元素或组件的引用。
1 .给需要获取的 dom 元素或者组件, 添加 ref 属性
<div>
在dom元素中使用
<div ref="box">我是div盒子</div>
在组件中使用
<JaCk ref="jack"></Jack>
<button @click="fn">按钮</button>
</div>
2 .通过 this.$refs.xxx
获取, 放在普通标签上可以拿到当前Dom元素,写在组件标签上拿到组件可以调用组件的 Data中的属性和methods中的方法
import Jack from './jack.vue'
export default {
methods: {
fn () {
console.log(this.$refs.box)
console.log(this.$refs.jack)
this.$refs.jack.sayHi() //调用父组件中的sayHi方法
}
},
components: {
Jack
}
}
$nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的 DOM 进行操作时,要在
nextTick
指定的回调函数中执行 - 组件的
$nextTick(callback)
方法,会把 callback 回调推迟到下一个 DOM 更新周期之后执行,即在 DOM 更新完成后再执行回调,从而保证 callback 回调可以获取最新的 DOM 元素
<template>
<div>
<!-- 需求: 点击按钮, 切换显示输入框 -->
<input ref="inp" type="text" v-if="isShowInput">
<button @click="fn" v-else>点此搜索</button>
</div>
</template>
<script>
export default {
data () {
return {
isShowInput: false
}
},
methods: {
fn () {
this.isShowInput = true
//this.isShowInput = true 执行完时, 实际的 dom 还没渲染出来
this.$nextTick(() => {
//nextTick 会等组件的DOM 刷新之后,再执行 callback 回调函数
this.$refs.inp.focus()
})
}
}
}
</script>
动态组件
- 什么是动态组件: 让多个组件使用同一个挂载点,并动态切换,这就是动态组件
用法:
<标签名>标签名>
- is的值是什么,它就是什么组件
- is的值一定要绑定,一定要加:
<template>
<div>
<h3>动态组件的演示</h3>
<!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
<button @click="comName = 'my-swiper'">swiper</button>
<button @click="comName = 'my-nav'">nav</button>
<!--
<my-nav></my-nav>
<my-swiper></my-swiper>
-->
<component :is="comName"></component>
</div>
</template>
<script>
import MyNav from './my-nav.vue'
import MySwiper from './my-swiper.vue'
export default {
data () {
return {
comName: 'my-nav'
}
},
components: {
MyNav,
MySwiper
}
}
</script>
Keep-alive
默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive>
组件保持动态组件的状态,对被包裹的组件进行状态缓存。
被 <keep-alive>
包裹的组件会多出两个生命周期函数:当组件被激活时,触发 activated
钩子;当组件被缓存时,触发 deactivated
钩子。
<keep-alive>
<component :is="comName"></component>
</keep-alive>
<keep-alive>
的 include
和 exclude
属性,分别用于指明哪些组件要缓存、哪些组件不要缓存。
<keep-alive include="Left, Right">
<component :is="comName"></component>
</keep-alive>
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>
自定义指令
- 自定义指令
- 私有自定义指令:在组件的
directives
节点声明- 全局自定义指令:在
main.js
文件中声明
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。 v-xxx
注意,代码复用和抽象的主要形式是组件。
然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新(VNode:Vue 编译生成的虚拟节点)。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
钩子函数参数
指令钩子函数会被传入以下参数:(el,binding,vonde,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 来了解更多详情。(通过vonde.context.属性 可以拿到指令所在组件的 Data 属性值)oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
完整写法示例
<input type="text" v-focus="鸡你太美"/>
directives: {
自定义一个局部指令
focus: {
bind(el, binding, vnode) {
// 在bind函数中可以对元素进行一些初始化的设置,但不能操作该元素
el.style.color = 'yellow'
el.value = binding.value
},
inserted(el, binding, vnode) {
// 在inserted函数中元素已经渲染到模板当中,此时可以操作该DOM元素
el.focus()
},
update(el, binding, vnode) {
// 当指令所在模板结构被重新解析时,update函数会被调用,此时指令的值可能没改变
console.log('update')
el.value = binding.value
},
componentUpdated(el, binding, vnode) {
// 指令所在模板的值全部更新完毕
console.log('update')
el.value = binding.value
},
unbind(el, binding, vnode) {
// 只调用一次,指令解绑时调用
console.log('update')
el.value = binding.value
},
}
}
-------------------------------------------------------------------
全局写法:在main.js中声明一个全局自定义指令
Vue.directive('focus', {
bind(el, binding) {
el.value = binding.value
}
inserted(el, binding) {
el.focus()
}
update(el, binding) {
el.value = binding.value
}
})
简写形式
- 当
bind
函数和update
函数里的逻辑完全相同时,可以简写 - 不需要定义
inserted
函数操作Dom元素时 才使用简写形式 - 因此简写形式的调用时机:初次绑定和 DOM 更新(指令所在模板被重新解析)
<h2 v-color="'red'">简写形式</h2>
directives: {
color(el, binding) {
el.style.color = binding.value
}
}
// 全局写法
Vue.directive('color', (el, binding) => {
el.style.color = binding.value
}))
注意事项
- 自定义指令使用时需要添加
v-
前缀 - 指令名如果是多个单词,要使用
kebab-case
短横线命名方式,不要用camelCase
驼峰命名
<span v-big-number="n"></span>
data() {
return {
n: 1
}
},
directives: {
// 添加引号才是对象键名完整写法
// 平时不加引号都是简写形式
// 遇到短横线的键名就必须添加引号
'big-number': {
bind(el, binding) {
console.log(this) // Window
el.innerText = binding.value * 10
}
}
}
插槽
何为插槽
- 插槽可以理解为组件封装期间,为用户预留的内容占位符。它是 vue 为组件封装者提供的能力,允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
插槽作用:
用于实现组件的内容分发, 通过 slot 标签, 可以接收到写在组件标签内的内容**
插槽:slot 作用:占位置
默认插槽
基础使用:
<!-- 子组件中预留插槽 -->
<template>
<div class="contianer">
<h1>这是子组件</h1>
<slot></slot>
</div>
</template>
<!-- 父组件使用子组件时,向插槽填充内容 -->
<child-comp>
<p>填充到插槽的内容</p>
</child-comp>
如果子组件没有预留插槽,那么父组件填充给子组件的自定义内容会被丢弃:
<!-- 子组件没有预留插槽 -->
<template>
<div class="contianer">
<h1>这是子组件</h1>
</div>
</template>
<!-- 父组件的自定义内容会被丢弃 -->
<child-comp>
<p>这段自定义内容会被丢弃</p>
</child-comp>
子组件可以为插槽提供后备内容,当父组件没有提供自定义内容时,后备内容就会生效。
<!-- 子组件提供后备内容 -->
<template>
<div class="contianer">
<h1>这是子组件</h1>
<slot>这是后备内容,父组件没有提供自定义内容就会生效</slot>
</div>
</template>
<!-- 父组件没有提供自定义内容 -->
<child-comp> </child-comp>
具名插槽
插槽的分类:
1 .默认插槽(匿名插槽)
<slot></slot>
只要没有具体分发的内容, 都会给到默认插槽<slot name="default"></slot>
是默认插槽完整的写法 和<slot></slot>
完全等价
2..具名插槽: 具有名字的插槽 (配置了名字), 可以实现定向分发
- 一旦配置了名字, 只会接收对应的内容, 不是分发给他的, 就不要
<!-- 子组件预留多个具名插槽 -->
<template>
<div class="contianer">
<h1>这是子组件</h1>
<slot name="title">title 具名插槽</slot>
<hr />
<slot name="content">content 具名插槽</slot>>
<hr />
----------------------------------------------
<slot>没有设置 name 名称则默认为 default</slot>
<slot name="default"></slot>
</div>
</template>
父组件向具名插槽提供自定义内容
- 新的写法:包裹一个
<template>
标签,同时在<template>
中通过v-slot:名称
指明插槽的名称。简写形式为#名称
,且v-slot
只能使用在<template>
和组件标签上,普通 HTML 标签不行 - 旧的写法:
slot="名称"
指明插槽名称 - 如果不指定插槽名称,那么自定义内容会被填充到所有的
default
插槽当中 - 同一插槽填充多个内容,是追加不是覆盖
<!-- 父组件向具名插槽提供自定义内容 -->
<child-comp>
旧写法
<h1 slot="title">《赠汪伦》</h1>
-------------------------------------------
<template v-slot:title>
<h1>《静夜思》</h1>
</template>
<!-- 简写形式 -->
<template #content>
<p>床前明月光,疑是地上霜。</p>
<p>举头望明月,低头思故乡。</p>
</template>
<template>
<p>这段内容没有指定名称,会被填充到所有 default 插槽中。</p>
</template>
</child-comp>
作用域插槽
默认插槽和具名插槽都可以传递参数
默认插槽传值
子组件插槽传值
...
<slot xxx="123"><slot>
...
父组件接收
<template v-slot="Xx">{{Xx}}</template>
//Xx拿到得数据会被一个对象包裹 { "xxx": 123 } 所以需要 Xx.xxx才可以拿到123
具名插槽传值
子组件 给 slot 标签, 以 添加属性的方式传值
<slot name="bottom" :yes="yes" :no="no" money="100"></slot>
所有添加的属性, 都会被收集到一个对象中
{ yes: '确认', no: '取消', money: '100' }
父组件接收 在template中, 通过 v-slot:插槽名= "obj"
接收
<template #bottom="obj">
<!-- {{ obj }} -->
<button>{{ obj.yes }}</button>
<button>{{ obj.no }}</button>
<button>{{ obj.money }}</button>
</template>
补充
- 因为 bottom={ yes: '确认', no: '取消', money: '100' }
- 所以可以解构为 { yes , no , money } = bottom
注意 : 只有具名插槽才可以这样解构
<template #bottom="{ yes, no, money }">
<button>{{ yes }}</button>
<button>{{ no }}</button>
<button>{{ money }}</button>
</template>