繁星永存 记忆亘古不变

vue-cli

什么是 vue- cli

tip vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

安装与使用

安装

npm -g @vue/cli

使用 快速初始化

vue create 项目名字

运行流程

vue 通过 main.js 把 app.vue 渲染到 index.html 指定区域

  • app.vue 用来编写待渲染的模板结构
  • index.html 中需要预留 el 区域

vue 组件的三个组成部分

template(组件的模板结构)

<template>
<div>
<!-- 定义当前组件的 DOM 结构 -->
</div>
</template>
  • 每个组件的模板结构 ,需要调用到 template
  • template 的 vue 提供的容器标签 ,只有包裹性作用,不会渲染为真正的DOM元素
  • template 只能包含唯一的根节点
  • 简单理解里面写 HTML即可

script(组件的 JavaScript 行为)

<script>
export default {
// 封装组件的 JS 业务逻辑
}
</script>

style (组件的样式)

<style>
/* css代码 */
</style>

添加less 语法支持

<style leng="less">
/* less代码 */
</style>

每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分

组件关系

封装好 App.vue Left.vue Right.vue 之后,彼此间相互独立不存在关系

使用组件

<template>
<div>
<!-- 步骤三:以标签形式使用组件 -->
<Left></Left>
</div>
</template>
<script>
// 步骤一:使用 import 导入需要的组件
import Left from "@/components/Left.vue"
export default{
// 步骤二:使用 components 结点注册组件
components:{
Left,
}
}
</script>

全局组件

通过 components 注册的是私有子组件

在组件 A 的 components 节点下,注册了组件 F。

则组件 F 只能用在组件 A 中;不能被用在组件C 中。

注册全局组件

在 vue 项目的 main.js 入口文件,通过 Vue.component() 注册全局组件

// 导入需要注册的全局组件
import Count from "@/components/Count.vue"

props (自定义属性)

tip props 是组件的自定义属性,在封装通用组件的时候,合理地使用props 可以极大的提高组件的复用性!

导入组件后以标签形式使用组件 在标签即可使用 自定义属性

<!-- 绑定自定义属性  -->
<!-- 自定义属性有小驼峰命名 改成连字符-命名 -->
<count :init="1" :cmt-count="2"></count>
// count.vue
export default {
// 组件的自定义属性
// 格式一
// props: ["自定义属性1","自定义属性2","自定义属性3"],
// props: ["init"],
// 格式二:props:{
// 自定义属性1:{...}
// 自定义属性2:{...}
// 自定义属性3:{...}
// }
props: {
// 自定义属性名
init: {
// 默认值,如果用户不传递默认的值
default: 0,
// 类型 ,传递过来的值必须是规定类型的值
type: Number,
// 必选项,开启后必须传递值 ,这个组件才可以使用
require: true,
// props 自定义属性为只读值,不能用户修改
},
// 如果自定义属性名为小驼峰命名
// 建议绑定属性时可改成连字符-命名
cmtCount:{
default: 0,
type: Number,
}
},
data() {
return {
// props 的值转存到 data
count: this.init,
};
},
};

tip

vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值

要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

组件冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

导致组件之间样式冲突的根本原因是:

  • 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
  • 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

style 节点的 scoped 属性

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题

<style leng="less" scoped >
/* less代码 */
</style>
  • 原理:为当前组件所有 DOM 元素分配唯一的自定义属性,写样式时使用属性选择器防止样式冲突问题
  • scoped 只给子组件最外层的 div 添加了自定义属性 [data-v-xxx] ,子组件内部的标签并没有添加。因此父组件只能修改子组件最外层的 div 样式,修改子组件内层元素的样式是不可行的
  • 若想让某些样式对子组件生效,需使用 /deep/ 深度选择器 (常用于修改 第三方 ui 组件库 )
/* 细细品味 */
<style lang="less" scoped>
.title {
/* 不加 /deep/,选择器格式为 .title[data-v-052242de] */
color: blue;
}

