目录
(1)、使用 Vue.component() 函数全局注册组件
七、组件是如何更新的?(vue 的响应式原理 / 双向绑定原理)
4、捕获后代组件错误的特有的生命周期——errorCaptured
通常一个应用会以一棵嵌套的组件树的形式来组织。
一、创建一个 vue 实例(注意与创建组件区别开来)
var vm = new Vue({
el: "#app",
data() {
return {
}
},
})
一个 vue 实例 与 一个 vue 组件的区别:
- 一个 vue 组件的 data 选项必须是一个函数,以防污染全局变量,这样每个组件都可以维护一份被返回对象的独立的拷贝。
- 一个 vue 实例 的 data 选项可以是一个对象。
二、创建组件
1、创建一个普通的 vue 组件
一个普通的 vue 组件,其实就是一个对象。
const componentA = {
data () {
return {
msg: 'hello world'
}
},
template: '<h3>{{msg}}</h3>'
}
2、基于 “模块系统” 创建的 “单文件组件”
文件扩展名为 .vue 的文件都是 vue 的 “单文件组件”。
<template>
<div>hello {{str}}</div>
</template>
<script>
export default {
data () {
return {
str: 'world'
}
}
}
</script>
<style></style>
三、注册组件
组件的注册类型:全局注册 和 局部注册。
1、局部注册
局部注册组件的方式:
- 在 components 对象中局部注册要用到的组件
- 基于模块系统局部注册组件
(1)、在 components 对象中局部注册组件
<div id="app">
<component-a></component-a>
</div>
<script>
// 创建一个局部组件
const componentA = {
data () {
return {
msg: 'hellow'
}
},
template: '<h3>{{msg}}</h3>'
}
// 创建 Vue 的一个根实例
new Vue({
el:'#app',
data: {},
components:{
'component-a': componentA
}
})
</script>
(2)、单文件组件中局部注册组件
基于模块系统定义一个 componentA 组件:
<template>
{{str}}
</template>
<script>
export default {
name: 'componentA',
data () {
str: 'hello'
}
}
</script>
局部注册并使用 componentA 组件:
<template>
<component-a></component-a>
</template>
<script>
import componentA from '../src/components/componentA.vue'
export default {
components:{
componentA
}
}
</script>
【注意】
- 局部注册的组件在其子组件中不可用。
2、全局注册
全局注册组件的方式:
- 使用 Vue.component() 函数全局注册组件。
- 模块系统中全局注册组件。
全局注册的组件,可以在 Vue 根实例和所有的子组件中使用。
(1)、使用 Vue.component() 函数全局注册组件
// 假设有一个组件 componentA,将 componentA 组件全局注册为 button-counter 组件
const componentA = {
data () {
return {
msg: 'hello world'
}
},
template: '<h3>{{msg}}</h3>'
}
Vue.component("button-counter", componentA)
等价于:
Vue.component("button-counter", {
data () {
return {
msg: 'hello world'
}
},
template: '<h3>{{msg}}</h3>'
})
(2)、单文件组件中全局注册组件
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
所以会导致很多组件里都会有一个包含基础组件的长列表。例如:
<template>
<base-input
v-model="searchText"
@keydown.enter="search"
/>
<base-button @click="search">
<base-icon name="search"/>
</base-button>
</template>
<script>
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
</script>
如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context() 函数只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
【拓展】
require.context() 是 webpack 的 一个 api。它用来获取一个特定的上下文,主要用来实现自动化导入模块,以优化从一个文件夹引入很多模块的情况——它会遍历文件夹中的指定文件,然后自动导入,而不需要每次显式的调用 import 导入模块了。
【典例】
引入所有的 api(存放接口):
const files = require.context('.', false, /\.js$/)
const modules = {}
function getFileName (name) {
if (name.indexOf('-')) {
const _name = name.toLowerCase().split('-')
const len = _name.length
for (let i = 1; i < len; i++) {
_name[i] = _name[i].substring(0, 1).toUpperCase() + _name[i].substring(1)
}
return _name.join('')
} else {
return name
}
}
files.keys().forEach(key => {
if (key === './index.js') { return }
const _key = key.replace(/(\.\/|\.js)/g, '')
const humpName = getHumpName(_key)
modules[humpName] = files(key)
})
export default modules
额外的补充——上述代码的核心算法是:遍历树形结构,将其转化为一个对象的形式输出。
【注意】
- 全局注册的行为,必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
- 非必要不使用全局注册组件——全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
四、组件与插槽配合使用
详情请戳这里:vue 插槽 slot
五、组件的继承与混合
请戳这里:vue 组件的继承与混合
六、组件之间的通信
请戳这里:vue 组件之间的通信
七、组件是如何更新的?(vue 的响应式原理 / 双向绑定原理)
vue 核心——数据驱动 DOM(尽量避免直接操作 DOM)。
数据的来源:
- 来自父元素的属性;
- 来自组件自身的状态 data;
- 来自状态管理器,如 vuex,vue.observable。
状态(data)与属性(props)
- 状态是组件自身的数据,状态的改变未必触发更新;
- 属性是来自父组件的数据,属性的改变也未必触发更新。
data 触发更新条件:数据写在 data 里,并 return 出去,同时,必须在 template 中有使用。
props 触发更新条件:若以对象接收,可以且必须在 template 中有使用;若以数组接收,还得在data 中转接一下,才能且必须在 template 中有使用。
vue 响应式更新:
vue 在实例化的时候,使用 Object.definePropery() 方法或 Proxy 构造函数,对 data 进行 getter 和 setter 的处理。在组件渲染时,若用到 data 里的某个数据,这个数据就会被依赖收集进 watcher 里。当数据更新,如果这个数据在 watcher 里,就会收到通知并更新,否则不会更新——vue 采用“数据劫持”+“观察者模式(发布者-订阅者模式)”相结合的方式实现了双向绑定——vue 的响应式原理。
computed 和 watch 请戳此链接:vue 的计算属性(computed)和侦听器(watch)
八、vue 组件实例的生命周期
截止此次更新,vue 共有 11 个钩子函数:
vue 生命周期(钩子函数) | 执行时机 |
---|---|
beforeCreate | 初始化数据 |
created | 数据初始化完成 |
beforeMount | 准备挂载数据 |
mounted | 数据挂载完毕,DOM 加载完成 |
beforeUpdate | 更新前的状态 |
updated | 更新完成的状态 |
activated | 被 keep-alive 缓存的组件激活时调用 |
deactivated | 被 keep-alive 缓存的组件失活时调用 |
beforeDestroy | 在实例销毁之前调用,实例仍然完全可用 |
destroyed | 在Vue 实例销毁后调用,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 |
errorCaptured | 在捕获一个来自后代组件的错误时被调用 |
1、vue 生命周期的三个阶段
- 创建阶段
- 更新阶段
- 销毁阶段
(1)、创建阶段
beforeCreate:初始化事件和生命周期
created:数据初始化完成,包括数据观测、属性、侦听器的配置等。
beforeMount:将模板编译到虚拟DOM上。
rander——生成虚拟DOM
mounted:处理“异步请求”、“操作DOM”、“定时器”等,但是在mounted中,vue不承诺子组件也会挂载到真实DOM上,为确保子组件已经挂载到真实DOM上,常常需要用this.$nextTick()在回调中对DOM进行一些操作。
【拓展】
vue获取后端数据放在created还是mounted方法里面?
(2)、更新阶段
beforeUpdate:移除已经添加的事件监听器等,不可以更改依赖数据(响应式数据),否则会造成死循环。
rander——生成虚拟DOM
updated:可以操作DOM,添加的事件监听器等,但是vue不承诺子组件也会挂载到真实DOM上,为确保子组件已经挂载到真实DOM上,常常需要用this.$nextTick()在回调中对DOM进行一些操作。另外,不可以更改依赖数据,否则会造成死循环。
(3)、销毁阶段
beforeDestroy:移除已经添加的事件监听器,计时器等。
destroyed:移除已经添加的事件监听器,计时器等。
2、Vue 父子组件的生命周期顺序
(1)、加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
(2)、子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
(3)、父组件更新过程
父beforeUpdate->父updated
(4)、销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
3、缓存组件相关的两个特有的生命周期
(1)、activated
被 keep-alive 缓存的组件激活时调用。
(2)、deactivated
被 keep-alive 缓存的组件失活时调用。
4、捕获后代组件错误的特有的生命周期——errorCaptured
errorCaptured:在捕获一个来自后代组件的错误时被调用。
此钩子会收到三个参数:
- 错误对象
- 发生错误的组件实例
- 一个包含错误来源信息的字符串。
此钩子可以返回 false 以阻止该错误继续向上传播。以防止该组件可能会进入一个无限的渲染循环。
【拓展】Vue 的错误处理机制——errorCaptured 与 config.errorHandler