Vue3 组件笔记

Vue3 组件笔记

十二月 03, 2020

VUE组件

  1. 了解组件化
  2. 了解什么是组件
  3. vue是如何注册组件的
  4. vue组件的嵌套和组合如何实现
  5. v-is
  6. 通信
  7. 组件过渡动效
  8. 组件生命周期

注册

html部分

1
2
3
4
5
6
7
8
9
<div id="app">
<my-banner></my-banner>
</div>

<template id="banner">
<div>
banner
</div>
</template>

局部注册

1
2
3
4
5
6
7
8
Vue.createApp({
components: { // 使用components进行局部注册
// 组件名: 组件选项
'my-banner': {
template: '#banner'
}
}
}).mount('#app')

全局注册

1
2
3
4
5
Vue.createApp({})
.component('my-banner', {
template: '#banner'
})
.mount('#app')

vue2和vue3的不同点,

1. vue3中发现template中可以直接写多个元素 | vue2 中template下的直接子元素有且仅有一个
2. vue3中销毁阶段的钩子函数为beforeUnmount和Unmount | vue2中是beforeDestroy和destroyed

组件嵌套

组件嵌套就是将子组件以标签的形式放到父组件template中

html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app">
<lay-out></lay-out>
</div>

<template id="lay-out">
<div>
我在上面
<hr>
<!-- 在lay-out组件中插入lay-inside标签 -->
<lay-inside></lay-inside>
</div>
</template>
<template id="lay-inside">
<div>
我在下面
</div>
</template>

组件嵌套-局部

1
2
3
4
5
6
7
8
9
10
11
12
Vue.createApp({
components: {
'lay-out': {
template: '#lay-out',
components: {
'lay-inside': {
template: '#lay-inside'
}
}
}
}
}).mount('#app')

组件嵌套-全局

1
2
3
4
5
6
7
8
Vue.createApp({})
.component('lay-out', {
template: '#lay-out'
})
.component('lay-inside', {
template: '#lay-inside'
})
.mount('#app')

组件-组合-插槽

插槽的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<div id="app">
<lay-out>
<!-- lay-inside组件是作为lay-out组件内容呈现的 -->
<!-- 问题: lay-inside组件显示不出来 -->
<!-- 解决方案: slot插槽 -->
<lay-inside></lay-inside>
</lay-out>
</div>

<template id="lay-out">
<div>
我在上面
<hr>
<!-- vue内置组件,用于放置组件的内容 -->
<slot></slot>
</div>
</template>
<template id="lay-inside">
<div>
我在下面
</div>
</template>

<script>
Vue.createApp({})
.component('lay-out', {
template: '#lay-out'
})
.component('lay-inside', {
template: '#lay-inside'
})
.mount('#app')
</script>

具名插槽

  1. 在#app内的组件中创建template标签 并给它设置指令v-slot:nameVal
  2. 在下面template中内部插槽slot 中设置name=”nameVal”
  3. 具名插槽是一对一的 上方#app内的标签顺序颠倒都没事
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div id="app">
<my-txt>
<template v-slot:footer>
<p>这是尾部</p>
</template>
<template v-slot:default>
<p>这是身体</p>
</template>
<template v-slot:header>
<h2>这是头部</h2>
</template>
</my-txt>
</div>

<template id="my-txt">
<!-- 插槽的一对一 -->
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>

<script>
Vue.createApp({})
.component('my-txt', {
template: '#my-txt'
})
.mount('#app')
</script>

作用域插槽

  1. 组件是一个独立的聚合体,那么组件的数据应该由自己来定义,组件内通过data来定义
  2. 单向数据绑定,将数据绑定给slot
  3. 设置v-slot:nameVal=”scoped” // scoped是自定义的
  4. 渲染数据

js部分

1
2
3
4
5
6
7
8
9
10
11
Vue.createApp({})
.component('my-txt', {
template: '#my-txt',
// 第一步,定义data
data () {
return {
msg: 'Hello Vue3'
}
}
})
.mount('#app')

html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<my-txt>
<!-- 第三步 -->
<template v-slot:default="scoped">
<!-- 第四步 数据渲染 -->
<p> {{ scoped.msg }} </p>
</template>
</my-txt>
</div>

<template id="my-txt">
<div>
<!-- 第二布: 单向数据绑定,将数据绑定给slot -->
<slot :msg="msg"></slot>
</div>
</template>

组件的几个常用选项(案例:反向数据)

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Vue.createApp({})
.component('my-txt', {
template: '#my-txt',
data () {
return {
msg: 'Hello Vue3'
}
},
methods: {
reverseMsg() {
this.msg = this.msg.split('').reverse().join('')
}
},
computed: {
newMsg() {
return this.msg.split('').reverse().join('')
}
},
watch: {
msg() {
alert('数据改变了')
}
}
})
.mount('#app')

