nextTick异步更新队列🔥🔥🔥
文档 - 链接
Vue.nextTick - 链接
异步更新队列 - 链接
javascript
// 1. $nextTick
// 作用:获取异步更新的DOM元素,
// 我们虽然说:数据变化,视图自动更新 --> 但是Vue的视图更新是异步的(攒着不慌)
this.$nextTick(() => {
// 当dom元素内容更新之后,执行回调
})
// 2. $nextTick实现原理 ---> 理解记忆,面试题!
// 当Vue监听到数据变化,会开启一个队列(js中数组实现),并缓冲在同一事件循环中
// 发生的所有数据变更。等同一事件循环中所有数据变化完成后,才会异步更新视图。
// 并触发nextTick中的回调函数。
//(异步的理解:也就是说,并不是数据变化一次,就更新一次dom(视图))
// nextTick本质上就是宏微任务的一个降级处理, nextTick,下一个Tick
// promise.then ===> MutationObserver ==> setImmediate ==> setTimeout
----碾压面试官
// 3. src/core/global-api/index.ts ==> 45-line
Vue.nextTick = nextTick
// scor/instance/init.ts --> initRender(vm)
// src/core/instance/render.ts -- 100-line
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
不论是Vue.nextTick 还是 vm.$nextTick实例方法 内部都是调用的nextTick函数
// nextTick源码位置
// src/util/next-tick.ts 源码不就110多行吗
Eg.练习
vue
<template>
<div>
<!-- 首先页面中要准备两个元素,一个input,一个button -->
<input v-if="isSearch" class="ipt" type="text" ref="ipt">
<!-- 点击按钮修改状态, 所以要@click -->
<button v-else @click="changeOther">点击搜索</button>
</div>
</template>
<script>
export default {
data () {
return {
isSearch: false
}
},
methods: {
change () {
// 想要,一点击搜索按钮,输入框切换,并且输入框获取到焦点
this.isSearch = true // 按理说,input框应该渲染 ,但是因为Vue的dom更新是异步的
this.$nextTick(() => {
this.$refs.ipt.focus()
})
// setTimeout(() => {
// this.$refs.ipt.focus()
// }, 0);
},
async changeOther(){
this.isSearch = true
await this.$nextTick()
this.$refs.ipt.focus()
}
}
}
</script>
动态组件
javascript
npm i less less-loader
javascript
<template>
<div id="app">
<h1>动态组件dynamic的学习</h1>
<button class="my-btn-primary" @click="handleClick('UserAccount')">
账号密码填写
</button>
<!-- <button class="my-btn-success" @click="handleClick('UserInfo')">
个人信息填写
</button> -->
// 或者
<button class="my-btn-success" @click="compName = 'UserInfo'">
个人信息填写
</button>
<!--
动态组件的使用 ( component 组件 + is 属性 )
(1) 设置挂载点<component>, (在哪显示)
(2) 使用is属性来设置要显示哪个组件 (显示哪一个组件)
-->
<component :is="compName"></component>
</div>
</template>
<script>
import UserAccount from './components/UserAccount.vue'
import UserInfo from './components/UserInfo.vue'
export default {
components: {
UserAccount,
UserInfo
},
data () {
return {
compName: 'UserAccount'
}
},
methods:{
handleClick(comp){
this.compName = comp
}
}
}
</script>
自定义指令
vue
<template>
<div id="app">
<h3>自定义指令</h3>
<input ref="ipt" type="text" v-focus>
</div>
</template>
<script>
// 需求,当页面加载时,元素获取焦点
export default {
name: 'App',
// 补充代码
// mounted(){
// this.$refs.ipt.focus()
// }
directives:{
focus:{
inserted(el){
el.focus()
}
}
}
}
</script>
自定义指令的值
vue
<template>
<div id="app">
<h3 v-color="color1">自定义指令值-color-skyblue</h3>
<h3 v-color="color2">自定义指令值-color-orange</h3>
<hr>
<button @click="changeColor">点击修改颜色</button>
</div>
</template>
<script>
// 需求:不同的指令值,显示不同的字体颜色,点击按钮修改,更新颜色
export default {
name: 'App',
data(){
return {
color1:'pink',
color2:'orange'
}
},
methods:{
changeColor(){
this.color1 = 'purple'
}
},
directives:{
color:{
// 当指令所在的元素挂载到页面上时触发
// eslint-disable-next-line
inserted(el, {value}){
// console.log(binding)
el.style.color = value
},
// 当我们修改指令的值的时候,触发
update(el, binding){
el.style.color = binding.value
}
}
}
}
</script>
slot插槽
基本使用
- 先在组件内用 slot 占位
- 使用组件时, 传入具体标签结构内容插入(父组件中使用组件)
插槽后备内容
后备内容就是默认内容,当使用组件标签时,不写结构,就默认展示的内容
具名插槽
作用域插槽
App.vue
vue
<template>
<div>
<my-user>
<template v-slot:header>
<h2>我是标题渲染的</h2>
</template>
<!-- 子组件slot上传过来的值,还可以解构 -->
<!-- 给v-slot:插槽名 = "喜欢的变量名", slot标签上的属性都存到了这个对象中 -->
<template #default="{user, age}">
<!-- <div>{{slotProps}}</div> -->
<div>{{age}}</div>
<div>{{user.lastName}}</div>
<div>{{user.firstName}}</div>
<!-- // 补充代码 -->
</template>
<template v-slot:footer="obj">
<div>{{obj}}</div>
<div>{{obj.msg}}</div>
<p>我是底部footer渲染</p>
</template>
</my-user>
</div>
</template>
<script>
import MyUser from './components/MyUser.vue'
export default {
components:{
// MyDialog,
MyUser
}
}
</script>
<style>
.main{
width: 200px;
height: 200px;
border:1px solid red;
}
</style>
vue
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<!-- slot不加name属性,默认name为default -->
<!-- 可以将user变量作为slot标签的属性绑定
=> 父组件可以使用-->
<slot :user="user" :age="age">
<!-- 插槽的默认内容 -->
{{ user.lastName }}
</slot>
</main>
<footer>
<slot name="footer" msg="我是footer属性传的值"></slot>
</footer>
</div>
</template>
<script>
export default {
data(){
return {
user:{
lastName:'王',
firstName:'大锤'
},
age: 18
}
}
}
</script>
作用域插槽-表格练习
vue
<template>
<div>
<MyTable :data="list">
<template v-slot:default="{id}">
<!-- {{slotProps}} -->
<button @click="del(id)">删除</button>
</template>
</MyTable>
<MyTable :data="list2">
<template #default="{item}">
<button @click="showItem(item)">查看</button>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
data () {
return {
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
],
list2: [
{ id: 1, name: '赵小云', age: 18 },
{ id: 2, name: '刘蓓蓓', age: 19 },
{ id: 3, name: '姜肖泰', age: 17 },
]
}
},
methods:{
del(id){
console.log(id)
// filter过滤,留下el.id不等于传过来的id的元素
this.list = this.list.filter(el =>el.id !==id)
},
showItem(item){
alert(JSON.stringify(item))
}
},
components: {
MyTable
}
}
</script>
MyTable.vue
vue
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in data" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<!-- <button>删除</button> -->
<slot :id="item.id" :item="item"></slot>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props:{
data:{
type:Array,
// 如果是引用类型,需要用一个工厂
default:() => []
}
}
}
</script>
<style scoped>
.my-table {
width: 450px;
text-align: center;
border: 1px solid #ccc;
font-size: 24px;
margin: 30px auto;
}
.my-table thead {
background-color: #1f74ff;
color: #fff;
}
.my-table thead th {
font-weight: normal;
}
.my-table thead tr {
line-height: 40px;
}
.my-table th,
.my-table td {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.my-table td:last-child {
border-right: none;
}
.my-table tr:last-child td {
border-bottom: none;
}
.my-table button {
width: 65px;
height: 35px;
font-size: 18px;
border: 1px solid #ccc;
outline: none;
border-radius: 3px;
cursor: pointer;
background-color: #ffffff;
margin-left: 5px;
}
</style>
综合案例
MyTag的封装
MyTag-v-model回显数据
vue
<template>
<div class="my-tag">
<input
class="input"
type="text"
placeholder="输入标签"
ref="ipt"
v-if="isEdit"
@blur="handleClose"
@keyup.enter="$event.target.blur()"
:value="value"
v-focus
/>
<div v-else class="text" @dblclick="handleClick">{{value}}</div>
</div>
</template>
<script>
export default {
props:{
value:[Number,String]
},
data(){
return {
isEdit:false
}
},
methods:{
// 1. 自动获取焦点,方式1 $nextTick
async handleClick(){
this.isEdit = true
// await this.$nextTick()
// 等数据更新完了,获取最新的DOM元素
// this.$refs.ipt.focus()
},
handleClose(e){
this.isEdit = false
console.log(e.target.value)
if(!e.target.value.trim()){
return alert('内容不能为空哦')
}
// :value + @input
this.$emit('input', e.target.value)
}
},
// 2. 自定义指令的方式,自动获取焦点
directives:{
focus:{
// 当指令所在的元素插入到页面中时,触发
inserted(el){
el.focus()
}
}
}
}
</script>
<style lang="less" scoped>
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
input{
width: 80px;
}
</style>
MyTable的封装
vue
<template>
<table class="my-table">
<thead>
<tr>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</tr>
</thead>
<tbody>
<tr v-for="item in goods" :key="item.id">
<td>{{item.id}}</td>
<td><img :src="item.picture" /></td>
<td>{{item.name}}</td>
<td>
<!-- v-model -->
<my-tag v-model="item.tag"></my-tag>
</td>
</tr>
</tbody>
</table>
</template>
<script>
import MyTag from './MyTag.vue'
export default {
components:{
MyTag
},
data () {
return {
goods: [
{ id: 101, picture: 'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg', name: '梨皮朱泥三绝清代小品壶经典款紫砂壶', tag: '茶具' },
{ id: 102, picture: 'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg', name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌', tag: '男鞋' },
{ id: 103, picture: 'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png', name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm', tag: '儿童服饰' },
{ id: 104, picture: 'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg', name: '基础百搭,儿童套头针织毛衣1-9岁', tag: '儿童服饰' },
]
}
}
}
</script>
<style lang="less" scoped>
.my-table {
width: 100%;
border-spacing: 0;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
th {
background: #f5f5f5;
border-bottom: 2px solid #069;
}
td {
border-bottom: 1px dashed #ccc;
}
td,
th {
text-align: center;
padding: 10px;
transition: all .5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
</style>
MyTag.vue
vue
<template>
<div class="my-tag">
<input
class="input"
type="text"
placeholder="输入标签"
ref="ipt"
v-if="isEdit"
@blur="handleClose"
@keyup.enter="$event.target.blur()"
:value="value"
v-focus
/>
<div v-else class="text" @dblclick="handleClick">{{value}}</div>
</div>
</template>
<script>
export default {
props:{
value:[Number,String]
},
data(){
return {
isEdit:false
}
},
methods:{
// 1. 自动获取焦点,方式1 $nextTick
async handleClick(){
this.isEdit = true
// await this.$nextTick()
// 等数据更新完了,获取最新的DOM元素
// this.$refs.ipt.focus()
},
handleClose(e){
this.isEdit = false
console.log(e.target.value)
if(!e.target.value.trim()){
return alert('内容不能为空哦')
}
// :value + @input
this.$emit('input', e.target.value)
}
},
// 2. 自定义指令的方式,自动获取焦点
directives:{
focus:{
// 当指令所在的元素插入到页面中时,触发
inserted(el){
el.focus()
}
}
}
}
</script>
<style lang="less" scoped>
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
input{
width: 80px;
}
</style>
App.vue
vue
<template>
<div class="table-case">
<my-table></my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
name: 'TableCase',
components:{
MyTable
}
}
</script>
<style lang="less" scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
}
</style>
自定义表头和结构
app.vue
vue
<template>
<div class="table-case">
<my-table>
<template v-slot:head>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</template>
<template #main="{item}">
<td>{{item.id}}</td>
<td><img :src="item.picture" /></td>
<td>{{item.name}}</td>
<td>
<my-tag v-model="item.tag"></my-tag>
</td>
</template>
</my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
import MyTag from './components/MyTag.vue'
export default {
name: 'TableCase',
components:{
MyTable,
MyTag
}
}
</script>
<style lang="less" scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
}
</style>
MyTable
vue
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
</tr>
</thead>
<tbody>
<tr v-for="item in goods" :key="item.id">
<slot name="main" :item="item"></slot>
</tr>
</tbody>
</table>
</template>
<script>
/*
数据不能写死:动态渲染
结构不能写死:slot插槽 ==> 多个结构要自定义 => 具名插槽
- 表头自定义
- 表格主体自定义
*/
export default {
components:{
},
data () {
return {
goods: [
{ id: 101, picture: 'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg', name: '梨皮朱泥三绝清代小品壶经典款紫砂壶', tag: '茶具' },
{ id: 102, picture: 'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg', name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌', tag: '男鞋' },
{ id: 103, picture: 'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png', name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm', tag: '儿童服饰' },
{ id: 104, picture: 'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg', name: '基础百搭,儿童套头针织毛衣1-9岁', tag: '儿童服饰' },
]
}
}
}
</script>
<style lang="less" scoped>
.my-table {
width: 100%;
border-spacing: 0;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
th {
background: #f5f5f5;
border-bottom: 2px solid #069;
}
td {
border-bottom: 1px dashed #ccc;
}
td,
th {
text-align: center;
padding: 10px;
transition: all .5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
</style>