/deep/ .title {
/* 加 /deep/,选择器格式为 [data-v-052242de] .title */
color: blue;
}
</style>

组件数据共享

父子间的共享

父向子共享

父组件向子组件共享数据需要使用自定义属性props

父组件

<son :msg="message" :user="userinfo"></son>

<script>
export default{
data(){
return {
// 定义要传递的值
message:'hello vue.js'
userinfo:{name:'张三',age:18}
}
}
}
</script>

子组件

<p>父组件传递过来的 msg 是 :{{msg}}</p>
<p>父组件传递过来的 user 是 :{{user}}</p>

<script>
export default{
// 定义自定义属性
props:['msg','user']
}
</script>

子向父共享

子组件向父组件共享数据使用自定义事件。

子组件

export default{
data(){
return {
// 定义要传递的值
count: 0
}
},
methods:{
add(){
this.count++

// 修改数据时 ,通过 $emit() 触发自定义事件
// 参数1:自定义事件的名字,
// 参数2:要传递的值
this.$emit("numChange",this.count)
}
}
}

父组件

<son @numChange="getCount"></son>
<script>
export default{
data(){
return {
// 定义储存传递过来的值
countFromSon: 0
}
},
methods:{
// 定义触发自定义事件 numChange 的函数
// val 接收 son 自定义事件发送过来的值
getCount(val){
// 转存到countFromSon
this.countFromSon=val
}
}
}
</script>

兄弟间共享

在 vue2.x 中,兄弟组件之间数据共享的方案是EventBus。

EventBus 的使用步骤

  1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
  3. 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件

兄弟组件 A(数据发送方)

<!--定义一个按钮 触发  -->
<button @click="sendMsg">发送数据给 兄弟组件 C</button>
<script>
// 导入 EventBus 模块
import bus from "./eventBus.js";
export default {
data() {
return {
// 共享的数据
msg: "hello vue.js",
};
},
methods: {
sendMsg() {
// 自定义事件
// 参数:事件名称, 要发送的数据
bus.$emit("share", this.msg);
},
},
};
</script>

eventBus.js

import Vue from 'vue'
// 向外共享 Vue 的实例对象
export default new Vue()

兄弟组件 C(数据接收方)

<p>兄弟组件 A 共享的数据 :{{ msgFromLeft }}</p>
<script>
// 导入 EventBus 模块
import bus from "./eventBus.js";
export default {
data() {
return {
// 定义用来转存 发送过来的值 的变量
msgFromLeft: "",
};
},
// 在 created 钩子中注册函数
created() {
// 使用箭头函数,则 this 指向该组件而非 bus
// 参数:事件名称, 事件处理函数
// 触发兄弟组件 A 自定义事件 share
// 函数处理 把传递过来的值转存到 msgFromLeft
bus.$on("share", (val) => {
this.msgFromLeft = val;
});
},
};
</script>

生命周期

概念:每个组件从创建 -> 运行 -> 销毁的一个过程,强调的是一个时间段!

tip 生命周期阶段

组件生命周期函数的分类

tip 官方生命周期图示

vue 官方文档给出的生命周期图示

ref 引用

每个 vue 的组件实例上,都包含一个$refs 对象,里面存储着对应的DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

引用 dom 元素

<!-- 使用 ref 属性,为对应的 dom 元素添加引用的名称  -->
<h3 ref="myH3Ref"> myRef 组件</h3>
<!-- 点击调用getRef -->
<button @click='getRef'>获取 $refs 引用</button>
<script>
export default {
methods:{
getRef(){
// 通过 this.$refs.引用名称
// 可以得到 dom 元素引用
console.log(this.$refs.myH3Ref)
// 操作 dom 元素,把文本颜色改为红色
this.$refs.myH3Ref.style.color='red'
}
}
}
</script>

引用组件实例

<!-- 使用 ref 属性,为对应的组件添加引用的名称  -->
<my-counter ref="counterRef"></my-counter>
<button @click='getRef'>获取 $refs 引用</button>
<script>
export default {
methods:{
getRef(){
// 通过 this.$refs.引用名称
// 可以得到组件实例引用
console.log(this.$refs.counterRef)
// 可以访问获取后的组件数据和方法
this.$refs.counterRef.count = 1
this.$refs.counterRef.add()
}
}
}
</script>

