01-v-model语法糖
官网文档:组件使用v-model
-
v-model本质上是 value属性和input事件的一层包装
-
v-model的作用:提供数据的双向绑定
-
数据发生了改变,页面会自动变 v-bind:value
-
页面输入改变 , 数据会自动变化 v-on:input
-
-
v-model是语法糖, v-model等价于 给一个input框提供了 :value属性以及 @input事件
很显然如果每次使用input框,都需要提供value和input事件,比较麻烦,所以使用v-model
-
App.vue
<template> <div> <h1>根组件App.vue</h1> <!-- 1.v-model = "msg" (1)data中的数据变化,表单的值也会变化 :value="msg" (2)表单的值发生变化,data中的数据也会变化 @input="msg=$event.target.value" --> <input type="text" v-model="msg" /> <hr /> <!-- 这种写法与上面写法功能一致 --> <input type="text" :value="msg" @input="msg = $event.target.value" /> <hr /> <!-- 这种写法也与上面写法一致 --> <input type="text" :value="msg" @input="doInput" /> <hr /> </div> </template> <script> export default { data() { return { msg: "" }; }, methods: { doInput(e) { this.msg = e.target.value; } } }; </script> <style> </style>
02-组件使用v-model
-
我们经常遇到一种场景:
-
父组件提供一个数据给子组件使用(父传子)
-
子组件又需要修改父组件传过来的这个数据,所以需要子传父把值传给父组件。
-
-
这种场景可以使用v-model进行简写。
-
定义组件的时候,注意接收的值叫value, 子传父触发的事件叫 input
-
如果父传子的props值叫 value, 且 子传父触发的事件叫 input 。 那么这两个功能就可以使用v-model来简写
03-ref和$refs(vue操作dom)
-
官方文档:API — Vue.js
-
ref作用:在vue中操作dom元素或组件vm实例
-
vue不推荐我们直接操作dom。如果真的要在vue中操作dom,可以使用ref语法
-
说人话 : vue不能直接操作dom,真的要操作也要按vue规定的语法来。(ref语法)
-
-
-
每个 vue 的组件实例上,都包含一个$refs 对象,里面存储着对应的DOM 元素或组件的引用。
ref语法使用流程语法
(1)给标签添加自定义属性red :
<button ref="属性名"></button>
vue会自动把页面所有的ref属性,挂载到vue实例的$ref对象中
(2)通过
vm.$refs.属性名
获取该标签
一定要注意
: vue在mounted勾子中完成页面真实DOM渲染,所以最早能获取dom的就是mounted钩子
ref易错点
1.添加的的时候是:
ref
2.获取的时候是:
$refs
1 给需要获取的 dom 元素或者组件, 添加 ref 属性
<template> <div> <h1>根组件App.vue</h1> <div ref="box">我是div盒子</div> <Goods ref="goods"></Goods> <button @click="fn">点我获取ref</button> </div> </template>
2 通过 this.$refs.xxx
获取, 拿到组件可以调用组件的方法
<script> //导入子组件 import Goods from "./components/Goods.vue"; export default { //注册组件 components: { Goods }, //方法 methods: { fn() { // 如果ref给dom元素添加,获取的就是dom对象 console.log(this.$refs.box);//DOM对象 // 如果ref给组件添加,获取的就是组件vm实例 console.log(this.$refs.goods);//组件vue实例 this.$refs.goods.sayHi(); } } }; </script>
-
子组件代码Goods.vue
<template> <div> <h2>我是子组件</h2> </div> </template> <script> export default { methods: { sayHi(){ console.log('你好我是子组件') } }, } </script> <style> </style>
04-$nextTick使用
需求1: 点击按钮, 切换显示输入框
<template> <div> <!-- 需求1: 点击按钮, 切换显示输入框--> <input type="text" v-if="showInput"> <button @click="fn" v-else>点此搜索</button> </div> </template> <script> export default { data () { return { showInput: false } }, methods: { fn () { this.showInput = true } } } </script> <style> </style>
需求2: 显示输入框的同时, 要获取焦点
当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的.focus() 方法即可。
直接调用会报错, 因为 vue 是 异步dom更新的 (提升渲染效率), this.showInput = true
执行完时, 实际的 dom 还没渲染出来
<input ref="inp" type="text" v-if="showInput"> fn () { this.isShowInput = true this.$refs.inp.focus() }
组件的 $nextTick(callback)
方法,会把 callback 回调推迟到下一个 DOM 更新周期之后执行。
通俗的理解是:等组件的DOM 刷新之后,再执行 callback 回调函数。从而能保证 callback 函数可以操作到最新的 DOM 元素。
<template> <div> <!-- 需求1: 点击按钮, 切换显示输入框 需求2: 显示输入框的同时, 要获取焦点 --> <input type="text" v-if="showInput" ref="inp"> <button @click="fn" v-else>点此搜索</button> </div> </template> <script> export default { data () { return { showInput: false } }, methods: { fn () { // 显示输入框 this.showInput = true // 输入框获取焦点 /* 报错原因: (1) vue更新DOM是一个异步的过程。(虽然设置了showInput为true,但是不会立即显示输入框) (2) 异步代码需要等当前队列同步代码全部执行完毕之后才会执行 解决方案: this.$nextTick(callback) : 会等组件的DOM刷新之后再来执行callback回调函数 */ // this.$refs.inp.focus() // 报错 this.$nextTick( ()=>{ this.$refs.inp.focus() } ) } } } </script> <style> </style>
05-dynamic动态组件
-
什么是动态组件: 让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
-
混淆点解读:动态组件 看起来和v-if v-else功能有些类似,但其实两者是不同的。
-
v-if v-else : 只是根据条件来决定渲染哪一个盒子,不能像组件那样复用。
-
动态组件:通过设置组件名,让一个挂载点可以切换不同的组件
-
-
-
App.vue
<template> <div> <h3>动态组件的演示</h3> <!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 --> <button @click="comName = 'UserAccount'">未登录</button> <button @click="comName = 'UserInfo'">已登录</button> <!-- 动态组件:让多个组件使用同一个挂载点,实现动态切换 <component :is="组件名" ></component> --> <component :is="comName"></component> </div> </template> <script> // 导入组件 import UserAccount from './components/UserAccount.vue' import UserInfo from './components/UserInfo.vue' export default { data () { return { comName: 'UserAccount' } }, //注册组件 components: { UserAccount, UserInfo } } </script>
-
UserAccount.vue
<template> <div class="user-account form-box"> <div class="form-box-item"> <label for="username">账 户: </label> <input id="username" type="text" placeholder="请输入用户名"> </div> <div class="form-box-item"> <label for="password">密 码: </label> <input id="password" type="text" placeholder="请输入密码"> </div> </div> </template> <script> export default { name: 'UserAccount' } </script> <style lang="less"> </style>
-
UserInfo.vue
<template> <div class="user-info form-box"> <div class="form-box-item"> <label for="words">人生格言: </label> <input id="words" type="text" placeholder="请填写人生格言"> </div> <div class="form-box-item"> <label for="info">个人简介: </label> <input id="info" type="text" placeholder="请填写个人简介"> </div> <div class="form-box-item"> <label for="hobby">兴趣爱好: </label> <input id="hobby" type="text" placeholder="请填写兴趣爱好"> </div> </div> </template> <script> export default { name: 'UserInfo' } </script> <style lang="less"> </style>
06-自定义指令
-
官方文档:自定义指令 — Vue.js
-
1.复习指令作用 : 给标签添加额外的功能
-
2.复习指令本质 : 行内自定义属性
-
3.自定义指令作用 : 给标签添加 vue没有的,额外的功能
1.1-自定义指令:局部注册
-
局部注册:只能在当前组件使用
-
需求:
-
1.添加一个自定义指令
v-focus
,作用是让input表单自动聚焦 -
2.添加一个自定义指令
v-color
,作用是设置标签文本颜色
-
<template> <div> <h1>根组件</h1> <!-- 使用指令 v-指令名 --> <input type="text" v-focus/> <br /> <p v-color=" bgc ">我是p标签,我使用了自定义指令v-red</p> </div> </template> <script> export default { data() { return { bgc:'green' } }, //自定义指令都写在这个对象里面 directives: { //1.指令名: focus focus: { // inserted(el) : 当指令被使用的时候会执行一次 inserted(el) { //el : 你的指令写在哪一个标签上,这个el就是标签dom对象 el.focus() }, }, //2.指令名: color color: { inserted(el,binding) { console.log(el)//指令所绑定的元素 console.log(binding)//一个对象,包含指令名、指令值等数据 console.log(binding.value)//指令值 el.style.color = binding.value }, // update(el,binding) : 当指令的值发生改变时触发 update(el,binding) { console.log(el,binding) el.style.color = binding.value }, } } } </script> <style></style>
1.2-自定义指令:全局注册
-
全局注册: 在main.js中注册,任何地方可用
// 全局指令 - 任何组件内"直接"使用 Vue.directive("focus", { inserted(el) { el.focus() // 触发标签的事件方法 } })
07-slot匿名插槽
课前准备
匿名(默认)插槽使用
插槽作用:
父
组件 传递结构
给子
组件官网文档:
插槽使用2个步骤
第一步:在
子
组件中定义一个插槽<slot>默认值:如果父组件没有传递则默认显示</slot>
第二步:在
父
组件中传递结构:<子组件>父组件需要传递的结构</子组件>
-
子组件goods.vue
<template> <div class="son"> <h3>我是子组件</h3> <h4>商品名称</h4> <!-- 插槽:可以让父组件决定这里放什么。 也可以设置默认值 --> <slot>我是默认值</slot> </div> </template> <script> export default { name: "goods", data() { return {} } } </script> <style scoped> .son { border: 1px solid red; } </style>
-
父组件App.vue
<template> <div id="app"> <h1>我是父组件</h1> <!-- 子组件1:插入购买链接 --> <goods> <button> <a href="http://www.jd.com">点击购买</a> </button> </goods> <!-- 子组件2:插入禁用点击的按钮 --> <goods> <button disabled>已卖完</button> </goods> <!-- 子组件3:没有插入内容,则显示默认插槽 --> <goods></goods> </div> </template> <script> //导入局部组件 import goods from "./components/goods.vue" export default { data() { return {} }, components: { goods } } </script> <style> #app { border: 1px solid #000; } </style>
知识点验收
注意
-
插槽的作用是什么让
父组件
传递什么到子组件
中?html结构
-
插槽的
默认值
写哪里?<solt>默认值</slot>
08-slot具名插槽
-
官方文档:具名插槽
-
1.插槽作用: 父组件
传递html结构
给子组件-
给所有slot分发相同内容
-
-
2.具名插槽作用: 父组件
传递多个html结构
给子组件-
给不同slot分发不同内容
-
具名插槽语法如下
1.给子组件的<slot>添加name属性 :
name="插槽名"
2.父组件使用
v-slot:插槽名
: 给指定的插槽传递结构
注意:这个v-slot指令必须要写在
<template>
标签中,否则会报错
<template>
是HTML5新增的一个语义化标签,模板的意思。 这个标签本身不会被渲染,因此最终在页面是看不见的。 这个标签类似于div,就是一个空盒子容器。 与div唯一的区别就是它不会渲染。
vue中并不是所有的指令都能简写,有简写符号的指令主要有三个
v-on 指令 可以简写成
@
v-bind指令 可以简写成
:
v-slot指令 可以简写成
#
-
课外思考? : 为什么具名插槽v-slot一定要写在<templaye>标签中呢
-
cell.vue
-
<template> <div class="cell"> <div class="title" > <slot name="a">我是标题</slot> </div> <div class="content" > <slot name="b">我是内容</slot> </div> <div class="right" > <slot name="c">我是图标</slot> </div> </div> </template> <script> export default { } </script> <style> .cell{ border: 1px solid #f00; height: 60px; padding: 10px; position: relative; } .title{ float: left; } .content{ position: absolute; bottom: 10px; left: 10px; } .right{ float: right; } </style>
-
App.vue
-
<template> <div id="app"> <h1>我是父组件</h1> <cell> <template v-slot:a> <strong>我是h3</strong> <span>标题内容</span> </template> <template v-slot:b> <a href="#">我是链接</a> </template> <template #c> <button>我是按钮</button> </template> </cell> </div> </template> <script> //导入局部组件 import cell from "./components/cell.vue" export default { data() { return {} }, components: { cell } } </script> <style> #app { border: 1px solid #000; } </style>
09-v-slot作用域插槽(难点)
-
官网文档:作用域插槽
1.插槽与props的异同点
相同点: 都是父传子
不同点:
props: 传递的是数据
插槽:传递的是html结构
2.作用域插槽和$emit异同点
相同点:都是子传父
不同点:
$emit : 子传父的数据通过事件来接收
作用域插槽:子传父的数据是通过插槽v-slot接收 (子传父的数据,只能给插槽用)
-
1.插槽作用:父组件 传递
html结构
给 子组件 -
2.具名插槽作用:父组件 传递
多个html结构
给 子组件 -
3.作用域插槽作用:父组件 给 子组件 传递插槽 时,可以使用子组件内部的数据
作用域插槽语法如下
1.给子组件的<slot>添加一个自定义属性 :
<slot :属性名="属性值" ></slot>
2.给父组件的<template>添加v-slot属性接收数据:
<template v-slot="对象名"></template>
父组件使用子组件内部数据语法:
对象名.属性名
注意点
: 不要把具名插槽语法
和作用域插槽
语法搞混淆具名插槽:
<template v-slot:name值></slot>
作用域插槽:
<template v-slot="对象名"></slot>
-
App.vue
<template> <div> <!-- 1.匿名插槽 : 父组件传递 一个html结构 给子组件 (1)子组件: <slot> 插槽默认内容 </slot> (2)父组件: <子组件> html结构 </子组件> 2.具名插槽 : 父组件传递 多个html结构 给子组件 (1)子组件: 给插槽添加一个name(插槽名) <slot name="插槽名"> 插槽默认内容 </slot> (2)父组件: 使用 v-slot:插槽名 或 #插槽名 <子组件> <template v-slot:插槽名> html结构 </template> </子组件> 3.作用域插槽: 子组件传递 数据 给父组件插槽 (1)子组件 : 给<slot>内置组件添加自定义属性 <slot 属性名="属性值" > 插槽默认内容 </slot> (2)父组件 : 使用 v-slot="对象名" --> <scope> <template v-slot="obj"> <p>{{ obj.a }}</p> <p>{{ obj.b }}</p> <p>{{ obj.c }}</p> </template> </scope> </div> </template> <script> //导入局部组件 import scope from './components/scope.vue' export default { //注册组件 components: { scope }, data() { return { } }, }; </script> <style> #app { border: 1px solid #000; } </style>
-
scope.vue
<template> <div class="box"> <h2>学习作用域插槽</h2> <input v-model="msg" type="text" placeholder="输入搜索内容"> <br> <!-- 定义插槽 --> <slot a="1" b="2" :c="msg"> 插槽默认内容 </slot> </div> </template> <script> export default { data() { return { msg:'' } }, } </script> <style scoped> .box{ border: 1px solid #000; } </style>
10-作用域插槽应用场景
-
作用域插槽实际开发应用:Element - The world's most popular Vue UI framework
-
需求:
-
页面需要展示两个功能不同的表格
-
第一个表格:显示用户头像
-
第二个表格:显示删除按钮
-
-
-
给大家捋一下这个按钮完整流程是咋样的。(因为作用域插槽涉及到两个知识点:组件传值 + 插槽)
-
作用域插槽类似于 子传父,不同的是: 作用域插槽子组件传递数据,不是传给父组件的。而是传给父组件那个插槽的。(相当于只有插槽才能用子组件传递过来的数据)
-
(1) App.vue 有一个list数组
-
(2) 通过props父传子, 把App.vue的list传递给 student.vue的arr
-
(3) student.vue 渲染arr,把数组的数据渲染到页面表格中
-
(4)现在遇到问题了 : 有时候,student.vue组件希望表格的td能显示学生的头像。 有时候,student.vue组件希望表格的td能显示操作按钮 (
同一个组件呈现不同的功能
) -
(5)怎么解决这个问题呢? : 使用插槽来解决
-
父组件App.vue 通过插槽把需要展示的结构 传递给 子组件student.vue
-
-
(6) 又遇到问题了。 图片的话,需要路径怎么办? 按钮的话,点击需要获取id怎么办?
-
因为这个v-for指令是在子组件里面用到的, 这个obj也是子组件内部的数据。 父组件App.vue肯定是拿不到的。(父组件只是把数组子组件了,至于子组件内部怎么处理,App.vue是不知道的)
-
-
(7)解决方案: 子组件在渲染td的时候,通过
作用域插槽
,把当前渲染的obj传递给父组件。 这样父组件在传递html结构的时候,就可以把obj里面需要用到的数据也一并放在html结构中,传递给子组件了。
-
-
作用域插槽本质: 子组件把自己当前数据 传递给 父组件插槽 。
-
只能父组件插槽内部使用,而不是任何地方都能用。
作用域一词因此而来
-
-
App.vue
-
<template> <!-- 1.父传子应用: 让组件在复用的时候, 加载不同的数据 2.插槽应用: 让组件在复用的时候,加载不同的结构样式 --> <div id="app"> <h1>我是父组件</h1> <h2>显示头像</h2> <!-- 子组件 --> <student :list="list1"> <template v-slot:head> <b style="color:red">头像</b> </template> <!-- 具名插槽 和 作用域插槽并存 (1)具名插槽语法: v-slot:插槽名 (2)作用域插槽 : v-slot="对象名" 问题: 两种语法都用到 v-slot,而一个标签只能写一个 解决: v-slot:插槽名="对象名" #插槽名="对象名" --> <template v-slot:body="scope"> <img :src="scope.row.headImgUrl" alt=""> </template> </student> <h2>显示操作按钮</h2> <!-- 子组件 --> <student :list="list2"> <template v-slot:head> <i style="color:green">操作</i> </template> <template #body="scope"> <button @click="list2.splice(scope.$index,1)">删除</button> </template> </student> </div> </template> <script> //导入局部组件 import student from './components/student.vue' export default { //注册组件 components: { student }, data() { return { list1: [ { id: "13575", name: "小传同学", age: 18, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210303/603f2d2153241.jpg" }, { id: "62408", name: "小黑同学", age: 25, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210304/6040b101a18ef.jpg" }, { id: "73969", name: "智慧同学", age: 21, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210302/603e0142e535f.jpg" } ], list2: [ { id: "13575", name: "传同学", age: 8, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210303/603f2d2153241.jpg" }, { id: "62408", name: "黑同学", age: 5, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210304/6040b101a18ef.jpg" }, { id: "73969", name: "慧同学", age: 1, headImgUrl: "http://yun.itheima.com/Upload/./Images/20210302/603e0142e535f.jpg" } ] } }, } </script> <style> #app { border: 1px solid #000; } </style>
-
student.vue
<template> <div> <table border="1"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th> <slot name="head">表头</slot> </th> </tr> </thead> <tbody> <tr v-for="(item,index) in list" :key="item.id" > <td>{{ index+1 }}</td> <td>{{ item.name }}</td> <td>{{ item.age }}</td> <td > <slot :row="item" :$index="index" name="body">内容</slot> </td> </tr> </tbody> </table> </div> </template> <script> export default { //父传子 props:{ list:Array }, data() { return {} } } </script> <style scoped> table{ margin-top: 20px; } td { height: 60px; } img{ height: 90%; } </style>