html部分

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
<my-txt></my-txt>
</div>

<template id="my-txt">
<div>
<button @click="reverseMsg">按钮</button>
<p> {{ msg }} </p>
<hr>
<p> {{ newMsg }} </p>
</div>
</template>

v-is

组件v-is

  1. 组件不能违背html基本规则
  2. v-is=”‘’” (双引号里面接单引号)

表格案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<!-- <Hello></Hello> -->
<!-- 问题: Hello被踢出去了 -->
<!-- 解决 v-is -->
<!-- 不正确的: <tr v-is="Hello"></tr> -->
<tr v-is="'my-txt'"></tr>
</table>
</div>

<template id="my-txt">
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
</tr>
</template>

js部分

1
2
3
4
5
Vue.createApp({})
.component('my-txt', {
template: '#my-txt'
})
.mount('#app')

v-is 案例

做一个用户密码或者手机号验证码的登陆页面

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.createApp({
data () {
return {
type: 'user-login'
}
},
methods: {
change() {
this.type = this.type == 'user-login' ? 'phone-login' : 'user-login'
}
}
})
.component('user-login', {
template: '#user-login'
})
.component('phone-login', {
template: '#phone-login'
})
.mount('#app')

html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
<!-- 登陆选择 -->
<button @click="change">切换</button>
<!-- vue提供了一个内置组件component,实现组件的切换 -->
<!-- 他叫动态组件 -->
<!-- 当我们频繁的切换按钮时,组件在频繁的创建和销毁 -->
<!-- 提高组件的切换速率,我们可以将组件放入缓存 -->
<!-- vue提供一个内置 keep-alive -->
<!-- 它可以写两个api
不写则全部包括
include 包含
exclude 排除
-->
<!-- <component :is="type"></component> -->
<!-- 可以写成 -->

<keep-alive>
<component :is="type"></component>
</keep-alive>
</div>

<template id="user-login">
<p>这是用户名登陆页</p>
</template>
<template id="phone-login">
<p>这是手机号登陆页</p>
</template>

通信

组件通信

过渡动效

  1. vue提供了一个内置组件: transition
  2. 属性mode 表示transition组件的过度的顺序 out-in(一般用这个)/in-out
  3. 属性enter-active-class 表示的是过度进入时的过渡效果
  4. 属性leave-active-class 表示的是过渡离开的过渡效果
1
2
3
4
5
6
<transition
mode="in-out"
enter-active-class="animate__animated animate__backInLeft"
leave-active-class="animate__animated animate__backOutLeft"
>
</transition>

生命周期

avatar

挂载阶段

虚拟DOM在created – beforeMount生成,真实DOM在mounted拿到

  1. beforeCreate()
    1. 该钩子函数表示组件创建前。
    2. 初始化事件和生命周期钩子函数
  2. created()
    1. 该钩子函数表示创建结束,
    2. 这里我们有了组件。初始化注入【option】并且数据响应式【数据改变,界面改变】处理
  3. beforeMount()
    判断组件是否有template选项?
    1. yes 使用render函数来编译 template 得到虚拟DOM对象模型
    2. no 使用el.innerHTML来代替模板
  4. mounted()
    1. 创建app.$el来代替el 渲染虚拟DOM为真实DOM
  5. mounted案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <div id="app">
    <Hello></Hello>
    </div>

    <template id="hello">
    <!-- 这里的标签我们称之为 jsx[javascript+xml] 是VDOM的一种简化型写法 -->
    <!-- jsx是对js语法的一种扩展,有了他我们就可以在标签中书写js了 -->
    <div>
    hello
    <p> 123 </p>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const app = Vue.createApp({})
    app.component('Hello',{
    template: '#hello',
    beforeCreate () {
    console.log('挂载-beforeCreate')
    },
    created () {
    console.log('挂载-created')
    },
    beforeMount () {
    console.log('挂载-beforeMount')
    },
    mounted () {
    console.log('挂载-mounted')
    document.querySelector('p').style.background = 'red'
    }
    })
    app.mount('#app')

更新阶段