this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行,即在 DOM 更新完成后再执行回调,从而保证 cb 回调可以获取最新的 DOM 元素

methods: {
showInput() {
this.inputVisible = true
// 对输入框的操作推迟到 DOM 更新完成之后
this.$nextTick(() => {
this.$refs.input.focus()
})
}
}

动态插件

vue 提供了一个内置的<component> 组件,专门用来实现动态组件的渲染

<script>
data(){
return {
// 定义要渲染组件的名称
comName:'Left'
}
}
</script>
<!-- 通过 js 动态绑定指定的渲染组件 -->
<component is:"conName" ><component>
<!-- 点击 动态切换组件 -->
<button @click="comName = 'Left'">展示Left组件</button>
<button @click="comName = 'Right'">展示Right组件</button>

keep-alive

在默认情况下,切换组件会将不需要的组件销毁,导致组件的数据清空,切换回来时数据不能还原到切换前

为了保持组件的状态可以使用 vue 内置的 <keep-alive> 组件

<keep-alive>
<component :is="comName"></component>
</keep-alive>

使用 <keep-alive> 组件后,动态切换组件时不需要的组件就会被缓存,切换回来时重新被激活

keep-alive 对应的生命周期函数

  • 组件被激活时,触发组件的 deactived 生命周期函数
  • 组件被销毁时,触发组件的 activated 生命周期函数
export default {
activated(){
console.log('组件被激活了')
}
deactived(){
console.log('组件被销毁了')
}
}

keep-aliveinclude 属性

include 属性用来指定,只有匹配名称的组件才会被缓存,其他销毁

<keep-alive include="Left">
<component :is="comName"></component>
</keep-alive>

keep-aliveexclude 属性

功能与 include 相反,排除不缓存的组件

如果同时使用 include, exclude, 那么 exclude 优先于 include
include 和 exclude 避免混乱只用一个就好

插槽

插槽(slot)是封装组件时,把不确定的,希望由用户指定部分定义为插槽

简单来讲,组件封装期间,为用户预留的内容的占位符。

image-20220821111326204

基础用法

封装组件时,用 <slot> 元素定义插槽,预留占位符

<!-- my-com.vue -->
<template>
<p> 这是 MyCom 组件的第一个 p标签 </p>
<!-- 通过 slot 占位符,为用户预留位置 -->
<slot></slot>
<p> 这是 MyCom 组件的最后的 p标签 </p>
</template>

使用

<my-com>
<!-- 使用 my-com 组件时,为插槽指定具体内容 -->
<p> 这是用户自定义的 p标签 ~~~ </p>
<my-com>

如果在封装组件时没有预留 slot 任何插槽,用户自定义的内容不生效,会被丢弃

后备内容

在封装组件时,可以为预留的 <slot> 插槽的提供后备内容(默认内容)

<slot> 
<p> 这是后备内容的 p标签 ~~~ </p>
</slot>

如果组件使用者没有为插槽提供任何的内容,后备内容就会生效
用户提供内容后,后备内容就会被覆盖掉

具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot> 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。

<!-- my-com.vue -->
<div class="container">
<header>
<!-- 希望页头放这里 -->
<slot name="header"></slot>
</header>
<main>
<!-- 希望主要内容放这里 -->
<slot name="main"></slot>
</main>
<footer>
<!-- 希望主要页脚放这里 -->
<slot name="footer"></slot>
</footer>
</div>

为具名插槽提供内容,在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称

<my-com>
<!-- v-slot 的属性 要和希望放到 slot 插槽name属性名一致 -->
<template v-solt:header>
<h1>头部 </h1>
</template>

<template v-solt:main>
<p> 主要内容 </p>
</template>

<template v-solt:footer>
<p> 页脚 </p>
</template>
</my-com>

注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。

