Vue基础-day03
今天是Vue基础的第3天
复习前天内容
-
v-bind绑定类名时传数组
v-bind:class="[c1, c2, c3]"- c1,c2,c3分别是变量,变量值是什么就代表它有什么类
v-bind:class="[c1,c2,'xx']"- c1,c2分别是变量,变量值是什么就代表它有什么类,并且有一个固定的xx类
v-bind:class=[c1, {'类名': 布尔值}]- c1是变量,它是值就有什么类,当布尔值为true就有这个类名
-
侦听器
-
简约写法
watch: { 要侦听的数据 (修改后,修改前) { } }- 细节:数组那些能改变自身的方法:push、pop、unshift、shift、splice、sort、reverse这些方法可以侦听到,但是concat、通过下标改值,侦听不到
- 对象的属性改变也侦听不到
-
完整写法
- 主要是为了解决侦听对象内任意属性改变都要侦听到(深度侦听)
watch: { 对象: { // 开启深度侦听 deep: true, handler () { // 当有改变调用的函数 }, // 当前页面一打开就默认调用handler immediate: true } }
-
-
组件化开发
- 以后可以把项目中一个大、小界面都称之为组件
- 因为属于组成网页的一部分所以就称之为组件
- 既然组件本质是界面,所以一定有
- html结构
- css样式
- js逻辑
- 所以我们以后写vue项目,用
.vue文件作为组件
- 以后可以把项目中一个大、小界面都称之为组件
-
vue-cli
-
俗称脚手架
-
相当于就是类似webpack的打包工具,但是不需要做太多复杂的配置,开箱即用
-
脚手架只是一个全局包,所以只要装一次
npm i -g @vue/cli -
如何创建项目
vue create 项目名 -
默认入口文件是?
- src/main.js
- 所以全局东西要写到
main.js里
-
项目启动的第一个组件是?
- App.vue
- 也称之为主组件
-
components文件夹
- 放组件的
-
assets文件夹
- 放静态资源:图片、字体
-
脚手架的配置文件叫?
- vue.config.js
-
-
ESLint
-
是一种语法规范的检查工具,能让不符合规范的代码报错
-
学习时可以先关掉
-
在
vue.config.js加一条配置lintOnSave: false
-
-
-
我们以后除了App.vue会自己再写组件,但默认写好的组件看不到
-
要想看到要导入并注册再使用
-
局部
// 导入 import 组件名 from '组件路径'// 注册 export default { components: { 组件名 // 完整写法是 使用的名字: 组件名 } }<组件名 />- 如果组件名是驼峰的,可以拆成小写,每个单词用-连接
<单词1-单词2 />- 在哪导入就只能在哪用
-
全局
- 导入一次,哪里都能用
// 导入 import 组件名 from '组件路径' // 注册 Vue.component('使用的名字', 刚刚导入的组件名) <使用的名字 />
-
-
组件名规范:
- 开发建议大驼峰,至少两个单词
- 使用标签时,不要用大驼峰,而是用小写带
-
- 使用标签时,不要用大驼峰,而是用小写带
- 例:
- 组件名叫 DemoLogin
- 但是使用时建议用
<demo-login />
- 开发建议大驼峰,至少两个单词
-
组件关系
- 父子关系:
- 包含与被包含的关系
- 包含别人的叫父组件,被包含的叫子
- 兄弟关系:
- 平级关系
- 父子关系:
作业
昨天的作业不需要逻辑,只需要使用提供好的组件拼接成界面即可
-
首先建立项目
vue create 项目名 -
先关掉
ESLint,来到vue.config.js加一条规则lintOnSave: false -
App.vue代码如下---原生实现
<div id="app"> <BaseBox style="width: 480px"> <!-- 标题部分 --> <div class="title"> <h2>记事本</h2> <input type="text" /> </div> <ul> <li> <div class="content"><span>1.</span> 西瓜</div> <button>❌</button> </li> </ul> <!-- 底部 --> <div class="bottom"> <span>合计:0</span> <button>🗑️</button> </div> </BaseBox> </div> -
App.vue代码如下 --- 改成组件实现
- 就是把原生标签换成组件
<div id="app"> <BaseBox style="width: 480px"> <!-- 标题部分 --> <base-title></base-title> <!-- 输入框 --> <base-input /> <ul> <li> <div class="content"><span>1.</span> 西瓜</div> <base-button>❌</base-button> </li> </ul> <!-- 底部 --> <div class="bottom"> <span>合计:0</span> <base-button>🗑️</base-button> </div> </BaseBox> </div>
讲插槽之前的总结
-
我们发现给组件写内容,默认不会改变组件显示的内容
- 例如
<base-title>囧事本</base-title> -
我们发现给组件加原生dom有的那些事件,默认也无法添加成功
<base-button @click="del">❌</base-button> -
原因:组件本质上并不是原生dom,但是它可以实现上面的2个功能,这就需要需要今天的知识:插槽(slot)、$emit
slot-基本使用
-
组件什么时候用插槽?
- 我封装了一个组件,但是这个组件有个区域的内容我不想写死,那就可以写
插槽(slot)
- 我封装了一个组件,但是这个组件有个区域的内容我不想写死,那就可以写
-
语法
-
在不想写死的地方,写一个
slot<slot />- 这种写法代表没有默认值,外界不传,这里就没有
-
有默认值的写法
<slot>默认值</slot>
-
-
外界传递的语法
<组件>传递的内容</组件>
BaseTitle组件
结合刚刚学习插槽部分的知识点,咱们来完成昨天作业中的的
BaseTitle标题组件
-
最后改良这个组件,因为这个组件之前写死了,名字只能叫记事本
-
为了不写死,所以使用插槽
<h2 class="base-title"> <!-- 挖了一个坑(专业叫法:插槽) --> <!-- 如果光这么写,代表没有默认值 --> <!-- 就意味着外面没传,这里没有显示 --> <!-- <slot /> --> <!-- 你传什么,这里就显示什么 --> <!-- 但是如果你没传,就显示记事本 --> <slot>记事本</slot> </h2> </template>
slot-具名插槽
需求:BaseBox组件我有两个地方不想写死,怎么办?
-
什么叫具名插槽
- 就是具有名字的插槽
-
为什么要用具名插槽?
- 因为我一个组件里可能多个地方都不想写死,那就使用具名插槽
-
语法
<slot name="名字">默认值</slot> -
如何给具名插槽传递?要依赖
template包裹,并且用v-slot:来指定插槽名字<template v-slot:插槽名> 要传递的内容 </template> -
简写就是把
v-slot:变成#<template #插槽名> 要传递的内容 </template> -
以上语法是vue2.6以后推出的语法,也是官方推荐语法
-
在vue2.6以前,有其他语法,但是目前已经废弃了(Vue3.0里已经明确不能用了)
-
vue2.6以前废弃语法(只要求会认,不要求以后拿来写)
<template slot="插槽名字"> 要传递的内容 </template> <!-- 如果只有一个要传,也可以直接写标签 --> <标签 slot="插槽名字">内容</标签> -
默认插槽其实也有名字,只不过名字叫
default -
小结
- 如果是新语法,不管传一个还是传多个,都要用
template包裹 - 新语法:
- 用
v-slot:插槽名字指定插槽,简写为:#插槽名字
- 用
- 旧语法
- 用
slot="插槽名字"指定插槽
- 用
- 如果是新语法,不管传一个还是传多个,都要用
BaseBox组件
接下来咱们基于刚刚学习的具名插槽来完成
BaseBox组件
-
BaseBox需要标题部分不写死,又希望
main这一部分也不写死,所以有两个插槽,为了区分,所以要用具名插槽<div class="base-box"> <div class="top"> <!-- 这里有一个具名插槽 --> <slot name="title">标题</slot> </div> <div class="main"> <!-- 这里有一个默认插槽 --> <slot /> </div> </div> -
传递部分
<BaseBox style="width: 480px"> <!-- 传递给具名插槽 --> <template #title> <!-- 标题部分 --> <base-title>黑马本</base-title> <!-- 输入框 --> <base-input /> </template> <!-- 即使是默认插槽,最好也包一个template --> <template> <ul> <li> <div class="content"><span>1.</span> 西瓜</div> <base-button @click="del">❌</base-button> </li> </ul> <!-- 底部 --> <div class="bottom"> <span>合计:0</span> <base-button>🗑️</base-button> </div> </template> </BaseBox>
props - 基本使用
通过props定义外部可以传递进来的数据
子组件模板
<template>
<div class="props-container">
<h1>我叫:jack</h1>
<h2>我今年:18岁</h2>
<h3>我是:男孩子</h3>
<h4>我喜欢的食物是:</h4>
<ul>
<li>西兰花</li>
<li>花菜</li>
<li>西红柿</li>
</ul>
</div>
</template>
<script>
export default {
name:'PropsCom'
}
</script>
<style >
.props-container {
border: 1px solid orange;
width: 300px;
background-color: yellowgreen;
padding: 10px;
}
h1,
h2,
h3,
h4 {
margin: 0;
}
</style>
-
props是用来实现:
父传子 -
步骤:
-
子里使用
props来声明需要传递哪些数据- props跟data这些平级
- props里声明的数据可以像
data中的数据一样使用
props: ['数据名1', '数据名2', '数据名3'] // 例 props: ['name', 'age', 'sex', 'foods'], -
父传递的语法
<子组件 :数据名="数据"/> <!-- 例子 --> <!-- 学习父子通信的组件 --> <!-- 父给子传写 :参数名="数据" --> <!-- :name="obj.name" 把父组件里的obj里的name,传递给子组件里的name --> <my-son :name="obj.name" :age="obj.age" :sex="'男'" :foods="['话梅','酸梅','溜溜梅','青梅']"/>
-
props-使用进阶
通过数组的方式定义prop,基本功能上并没有问题,如果要设置类型限制,设置默认值。。可搞不定咯,所以开发中更为推荐的是如下的写法
-
官方不推荐,props传数组,推荐传对象
-
以下是官方对于对象的说明
props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } -
小结:
- 用什么属性限制要传递的类型?
type
- 用什么属性限制
必传required给true
- 用什么设置默认值?
default,基本类型直接给,复杂类型要用函数来返回
- 用什么设置自定义校验规则
- validator
- 它是一个函数,函数有一个参数,参数就是传递过来的值
- 如果函数里return true代表校验通过,为false校验失败
- validator
- 用什么属性限制要传递的类型?
props-单向数据流
父组件中数据的改变会传递给子组件,但是反过来却不行
-
概念
- 父组件的数据改变了会自动流入到子组件,但是子
不允许修改由props流过来的数据
- 父组件的数据改变了会自动流入到子组件,但是子
-
如图
-
所谓单向数据流不允许子修改,是不允许改
栈,但是可以改堆-
例如:父传了数组给子,在子里面可以通过调用push、pop、shift、unshift、splice等等方法来改堆,而且改完后父也会跟着改,因为他们指向的是同一个堆
-
但是在子里不允许改
props的栈上的数据,父可以改,而且改完后能流入到子
-
-
小结
- 父的一个数据传递给了子,在父里如果把这个数据改了,请问会影响子吗?
- 会
- 子里是否允许修改props里流入的数据
- 不允许
- 子里能否使用push方法让父的数组多一个元素
- 可以
- 子里能否把父传递过来的对象的属性值给改了?
- 可以
- 父的一个数据传递给了子,在父里如果把这个数据改了,请问会影响子吗?
emit-基本使用
如何在子组件中给父组件传递信息呢?可以通过$emit来实现哦
-
子里要如何给父传递数据?
-
利用通知的形式来间接传数据
-
发通知的语法
this.$emit('自定义的事件名', 数据) -
父里要订阅这个通知(相当于就是监听这个事件)
<子组件 @自定义的事件名="函数"/> -
当子的
$emit执行时,就会自动调用父里绑定的函数,函数的参数就是子传递过来的数据
-
-
例
-
子里的代码
<button @click="$emit('youya', '永不过时')">点击后-要给父传递数据</button>- 解释:当子里面点了这个按钮,就会发一个叫
youya的事件,那么父里如果监听了youya就会调用
- 解释:当子里面点了这个按钮,就会发一个叫
-
父里的代码
<子组件 @youya="函数"/>- 当youya通知时,就会自动调用上面的函数,函数的参数就是子里传递过来的
永不过时
- 当youya通知时,就会自动调用上面的函数,函数的参数就是子里传递过来的
-
-
小结:
- $emit相当于通知一个事件,父里要不要监听才能触发
- 要
- @事件名
- $emit一定要传值吗?
- 不一定要传
- 如果要传值,父里如何接这个值?
- 绑定的函数的参数,就是这个值
- $emit相当于通知一个事件,父里要不要监听才能触发
BaseButton组件
接下来咱们来完成
BaseButton组件
-
父里默认情况下对着封装的组件的,加
click无效<base-button @click="del">❌</base-button>-
因为这并不是原生的标签,而是一个组件,而组件希望能够被
@事件名的语法触发一些事件 -
就必须要在内部用
$emit('事件名')写这个事件名才能触发
-
-
所以,来到
BaseButton这个组件内部,给按钮加一个点击事件,点击事件就通知父组件的事件触发<button @click="$emit('click')" class="base-button" type="button">
emit结合props
接下来咱们把
props和emit结合起来使用
- 因为子里通过
$emit可以给父传递数据,那么当父的数据一旦发生改变,通过props又能流入到子
- 小结
- 子不能直接修改父的props数据
- 但是子可以通过
$emit通知父组件,让父组件改掉props的值,就相当于改了
BaseInputNum组件
咱们通过计数器案例,来巩固
props和emit的应用
-
把第一天写的
计数器抽取到一个.vue文件,这个.vue文件名叫BaseInputNum -
最终代码如下
<template> <div class="my-input-number"> <!-- 递减 --> <span @click="sub" role="button" class="my-input-number__decrease" :class="{ 'is-disabled': num == 0 }" > - </span> <!-- 累加 --> <span @click="add" role="button" class="my-input-number__increase" :class="{ 'is-disabled': num == 10 }" > + </span> <div class="my-input"> <!-- 数字显示区域 --> <span class="my-input__inner">{{ num }}</span> </div> </div> </template> <script> export default { data() { return { num: 0, }; }, methods: { // +号的点击事件 add() { // 小于10才允许++ if (this.num < 10) { this.num++; } }, // -号的点击事件 sub() { // 大于0才允许-- if (this.num > 0) { this.num--; } }, }, }; </script> <style> .my-input-number { position: relative; display: inline-block; width: 180px; line-height: 38px; } .my-input-number span { -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; } .my-input-number .my-input { display: block; position: relative; font-size: 14px; width: 100%; } .my-input-number .my-input__inner { -webkit-appearance: none; background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #dcdfe6; box-sizing: border-box; color: #606266; display: inline-block; font-size: inherit; height: 40px; line-height: 40px; outline: none; padding: 0 15px; transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); width: 100%; padding-left: 50px; padding-right: 50px; text-align: center; } .my-input-number .my-input-number__decrease, .my-input-number .my-input-number__increase { position: absolute; z-index: 1; top: 1px; width: 40px; height: auto; text-align: center; background: #f5f7fa; color: #606266; cursor: pointer; font-size: 13px; } .my-input-number .my-input-number__decrease { left: 1px; border-radius: 4px 0 0 4px; border-right: 1px solid #dcdfe6; } .my-input-number .my-input-number__increase { right: 1px; border-radius: 0 4px 4px 0; border-left: 1px solid #dcdfe6; } .my-input-number .my-input-number__decrease.is-disabled, .my-input-number .my-input-number__increase.is-disabled { color: #c0c4cc; cursor: not-allowed; } </style> -
步骤:
-
为了让父里的数据改变后能影响到子,所以子里的
num,不应该是自己独立的数据(声明在data中数据),而应该是由props传递过来的,所以先把data中的num删了,再写一个propsprops: { num: { type: Number, required: true } } -
但此时点+和点- 都会报错,因为之前写的都是
this.num++或者this.num--,这样就破坏了单向数据流,为了不破坏单向数据流,该通知父组件自己来改值// +号的点击事件 add() { // 小于10才允许++ if (this.num < 10) { // 下面这句话没用了,此时num是props传过来的数据 // 如果直接改了会破坏单向数据流 // this.num++; // 通知父组件去+和- this.$emit('changeNum', this.num + 1) } }, // -号的点击事件 sub() { // 大于0才允许-- if (this.num > 0) { // this.num--; this.$emit('changeNum', this.num - 1) } }, -
父里接收此通知,并拿到参数赋值给父自己的num,那么父的num变了,父的num变了后,自动流入到子,子的界面也会跟着变
<base-input-num :num="num" @changeNum="changeNum"/>changeNum (val) { this.num = val }
-
-
图解
$event关键字
如果要把简短的逻辑提取到行内,需要认识一个关键字
$event
- 在之前
原生标签时,$event代表事件对象 - 现在,对于组件而言,
$event代表这个子组件传递过来的值
BaseInputNum使用调优
结合上一步的
$event关键字,提取父组件使用的逻辑到行内
<base-input-num :num="num" @changeNum="num = $event"/>
组件上使用v-model(数据流转图)
表单元素上可以通过
v-model便捷的实现双向数据绑定,组件上也可以哦
-
v-model用在表单元素
-
但实际上v-model也可以用在组件,要满足一定条件就能用
-
因为:
-
v-model的本质只是一个
语法糖 -
它相当于帮你生成父传子的代码,以及帮你生成子传父的代码
-
只不过父传子时,props默认只生成value属性的, 子传父事件名只生成input
-
所以如果你的代码如下
<组件 v-model="xx" ></组件>- 它会生成
<组件 :value="xx" @input="xx = $event"></组件> -
所以如果希望,用组件时能用v-model,那要保证子组件里的props要写成value,子传父的事件名要写成input
-
BaseInputNum组件 - 实现v-model
接下来咱们来实现
BaseInput组件
-
把props里的
num改名为value,并且把用到它的地方都改了 -
把子传父时的
$emit,事件名要从changeNum改成input -
最后父组件里可以用
v-model了<base-input-num v-model="num"/> -
最终它相当于生成了
<base-input-num :value="num" @input="num = $event"/>
Vue-CLI中启用less
使用css预处理器可以更好的编写样式,Vue-CLI中默认并没有启用,咱们今天首先把这个弄了
-
脚手架创建项目时如果选的是默认配置,是不支持less的,要支持less还得下载less以及less-loader
npm i less less-loader -
如何在某个组件的代码中使用less语法?
<style lang="less"> /* 这个样式使用less的语法 */ </style> -
或者直接写一个
.less的文件,在main.js里导入 -
浏览器不认识,但是我们的脚手架打包时会自动把它翻译成css
-
所以以前的vscode插件:
easy less禁用或者卸载
style中的样式是全局样式
组件style标签中的样式是全局样式哦
-
不管是在哪个组件里写的style,但凡这个组件导入了,这个组件里的样式都是全局的
-
所以啊,很容易造成样式冲突
Scoped CSS
除了上一节分析的解决方案意外,在
Vue中还可以通过Scoped CSS来解决哦,直译过来,就是范围样式
-
解决办法就是:
- 给组件的
style里写一个scoped - 那就代表这个
style里写的样式,只能给当前组件使用,就不冲突了
- 给组件的
-
加了scoped后的原理
- 会让当前组件的所有标签,具备一个行内属性:
data-v-hash值 - 而且会把你写的样式变成
属性选择器,那就代表既要满足之前的选择器又要它满足行内有data-v-hash值的属性,所以这样子就保证了只会让当前组件生效
- 会让当前组件的所有标签,具备一个行内属性:
总结 和 作业
今天的重点内容,和作业
总结:
-
slot的作用?- 可以让组件内某个html结构不写死
-
具名插槽
<slot name="top"/>如何传递内容?-
推荐语法
<template v-slot:top></template>,或者<template #top></template> -
废弃语法
<template slot="top"></template>
-
-
props推荐的写法是什么?- 对象
-
哪个单词是必传?
- required
-
哪个单词是限制类型?
- type
-
哪个单词是默认值?
- default
- 基本类型直接写值
- 复杂类型要写一个函数,在函数里return
- default
-
哪个单词是校验?
- validate
- return true代表通过
- return false代表校验不通过
- validate
-
单向数据流:父组件中数据的改变会传递给子组件,但是反过来却不行,不能修改是指不能修改栈,堆可以
-
<SonCom @numChange="func">,需要SonCom组件内部实现什么?- $emit('numChange')
-
$event用在内联语句时: -
dom元素:事件对象
-
组件: 传递过来的值
-
<SonCom v-model="msg">中的v-model等同于- :value="msg" @input="msg = $event"
-
Vue-CLI中编写less需要:
- 安装:
npm install -D less-loader less <style ></style>写成什么样
- 安装:
-
style标签中的样式默认是什么样式?- 全局
-
[data-v-哈希值]需要给style添加什么属性?- scoped
作业:
-
基于提供的模板完成
BaseSwitch组件- 需求:
<BaseSwitch v-model="isChecked" /> <script> import BaseSwitch from './components/BaseSwitch.vue' export default { name: 'App', data(){ return { isChecked:true } }, components: { BaseSwitch, }, } </script> -
基于提供的模板完成
BaseCheckBox组件-
需求:
<BaseCheckBox v-model="isChecked" /> <BaseCheckBox v-model="isChecked" >文本</BaseCheckBox> <script> import BaseCheckBox from './components/BaseCheckBox.vue' export default { name: 'App', data(){ return { isChecked:true } }, components: { BaseCheckBox, }, } </script>
-
-
说明:
- 模板中的默认逻辑是为了保证组件目前的切换效果
- 可以根据需求进行删减
今日单词
| 单词 | 解释 | 说明 |
|---|---|---|
| slot | 插槽 | |
| v-slot | 插槽 | <组件 v-slot:插槽名> </组件>简写为 <组件 #插槽名> </组件> |
| props | 属性 | 在子里写props,props推荐对象形式,type限制传递类型;不推荐数组形式。 |
| required | 写了required,又写default,还是需要自己传参。 | |
| default | props对象中default写默认值。写了default,不需要再写default。 | |
| validator | 数据校验 | props里可使用validator验证数据 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } |
| $emit | 子里传值$emit('自定义的事件名', 传递的数据);父里监听<子组件 @自定义的事件名=”函数“ /> | |
| $event | $event在原生标签代表 事件对象;在组件代表子组件传递过来的值 | |
| v-model | 双向绑定 | 如果希望组件时能用v-model,那要保证子组件里的props要声明成value,子传父的事件名要写成input;<组件 v-model="xx" ></组件>会生成<组件 :value="xx" @input="xx = $event"></组件> |
| less-loader | 脚手架使用less,安装命令npm i less less-loader;安装后,把vscode插件easy less禁用或卸载 | |
| scoped | 域内的 | 解决组件内的样式是全局样式的问题。<style scoped></style>。作用把样式变成选择器[data-v-hash]。可搭配深度作用选择器::v-deep /deep/ >>>使用 |