Vue框架 ---- 组件高级:ref、动态组件,插槽slot和自定义命令
作者:互联网
Vue框架
内容管理
Vue3组件基础:ref引用、动态组件、插槽、自定义指令
前面简单介绍了组件的相关,比如监听,组件关系和组件的数据共享,还有自定义事件,这样一个界面就可以为不同的组件相互作用结合形成,数据传递则依靠指令和之间【后代可以使用provide和inject便捷传输】 复杂的数据传递一般使用vuex
其实很多得概念之前就提到了,比如这里得ref引用;【这个在Spring的IOC的属性注入的位置,对象属性就是ref,普通的属性就是value】,之前的响应式数据传递,就配置全局数据项,就可以不写.value
ref引用
ref引用就是对象的引用,ref是帮助programmer在不依赖JQuery的情况下,获取DOM元素或者组件的引用【之前Jq最方便的就是简化了对象的操作,特别是$(选择器:过滤器)】
Vue里面是不建议使用Jq的 ,因为容易造成引用的混乱,所以就要使用ref来操作DOM或者组件。每一个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或者对于组件的引用,默认情况下,其默认指向一个空对象
之前分享组件的基础的时候,就提过this代表的就是当前组件的实例,这里可以看一下其内容
[[Target]]: Object
num: (...)
username: (...)
$: (...)
$attrs: (...)
$data: (...)
$el: (...)
$emit: (...)
$forceUpdate: (...)
$nextTick: (...)
$options: (...)
$parent: (...)
$props: (...)
$refs: Object
$root: (...)
$slots: (...)
$watch: (...)
_: Object
可以看到有很多的内置的对象,这里就有一个$refs
使用ref引用DOM元素
如果想要对页面上的某个DOM元素进行操作,就给需要操作的DOM结点添加ref属性
- 为对应的DOM添加ref属性,指明引用的名称
<h1 ref="myh1">LifeCircle子组件</h1>
-
通过
this.$refs.引用名称
,获取到dom元素的引用 -
通过该引用操作DOM元素
<button type="button" @click="getDom">获取DOM结点h1</button>
methods:{
getDom() {
console.log(this.$refs.myh1) //因为会将带有ref属性的结点注册到$refs中,所以这里想要使用myh1,就直接.引用即可
}
}
这里打印的结果就是: < h1>LifeCircle子组件< /h1>;成功获取到了该组件对应页面上的DOM结点,比如这里想要修改文本的颜色: this.$refs.myh1.style.color = 'red’就可以设置红色
使用ref引用组件
如果想要引用页面上的组件【组件对于父组件来说也是一个标签,和上面的DOM一样是一个结点】
- 为要引用的组件加上ref引用名称
- 使用this.$refs.引用名称就可以获取到组件的实例对象,通过.methods就可以直接使用组件的methods结点中定义的方法
这里比如在consum-kid组件中定义了方法setColor,可以获取组件页面中的dom元素修改其背景颜色,在App中点击按钮达到效果
//consum-kid.vue
X * 2 + 1的结果为 :<span ref='myspan' :style="{'background-color':color}">{{count * 2 + 1}}</span>
methods:{
setColor() { //在子组件中定义了一个set方法,在另一个组件中点击按钮就可以执行该方法,那么就要使用组件的引用
this.$refs.myspan.style.backgroundColor = 'pink'
}
}
//父组件App.vue
<button type="button" @click="changeColor">改变子组件的字体的颜色</button>
<cosum-kid ref="conKid"></cosum-kid>
methods:{
changeColor(){
this.$refs.conKid.setColor() //获取子组件调用其set方法修改颜色
}
}
这样通过ref引用就可以方便操作组件及上面的DOM结点
实例----控制文本框和按钮的按需转换
通过布尔值inputVisible来控制组件中的文本框于按钮的按需切换
当为true的时候就可以展示文本输入框,要想展示的一瞬间获得焦点,就使用元素js的方法.focus()即可;所以通过ref引用获取文本框即可
<hr color="#BA8B00">
<input type="text" ref="ipt" v-if="inputVisible" />
<button v-else type="button" @click="showInput" class="btn btn-primary">展示input的输入框</button>
<hr color="#BA8B00" />
methods:{
showInput() {
this.inputVisible = true
this.$refs.ipt.focus()
}
}
这样点击按钮就可显示文本框,但是控制台报错:Uncaught TypeError: Cannot read properties of undefined (reading ‘focus’) — 也就是浏览器并没有检测到文本框undefined
DOM元素的更新是异步的,也就是异步的任务【因为要重新渲染结点,属于宏任务】,在EventLoop中,这是在一个消息队列中,主任务就是优先执行,所以会立即去执行this.$refs,这个时候并没有获取到结点,报错
this.$nextTick(cb)将cb延迟到DOM更新完
上面就发现因为组件的更新是异步的,而js是将栈中的所有的主任务执行完毕才会执行异步任务,所以调用不应该在主任务,vue提供了内部函数$nextTick(cb),会将回调函数推迟到下一个DOM更新周期之后执行,也就是登组件重新渲染更新完毕之后,才会执行cb函数,保证cb操作的最新的回调函数
methods:{
showInput() {
this.inputVisible = true
//把对dom元素的操作推迟到页面的DOM更新完毕之后执行
this.$nextTick(() => {
this.$refs.ipt.focus()
})
}
}
这里将执行语句放到匿名回调函数中,等待更新完毕之后执行,相当于也是异步的任务
动态组件
动态组件指的是动态切换组件的显示和隐藏,vue提供了一个内置的< compnent>组件,用来实现组件的动态渲染 【之前的普通的DOM结点的显示可以使用v-if或者v-show】
- component是组件的占位符
- 通过js 属性动态指定要渲染的组件的名称
- < component is=“要渲染的组件的名称”>< /component>
也就是使用component标签占据位置,通过这个标签的is属性就可以动态绑定组件
<template>
<img alt="Vue logo" src="./assets/logo.png" /><br/>
<button type="button" @click="changeColor">改变子组件的字体的颜色</button><br>
<button type="button" @click="showComp">动态显示组件</button><br>
姓名<input type="text" v-model.lazy="info.username"/>
<hr/>
<!-- <life-circle v-if="flag"></life-circle>
<cosum-kid ref="conKid"></cosum-kid> -->
<component :is="conName"></component>
</template>
data() {
return {
conName:'',
showComp() {
this.conName = 'LifeCircle'
}
这样只要点击按钮,就可以显示LifeCircle组件
keep-alive保持组件状态
这里的component占位的效果,其实也是动态创建或者销毁组件实例,当切换其他的组件的时候,之前的组件实例就被销毁,第二次切换回来的时候就是重新创建组件实例,所以之前的状态就会恢复到最初的状态, 如果想要不销毁,那么就要使用keep-alive
默认情况下,切换动态组件时 无法保持组件的状态,这个时候可以使用vue内置的< keep- alive>组件包裹component标签保持动态组件的状态
<keep-alive>
<component :is = 'conName'></component>
</keep-alive>
这样组件实例切换的时候就不会被销毁,组件实例就会被缓存到浏览器的内存中,一般都会使用keep-alive来保持之前页面的状态
插槽slot
插槽是vue为组件的封装er提供的能力,允许programmer在封装组件的时候,把不确定的、希望由用户指定的部分定义为插槽
这样就可以通过插槽将html小的页面插入到封装好的组件中,插槽就是在组件的封装期间,为用户预留的占位符;上面的动态组件的component也是占位符,但是占的是整个组件的位,这里占位站的就只是DOM的位置
slot的基本使用
在封装组件期间,可以通过< slot>元素定义插槽,从而为用户预留的内容进行占位
<template>
<p>
这是子组件的第一个p标签
</p>
<slot></slot> <!-- 插槽:不确定放什么内容,后面组件的使用者进行自定义 -->
<p>
这是子组件的第二个P标签
</p>
</template>
使用者,也就是父组件调用的时候,使用双目的子组件的标签,标签之间内容都会被填充到插槽
这里可以演示一下
<template>
<p>SlotTest组件 ---- 这是第一个p标签;下面就是使用者自定义的内容</p>
<slot></slot>
<p>这是第二个p标签</p>
</template>
<script>
export default {
name: 'SlotTest',
}
</script>
<style lang="less" scoped>
</style>
-------------------父组件App.vue进行调用--------------
<!-- 调用子组件时,标签之间的内容就会被填充到子组件的插槽中 -->
<slot-test>
<div>
我是App根组件自定义的内容
</div>
</slot-test>
没有预留插槽,自定义内容丢弃
就是如果在组件中没有定义slot插槽,那么就算在父组件使用时,在标签间定义了内容,也不会填充,因为没有插槽,那么效果就是会被自动舍弃
后备内容【默认内容】— slot标签域内容
封装组件时,可以为预留的slot 插槽提供后备内容【默认内容】,如果使用者没有为插槽提供内容,那么就会使用后备内容,提供了就会覆盖
<template>
<p>
第一个p标签
</p>
<slot>
插槽的后备内容都在这里 【 如果使用者不提供,改内容生效】 --- 这样也实现动态页面
</slot>
<p>
第二个p标签
</p>
</template>
具名插槽
上面的只是定义了一个插槽,插槽标签slot中的内容就是后备内容;如果需要在封装组件的时候预留多个插槽结点,则需要为每一个插槽指定具体的name名称,这种含有名称的插槽为具名插槽
<template>
<header>
<!-- 页面的标题 -->
<slot name="header">
后备内容
</slot>
</header>
<main>
<slot>
这是一个默认的插槽
</slot>
</main>
<footer>
<slot name="footer">
后备内容 --- 具名插槽
</slot>
</footer>
</template>
<script>
export default {
name: 'SlotTest',
}
</script>
<style lang="less" scoped>
</style>
如果没有指定name名称的插槽,那么这个插槽的名称为default — 所以只能由一个default
具名插槽插入内容 v-slot:名称 【slot已经弃用】
比如这里定义了3个插槽
<template>
<div>
<header>
<!-- 页面的标题 -->
<slot name="header">
后备内容 ---- header
</slot>
</header>
<slot>
默认的插槽
</slot>
<footer>
<slot name="footer">
后备内容 --- 具名插槽
</slot>
</footer>
</div>
</template>
在App根组件中要使用这个组件,传入了需要插入的自定义的内容
<slot-test>
<div>
<h3>春晓</h3>
<center>孟浩然</center>
<p>春眠不觉晓,处处闻啼鸟</p>
<p>夜来风雨声,花落知多少</p>
</div>
</slot-test>
最终渲染的结果就是: 所有的内容都进入了默认插槽;也就是说,在不特殊说明的情况下,插入的内容都会进入默认插槽
要让内容进入具名插槽,需要使用template标签包裹内容,并且使用v-slot指定剧名插槽的名称
<!-- 调用子组件时,标签之间的内容就会被填充到子组件的插槽中 -->
<slot-test>
<template v-slot:header>
<h3>春晓</h3>
</template>
<template v-slot:default>
<center>孟浩然</center>
</template>
<template v-slot:footer>
<p>春眠不觉晓,处处闻啼鸟</p>
<p>夜来风雨声,花落知多少</p>
</template>
</slot-test>
对要插入的内容使用template标签包裹,并且使用v-slot指明具名插槽的名称,使用: ,不是=号,并且直接写名称,不用加引号,不然会报错
Error: Codegen node is missing for element/if/for node. Apply appropriate transforms first.
这里的问题就是因为:template标签放的位置有问题,template必须是最外层的标签,外面不能包裹div了,报错就是因为组件标签里的内容包裹在div中:happy:,template应该是最外面 ----- div也是属于DOM结点,template才能将其渲染到界面上 ---- 所以template必须包裹所有的DOM
<slot-test>
<template v-slot:header>
<h3>春晓</h3>
</template>
<span style="align-content: center;">作者:孟浩然</span>
<template v-slot:footer>
<p>春眠不觉晓,处处闻啼鸟</p>
<p>夜来风雨声,花落知多少</p>
</template>
</slot-test>
</template>
默认插槽可以省略template,具名插槽不能省略
具名插槽简写 : #插槽名
和v-on和v-bind一样,v-slot也可以简写,把参数之前的内容v-slot:替换为字符#, 比如
<template v-slot:footer>
<template #footer>
这里可以修改上面的代码
<slot-test>
<template #header>
<h3>春晓</h3>
</template>
<template #default>
<span style="align-content: center;">作者:孟浩然</span>
</template>
<template #footer>
<p>春眠不觉晓,处处闻啼鸟</p>
<p>夜来风雨声,花落知多少</p>
</template>
作用域插槽
之前的作用域插槽slot-scope已经弃用,在封装组件的过程中,可以为预留的slot插槽绑定props数据,带有props数据的slot就是作用域插槽
也就是说插槽中有数据,就是为slot动态绑定数据
<slot :prop = 'XXX'></slot>
使用者使用的时候传入数据的时候,还可以指定prop绑定的数据
<template #default='scope'>
xxx
</template>
在插槽的名称后面通过=‘scope’ ;然后这个scope就可以接收到上面的属性prop, 也就是实现了子组件的数据传输到了父组件
<slot name="header" :info = 'infomation' :msg = 'message'>
data() {
return {
infomation:{
stuname: 'Cfeng',
stuClass: 'HC2001',
stuMajor: 'CS'
},
message: 'Hello,Cfeng!!'
}
}
--------------------------------------------------------------
<slot-test>
<template #header='scope'>
<p>{{scope}}</p>
</template>
</slot-test>
然后打印的结果就是: { “info”: { “stuname”: “Cfeng”, “stuClass”: “HC2001”, “stuMajor”: “CS” }, “msg”: “Hello,Cfeng!!” }】
也就是scope会接收插槽传递的所有的属性形成的JSON对象
解构作用域插槽
之前已经使用过很多次解构了,就是可以直接加上{},然后就可以直接取出对象中的属性;
<template #header='{msg,info}'>
这样就可以直接使用msg的值,而不再需要使用scope.msg来获得数据
作用域插槽应用
作用域插槽的最主要的特点就是组件的多态,就类似之前的java的abstract一样;不会具体定义,交给用户来决定数据的样式
比如这里子组件提供了一个table
<template>
<div>
<table style="border: 1px solid rosybrown;">
<th>
<td>序号</td>
<td>姓名</td>
<td>状态</td>
</th>
<tr v-for="item in list":key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.state}}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'SlotTest',
data() {
return {
list:[
{id:1,name:'张三',state:true},
{id:2,name:'李四',state:true},
{id:3,name:'Cfeng',state:true}
]
}
}
}
</script>
这里的子组件将数据直接给到了template中,但是问题就是这里的state,并不知道用户想要一个什么样子的样式,是复选框,还是什么;所以这里不要直接将数据按照特定的格式渲染,而是交给使用者App来按需操作
<template>
<div>
<table style="border: 1px solid rosybrown;">
<th>
<td>序号</td>
<td>姓名</td>
<td>状态</td>
</th>
<tr v-for="item in list":key="item.id">
<slot :item = 'item'>
<!-- 插槽中放一个默认的渲染的样式,用户如果忘记可以后备 -->
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.state}}</td>
</slot>
</tr>
</table>
</div>
</template>
在App.vue中通过作用域插槽的操作,来进行具体的渲染
<slot-test>
<template #default='{item}'>
<td>{{item.id + 1}}</td>
<td>{{item.name}}</td>
<td>
<input type="checkbox" :checked="item.state" />
</td>
</template>
</slot-test>
父组件就可以解构出item属性然后使用,自定义样式;比如这里将state渲染成了一个复选框的样式
⚠: Whitespace was expected. 这是因为v-for指令和:key两个属性之间需要空格
<tr v-for = 'item in list' :key='item.id'> //因为这是属性绑定,key不是修饰符,绑定的key属性,两个不同的指令或者属性之间要有空格
自定义指令
vue官方提供了v-for,v-bind等内置指令,除此之外vue还允许开发者自定义指令,vue自定义指令主要有两类:
- 私有自定义指令
- 全局自定义指令
私有自定义指令 directives结点下声明
这里先定义一个组件MyHome,其中有一个文本框,现在的需求: 当渲染出组件的时候,文本框自动获得焦点
- 可以使用之前的ref来动态获得组件【这里使用的是动态显示组件,点击按钮会显示MyHome组件,然后这里的渲染必须是异步任务,所以需要使用$nextTick,这里前面是获取组件,后面是获取组件上的结点
<input ref="ipt" type="text" class="form-control" />
<keep-alive>
<component :is="conName" ref="myHome"></component>
</keep-alive>
showComp() {
this.conName = 'MyHome'
this.$nextTick(() => {//变成异步任务
this.$refs.myHome.$refs.ipt.focus()
})
}
这里直接给动态组件加上了ref属性,也是可以获取到这里具体的组件的
这里可以使用自定义指令,比如为input绑定自定义指令v-focus
<input type="text" class="form-control" v-focus/>
[Vue warn]: Failed to resolve directive: focus --自定义指令必须要进行声明
使用自定义指令,必须以v-开头,但是在directives下面声明私有指令时,不用v- 这里的directives结点和data等结点平级
directives: {
//自定义一个私有指令
focus: {
//当绑定元素插入到DOM时,自动会触发mounted函数
mounted(e1) {
e1.focus()
}
}
}
这里mounted的参数就是自定义指令绑定的DOM结点;mounted函数的执行时机: 指令绑定的结点渲染到DOM结构的时候自动触发
<input type="text" class="form-control" v-focus />
directives:{
focus: {
mounted(e) {
e.focus()
}
}
}
自动执行mounted方法,获取焦点; 所以自定义的私有指令就是在对应的组件的directives结点中声明指令,然后就可以调用了,mounted函数可以获取到绑定的元素DOM
全局自定义指令
上面的v-focus指令声明在MyHome组件中,在其他的组件中是不能使用这个指令的;就类似之前的组件的全局注册一样;只要在main.js进行指令声明就可以使用,使用spa_app.directive方法可以声明,第一个是名称,第二个是指令的方法体
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/css/bootstrap.css'
//导入axios
import axios from 'axios'
const spa_app = createApp(App)
//在mount之前进行配置
//声明请求的相对路径
axios.defaults.baseURL = 'https://www.escook.cn/api'
//全局注册挂载
spa_app.config.globalProperties.$ajax = axios
spa_app.config.unwrapInjectedRef = true
spa_app.directive('focus',{
mounted(e) {
e.focus()
}
})
spa_app.mount('#app')
还是要在spa_appmout之前进行声明,和组件的全局注册类似
全局声明自定义指令之后,不管在哪个组件中都可以使用该指令,这里将私有的v-focus声明去除;在MyHome组件中还是可以使用
updated函数保持v-focus持续触发
上面的v-focus指令有点缺陷: — mounted函数只是在元素第一次插入DOM时被调用,当DOM更新时mounted函数不会被触发; 与之相比,updated函数【之前分享组件的生命周期的时候,组件的几个函数和DOM的函数相同,mounted就是渲染到页面后触发,而updated就是每次页面更新都会触发】
spa_app.directive('focus',{
mounted(e) {
e.focus()
},
updated(e) {
e.focus()
}
})
这样声明之后就可以渲染和更新的时候都会获取焦点【和组件的生命周期函数是相同的】组件也可以看作父组件的一个DOM结点
<template>
<div class="home-container">
<p>MyHome组件 ---- {{count}}</p>
<hr color="yellowgreen"/>
<input ref="ipt" type="text" class="form-control" v-focus /><br>
<button type="button" class="btn btn-primary" @click="count++">数值+ 1</button>
</div>
</template>
这里点击按钮页面更新,这个时候会执行updated函数,还是会获取焦点
在vue2项目中使用自定义指令,是没有ed的,就是mount和update【提一句】
函数简写
如果mounted函数和updated函数的逻辑完全相同,就可以将方法的第二项完全变成一个方法,而不是方法体
spa_app.directive('focus',(e) => {
e.focus()
})
指令的参数值
自定义指令声明的方法的第一个形参指代的DOM对象,第二个形参指定的时指令绑定的参数对象,通过.value获取参数对象的具体值
vue提供的指令都是支持参数的,自定义指令也是支持的,在绑定指令的时候,可以通过等号
的形式为指令绑定具体的参数值
<input type='text' v-model.number = 'count' v-focus v-color= "'red'" />
比如上面就为v-color自定义指令绑定了参数red;
声明指令的时候,可以通过函数的形参binding来接收绑定的参数
,第一个参数是指代的DOM对象,第二个参数就是binding参数; 注意binding接收的是一个对象,获取red要使用binding.value
spa_app.directive('color', (e,binding) => {
e.style.backgroundColor = binding.value
})
子组件使用v-color指令时,需要注意里面还有单引号; 因为如果时属性绑定,后面的变量就要包裹在引号中,现在这个变量时字符串,所以需要有两重的引号
Table案例
案例达到的效果就是商品列表,主要步骤
首先建立一个vite项目table-demo,初始化项目
npm init vite-app table-demo
cd table-demo
npm i
npm i less -D
npm i axios -S
npm run dev
同时要导入bootstap.css,修改index.css全局的样式
:root {
font-size: 12px;
}
body {
padding: 8px;
}
- 请求商品列表的数据 ---- 上面安装了axios包,接下来就是在mian.js中进行配置
methods:{
async getGoodsList() {
const {data: res} = await this.$ajax.get('/goods')
if(res.status !== 0) return console.log("请求商品列表失败")
this.goodsList = res.data
}
},
components: {
},
created() {
this.getGoodsList()
}
}
这里需要注意的就是定义的全局属性$ajax要通过this进行调用,不能直接写出,不然undefined
- 封装MyTable组件
封装的要求: 用户通过名为data的prop属性,为MyTable组件指定数据源; 在Mytable组件中,预留名称为header的具名插槽;同时要预留名为body的作用域插槽
这里模板结构还是基于的bootstrap进行渲染,这里的作用域插槽携带的数据就是row当前的数据对象和当前的索引index
<template>
<!-- <button type="button" class="btn btn-primary">jkj</button> -->
<my-table :data = 'goodsList'>
<template #header>
<tr>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</tr>
</template>
<template #body="{row,index}">
<td>{{index + 1}}</td>
<td>{{row.goods_name}}</td>
<td>¥{{row.goods_price}}</td>
<td>{{row.tags}}</td>
<td>
<button type="button" class="btn btn-danger btn-sm">删除</button>
</td>
</template>
</my-table>
</template>
<script>
import MyTable from './components/my-table/MyTable.vue'
export default {
name: 'App',
data() {
return {
goodsList:[],
}
},
methods:{
async getGoodsList() {
const {data: res} = await this.$ajax.get('/goods')
if(res.status !== 0) return console.log("请求商品列表失败")
this.goodsList = res.data
}
},
components: {
MyTable,
},
created() {
this.getGoodsList()
}
}
</script>
<style lang="less" scoped></style>
- 实现删除的功能
为按钮添加事件处理函数,根据id删除goodsList中的数据
<button type="button" class="btn btn-danger btn-sm" @click="onRemoveGoods(row.id)">删除</button>
onRemoveGoods(id) {
this.goodsList = this.goodsList.filter(x => x.id !== id) //过滤出不是id的所有的数据
}
- 实现添加标签的功能
<td>
<!-- 循环渲染标签 -->
<span class="badge badge-warning ml-2" v-for="tag in row.tags">{{tag}}</span>
</td>
标签tags也是一个数组,所以需要使用class进行渲染
文本输入框的焦点事件blur,就是失去焦点的时候就会触发
这里的所有的代码都是在App.vue完成的
<template>
<!-- <button type="button" class="btn btn-primary">jkj</button> -->
<my-table :data = 'goodsList'>
<template #header>
<tr>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</tr>
</template>
<template #body="{row,index}">
<td>{{index + 1}}</td>
<td>{{row.goods_name}}</td>
<td>¥{{row.goods_price}}</td>
<td>
<!-- 基于当前的inputVisible,来控制input和button的按需显示 -->
<input type="text" class="form-control form-control-sm ipt-tag" v-if="row.inputVisible" v-focus v-model.trim = 'row.inputValue' @blur="onInputConfirm(row)" @keyup.enter="onInputConfirm(row)" @keyup.esc = "row.inputValue =''"/>
<button type="button" class="btn btn-primary btn-sm" v-else @click="row.inputVisible = true">+Tag</button>
<!-- 循环渲染标签 -->
<span class="badge badge-warning ml-2" v-for="tag in row.tags">{{tag}}</span>
</td>
<td>
<button type="button" class="btn btn-danger btn-sm" @click="onRemoveGoods(row.id)">删除</button>
</td>
</template>
</my-table>
</template>
<script>
import MyTable from './components/my-table/MyTable.vue'
export default {
name: 'App',
data() {
return {
goodsList:[],
}
},
methods:{
async getGoodsList() {
const {data: res} = await this.$ajax.get('/goods')
if(res.status !== 0) return console.log("请求商品列表失败")
this.goodsList = res.data
},
onRemoveGoods(id) {
this.goodsList = this.goodsList.filter(x => x.id !== id) //过滤出不是id的所有的数据
},
onInputConfirm(goods) {
//因为双向绑定,所以输入的值在inputValue中
const val = goods.inputValue
goods.inputValue = ''
//隐藏文本框
goods.inputVisible = false
//判断重复和空
if(!val || goods.tags.indexOf(val) !== -1) return
//将内容存放到tags中
goods.tags.push(val)
}
},
components: {
MyTable,
},
created() {
this.getGoodsList()
}
}
</script>
<style lang="less" scoped>
.ipt-tag {
width: 80px ;
display: inline;
}
</style>
这样就完成了列表的标签的添加
vue中关于组件部分就介绍到这里,接下来就是路由了
标签:slot,自定义,DOM,插槽,focus,指令,组件 来源: https://blog.csdn.net/a23452/article/details/123584870