具名插槽的简写形式: (v-slot:) 替换为字符 #,例如 v-slot:header可以被简写为 #header

作用域插槽

在封装组件过程中,为预留的 solt 插槽绑定 props 数据,叫做作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定

<template>
<slot v-for="item in list" :user='item'>
</template>
<script>
export default {
data() {
return {
list: [
{
id: 1,
name: 'Lily',
state: true,
},
{
id: 2,
name: 'Ben',
state: false,
},
{
id: 3,
name: 'Water',
state: true,
},
],
}
},
}
</script>

可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据

<my-com>
<template v-slot:default="scope">
<p>作用域插槽提供的数据: {{scope}}</p>
</template>
</my-com>

接收到的数据 scope 是一个对象。

// scope 的内容
{
'user': {
'id': 1,
'name': 'Lily',
'state': true
}
}

在接收作用域插槽提供的数据时可以使用解构赋值。

<my-com>
<template #default="{user}">
<p>id:{{ user.id }}</p>
<p>name:{{ user.name }}</p>
<p>state:{{ user.state }}</p>
</template>
</my-com>

自定义指令

私有自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

directives: {
color:{
// 为绑定的元素设置红色字体
bind(el){
// el 是绑定此指令的 dom 对象
el.style.color='red'
}
}
}

使用:需要加上 v- 前缀

<!-- 上面声明的自定义指令名为 color -->
<!-- 使用自定义指令名 color,需要加上 v- 前缀 -->
<h1 v-color>app组件</h1>

动态绑值

data:{
return{
color:'red'
}
}
<h1 v-color="color">app组件</h1>

通过 binding 获取指令的参数值

directives: {
color:{
bind(el,binding){
// el 是绑定此指令的 dom 对象
// 通过 binding 对象.value 属性获取动态的参数值
el.style.color=bing.value
}
}
}

update 函数

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用

directives: {
color:{
// 指令第一次绑定元素时调用
bind(el,binding){
// el 是绑定此指令的 dom 对象
// 通过 binding 对象.value 属性获取动态的参数值
el.style.color=bing.value
}
// 每次 dom 更新都调用
update(el,binding){
el.style.color=bing.value
}
}
}

简写

insert 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

directives: {
color(el,binding){
el.style.color=bing.value
}
}

全局自定义指令

全局共享的自定义指令需要通过 Vue.directive()进行声明

// 全局写法
Vue.directive('color', (el, binding) => {
el.style.color = binding.value
}))

注意事项

  • 自定义指令使用时需要添加 v- 前缀
  • 指令名如果是多个单词,要使用 camel-case 短横线命名方式,不要用 camelCase 驼峰命名
  • 自定义指令三个函数里的 this 指向 window
<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
}
}
}

评论



本站使用 Volantis 主题设计

:doodle { @grid: 1x5 / 100vmin; } @place-cell: center; width: @rand(45vmin, 75vmin); height: @rand(45vmin, 75vmin); transform: translate(@rand(-120%, 120%), @rand(-80%, 80%)) scale(@rand(.8, 2.8)) skew(@rand(45deg)); clip-path: polygon( @r(0, 30%) @r(0, 50%), @r(30%, 60%) @r(0%, 30%), @r(60%, 100%) @r(0%, 50%), @r(60%, 100%) @r(50%, 100%), @r(30%, 60%) @r(60%, 100%), @r(0, 30%) @r(60%, 100%) ); background: @pick(#f44336, #9c27b0, #673ab7, #3f51b5, #60569e, #e6437d, #ebbf4d, #00bcd4, #03a9f4, #2196f3, #009688, #5ee463, #f8e645, #ffc107, #ff5722, #43f8bf, #e136eb, #32ed39); opacity: @rand(.5, .9); position: relative; top: @rand(-80%, 80%); left: @rand(0%, 80%); animation: colorChange @rand(6.1s, 26.1s) infinite @rand(-.5s, -2.5s) linear alternate; @keyframes colorChange { 100% { left: 0; top: 0; filter: hue-rotate(360deg); } }