触发要求: 1. 数据要改变 2. 数据得使用 可以触发多次。updated可以做真实DOM操作

  1. beforeUpdate()
    1. 它表示组件更新前
    2. 任务: 用来监听数据改变的
  2. updated()
    1. 它表示组件更新结束了
    2. 任务:虚拟DOM渲染生成,并且通过diff算法比对新旧的虚拟DOM,得到一个patch补丁对象【虚拟DOM】,重新将patch补丁对象渲染到页面,也就是说我们又一次可以得到了真实DOM
  3. 案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <Hello></Hello>
    </div>
    <template id="hello">
    <div>
    <button @click="add">add</button>
    <p> {{ count }} </p>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Vue.createApp({})
    .component('Hello', {
    data() {
    return {
    count: 1
    }
    },
    methods: {
    add() {
    this.count++
    }
    },
    template: '#hello',
    updated() {
    // 上面写了一个方法
    // 点击按钮就数据自增
    // 每次数据改变时触发updated()钩子函数,发送请求
    axios('http://59.110.226.77:3000/api/category')
    .then(res => {
    console.log('res', res);
    })
    .catch(error => Promise.reject(error))
    }
    })
    .mount('#app')

销毁阶段

任选其一beforeUnmount或unmounted,他会自动删除组件的事件、指令、computed、watch,但需要手动删除计时器/定时器、window/document身上事件,清楚第三方实例,释放内存

  1. beforeUnmount()
    1. 它表示组件销毁前
    2. 自动销毁组件定义methods/指令/computed/watch
    3. 手动操作:
      1. 清除计时器和定时器
      2. 清除绑定在window/document身上的事件
      3. 清除第三方实例
  2. unmounted() 同上
  3. 案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <button @click="setFlag">切换</button>
    <Hello v-if="flag"></Hello>
    </div>
    <template id="hello">
    <div>
    <p>hello</p>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 设置类
    function People(name) {
    this.name = name
    }

    Vue.createApp({
    data() {
    return {
    flag: true // v-if可以来控制组件的销毁
    }
    },
    methods: {
    setFlag() {
    this.flag = !this.flag
    }
    }
    })
    .component('Hello', {
    template: '#hello',
    mounted() {
    // 在组件挂载后
    // 设置第三方实例
    // 设置计时器
    // 设置window身上事件resize
    this.people = new People('zhangsan')
    this.timer = setInterval(() => {
    console.log('计时器', this.people);
    }, 1000);

    window.onresize = function () {
    console.log('监听浏览器可视窗口尺寸');
    }
    },
    unmounted() {
    // console.log('销毁');
    // 销毁后不会清楚第三方实例,计时器,window身上事件
    // 必须手动消除
    delete this.people
    clearInterval(this.timer)
    window.onresize = null
    }
    })
    .mount('#app')

缓存阶段

触发条件: keep-alive[缓存组件] 如果缓存时间过久,可以自动去清楚缓存在deactivated()中设置

  1. activated()
    1. 表示你正在前台显示
  2. deactivated()
    1. 表示你正在后台缓存
  3. 案例
    等待补充…

错误处理阶段

任务: 父组件捕获子组件的错误,使用另外一个界面来代替错误的子组件,而这个界面我们称呼为 回退UI。 errorCaptured 写在父组件中

  1. errorCaptured(error, instance, info){}
    1. error 错误 instance 组件 info 具体信息
  2. 案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <div id="app">
    <Father></Father>
    </div>

    <template id="father">
    <div>
    <div v-if="flag"> 回退UI -- 您的网络有延迟,请重试 </div>
    <Son v-else></Son>
    </div>
    </template>
    <template id="son">
    <div>
    son
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Vue.createApp({})
    .component('Father', {
    template: '#father',
    data() {
    return {
    flag: false
    }
    },
    errorCaptured(error, instance, info) {
    // 写在父组件中
    // 如果子组件抛出错误会触发
    console.log('触发');
    if (error) { // 如果错误存在的话
    this.flag = true // 改变flag从而改变页面
    }
    }
    })
    .component('Son', {
    template: '#son',
    mounted() {
    throw new Error('出错了') // 在挂载阶段仍出个自定义错误
    }
    })
    .mount('#app')

虚拟DOM跟踪阶段

  1. renderTracked()
    1. 触发的操作是get
    2. 挂载的虚拟DOM和更新阶段虚拟DOM都会被追踪到
  2. renderTriggered()
    1. 触发的操作是set
    2. 更新阶段虚拟DOM都会被追踪到
  3. 案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div id="app">
    <Hello></Hello>
    </div>

    <template id="hello">
    <div>
    <button @click="add">+</button>
    <p> {{ count }} </p>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Vue.createApp({})
    .component('Hello', {
    template: '#hello',
    data() {
    return {
    count: 1,
    list: null
    }
    },
    methods: {
    add() {
    this.count++ // 这将导致renderTriggered调用
    }
    },
    renderTracked({ key, target, type }) {
    console.log('key', key);
    console.log('target', target);
    console.log('type', type);
    // 当组件第一次渲染时,这将被记录下来
    },
    renderTriggered({ key, target, type }) {
    console.log('key', key);
    console.log('target', target);
    console.log('type', type);
    }
    })
    .mount('#app')