)@vue/cli-service
"serve":"vue-cli-service serve","build":"vue-cli-service build","lint":"vue-cli-service lint"
那么vue-cli-serve 执行了怎样的命令,怎么实现这些命令的呢?我们需要知道vue-cli-service是什么。
从vue-cli官方文档中我们知道在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。vue-cli-service 其实执行的是 node_modules/.bin/vue-cli-service
最终是执行了@vue/cli-service
除了通过命令行,可以使用vue.config.js中devServer字段配置开发服务器的参数;下面给出一段配置的例子:
2)详解Vue.component和Vue.extend
Vue.extend({})简述:使用vue.extend返回一个子类构造函数,也就是预设部分选项的vue实例构造器。
后可使用vue.component进行实例化、或使用new extendName().$mount(''+el)方式进行实例化(从而实现模拟组件)。
vue中$el等属性
属性:
vm.$el
获取Vue实例关联的DOM元素;
vm.$data
获取Vue实例的data选项(对象)
vm.$options
获取Vue实例的自定义属性(如vm.$options.methods,获取Vue实例的自定义属性methods)
vm.$refs
获取页面中所有含有ref属性的DOM元素(如vm.$refs.hello,获取页面中含有属性ref = “hello”的DOM元素,如果有多个元素,那么只返回最后一个)
Js代码
var app = new Vue({ el:"#container", data:{ msg:"hello,2018!" }, address:"长安西路" })
console.log(app.$el);
返回Vue实例的关联DOM元素,在这里是#container
console.log(app.$data);
返回Vue实例的数据对象data,在这里就是对象{msg:”hello,2018“}
console.log(app.$options.address);
返回Vue实例的自定义属性address,在这里是自定义属性address
console.log(app.$refs.hello)
返回含有属性ref = hello的DOM元素(如果多个元素都含有这样的属性,只返回最后一个)<h3 ref = "hello">呵呵 1{{msg}}</h3>
VUE的$refs和$el的使用
ref有三种用法:
1、ref 加在普通的元素上,用this.$refs.(ref值) 获取到的是dom元素
3、如何利用 v-for 和 ref 获取一组数组或者dom 节点
如果通过v-for 遍历想加不同的ref时记得加 :号,即 :ref =某变量 ;
这点和其他属性一样,如果是固定值就不需要加 :号,如果是变量记得加 :号。(加冒号的,说明后面的是一个变量或者表达式;没加冒号的后面就是对应的字符串常量(String)
应注意的坑有:
1、ref 需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
2、如果ref 是循环出来的,有多个重名,那么ref的值会是一个数组 ,此时要拿到单个的ref 只需要循环就可以了。
vm.$el
获取Vue实例关联的DOM元素;
比方说我这里想获取自定义组件tabControl,并获取它的OffsetTop。就需要先获取该组件。
在组件内设置 属性 ref=‘一个名称(tabControl2)’,
然后 this.$refs.tabControl2 就拿到了该组件
切记:ref属性,而获取组件的时候要用$refs
获取 OffsetTop,组件不是DOM元素,是没有OffsetTop的,无法通过 .OffsetTop来获取的。就需要通过$el来获取组件中的DOM元素
3)Vue.js之初始化el
Vue中的el,称之为挂载点,它有两种写法
1.new Vue({ el: '#app' });
2.new Vue({ }).$mount('#app');
- Vue会管理与Vue进行el绑定的元素及其内部的后代元素,建议使用id选择器,可以使用其他的双标签,不能使用html和body。
- el绑定的元素内,都是Vue的作用范围。
- 一个Vue实例只能绑定一个el。
4)组件上的v-model
在子组件中声明 emits 自定义事件,格式为 update:xxx
调用 $emit() 触发自定义事件,更新父组件中的数据
- 自定义事件可以用于开发支持 v-model 的自定义表单组件。回忆一下 v-model 在原生元素上的用法:<input v-model="searchText" />
上面的代码其实等价于下面这段 (编译器会对 v-model 进行展开):
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
而当使用在一个组件上时,v-model 会被展开为如下的形式:
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
要让这个例子实际工作起来,<CustomInput> 组件内部需要做两件事:
- 将内部原生 input 元素的 value attribute 绑定到 modelValue prop
- 输入新的值时在 input 元素上触发 update:modelValue 事件
这里是相应的代码:
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
现在 v-model 也可以在这个组件上正常工作了
<CustomInput v-model="searchText" />
另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的计算属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
v-model 的参数#
默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字:<MyComponent v-model:title="bookTitle" />
在这个例子中,子组件应声明一个 title prop,并通过触发 update:title 事件更新父组件值:
<!-- MyComponent.vue -->
<script>
export default {
props: ['title'],
emits: ['update:title']
}
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
多个 v-model 绑定#
利用刚才在 v-model参数小节中学到的技巧,我们可以在一个组件上创建多个 v-model 双向绑定,每一个 v-model 都会同步不同的 prop:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<script>
export default {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName']
}
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
5)自定义组件的 v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突:
告知vue底层该组件监听checked属性的change事件。
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。
从官网上看到,v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text和textarea元素使用value属性和input事件
checkbox和radio使用checked属性和change事件
select使用value和change事件
因为自定的组件并没有默认的value和input事件,在使用时,我们需要按照上面那样显式的去声明定义这些东西。这时,需要model选项,在定义组件的时候,指定prop的值和监听的事件。
6)vm.$destroy()
完全销毁一个实例。清理与其他实例的连接,解绑它的全部指令及事件监听器。
触发beforeDestroy和destroyed的钩子
这是官方讲解,在实际操作时发现,通过这个方法销毁实例 ,dom是仍然存在的,并且data和methods中的数据也在,在 destroyed钩子中都可以拿到,甚至你绑定的点击事件也仍然可以有效触发。
那么,它到底销毁了什么?
执行vm.$destroy()之后,watcher失效,无法通过数据改变视图。
所以在大多数场景下你不应该调用这个方法,官方推荐使用v-if或v-for指令以数据驱动试图的方式控制子组件的生命周期。
它并不是用来清除已有页面上的DOM的。
包括Watcher对象从其所在Dep中释放
手写$forceUpdate,vm.$destroy方法
学习vue源码(15)手写$forceUpdate,vm.$destroy方法_vm.$destroy();-优快云博客
7)子组件修改props:
当通过props传递过来的是值是引用类型的时候,如数组或者对象,此时我们通过子组件更改其传递过来的值,也会改变父组件的值,但是是不会像基本类型那样控制台报警告的,这是需要注意的
由于在js中,对象和数组是引用类型,指向同一个内存空间,所以当我们通过v-bind绑定的参数是一个数组或对象,然后传递过来时,那么他们在子组件的改变会影响到父组件的,(这和vue组件无关,由js特性所决定的,如果想避免这种情况,那么在子组件接收到此引用类型参数后处理下即可,如用JSON方法处理或用Object.assign()方法处理等)
此时使用data声明的变量由于值中转了下,然后也不是引用类型,所以在这里我们父组件对此参数修改了,子组件就不会同步更改,如果此时想同步更改,我们可以是使用
watch: 可使用sync模拟v-model语法糖,实现父子组件传值双向绑定
8)Vue:父子组件传值props、sync、v-model详解
正常父子传值:
如果想prop(num)进行“双向绑定”。 因此推荐以 update:myPropName的模式触发事件取而代之。.sync修饰符
v-model和.sync修饰符的区别:
两者本质都是一样,并没有任何区别: “监听一个触发事件”="(val) => value = val"。
.sync与v-model区别是
相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。
区别点:
格式不同。 v-model="num", :num.sync="num"
v-model: @input + value
:num.sync: @update:num
v-model只能用一次;.sync可以有多个。
只不过v-model默认对应的是input或者textarea等组件的input事件,如果在子组件替换这个input事件,其本质和.sync修饰符一模一样。比较单一,不能有多个。// 子组件可以用自定义事件,来替换v-model默认对应的原生input事件,只不过我们需要在子组件手动 $emit
model: {
prop: "value",
event: "update"
},
一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”,而并不像v-model那样,一个组件只能有一个。
因为使用v-model,子组件中只能触发一个input事件,事件名是唯一的,而.sync修饰符,它触发的时间名是updata:属性名,所以它有多个事件名,就可以使用多次
props/$emit是父子组件最常用的通信方式,而v-model、.sync只是其语法糖
子组件只是单一的修改某个父组件值的话,表单类组件使用v-model语法糖
子组件只是单一的修改某个父组件值的话,非表单类组件使用sync语法糖
复杂逻辑还是老老实实props/$emit
其实语法糖只是在父组件用的时候更加方便,而子组件该咋样还是咋样。
9)vue中的异步组件
我们在讲异步组件之前,我们再来回顾一下webpack打包时的分包操作。我们可以使用import()异步加载模块来实现分包操作。import函数的返回值是一个Promise,所以我们可以使用then进行下一步处理。
如下图所示为打包后的文件目录,因为我们如果同步加载math.js文件,此时就不存在中间的文件,此时当浏览器请求资源时,就会很慢。
一、vue中的异步组件
通过上面的webpack配置我们明白了为什么要进行分包操作,此时我们想一个问题,如果一个网站的页面在用户第一次浏览器时就将全部页面都下载了,这样会出现一个问题,就是首屏加载过慢。
如果我们的项目过大了,对于某些组件我们想要异步加载(也就是分包处理),此时Vue给我们提供了一个函数defineAsyncComponentdefineAsyncComponent可以传入两种类型的参数,第一个是函数,该函数需要返回Promise,第二个参数是一个对象类型,对异步函数进行配置。
第一种写法:函数写法
打包后的文件
第二种写法:对象写法
如图所示是可以实现分包操作,相面详细介绍一下传入对象中的选项。
loader选项:需要一部加载的模块,对应的是一个函数。
loadingComponent:加载过程中显示的组件。
errorComponent:加载失败时显示的组件。
delay:给出时间,当加载时间超过该时间,直接显示error组件。
suspensible:定义组件是否可挂起,默认是true。
一、异步组件的简介
所谓的异步组件就是通过import或者require导入的vue组件。
例如:const componentA = import(’@/components/componentA.vue’);
或者 const componentA = require(’@/components/componentA.vue’);
二、使用异步组件的好处
可以避免页面一加载时就去加载全部的组件,从而导致页面访问时间变长的问题。使用异步加载组件后,只有当需要某个组件时才会去加载需要的组件。
三、定义异步组件
1、setTimeout
var resCom = { template: "#async-example" }
Vue.component('async-example', function(resolve, reject){
setTimeout(function(){
// 向resolve回调传递组件定义
resolve(resCom);
}, 1000);
});
2、局部
当然上面的代码也可以通过局部组件的方式使用:
var vm = new Vue({
el: '#app',
components: {
'async-example': function (resolve, reject) {
setTimeout(function () {
resolve(resCom);
// reject('加载失败描述内容');
}, 1000);
}
}
});
3、promise
在 Vue 中一样也可以通过 Promise 的方式来定义异步组件,这也是定义异步组件的第二种方式 Promise。
// 注册组件
var resCom = {
template: "#async-example"
};
var vm = new Vue({
el: "#app",
components:{
'async-example':function(){
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(resCom)
}, 1000);
});
}
}
})
4、webpack
require模式
- webpack遗留功能require.ensure
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
import()
Webpack + Vue-CLI是 Vue 所推崇的一种构建方式,同时 Webpack 也直接内建支持这种 Promise 的异步组件注册方式。当使用 Webpack 来构建正式项目时,我们也可以直接通过 import('./my-async-component') 来注册一个异步组件,import('./my-async-component') 会直接返回一个 Promise。
// 全局注册
Vue.component(
'async-webpack-example',
// 这个 `import` 函数会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
// 局部注册
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
5、高级异步组件应该如何定义:
const AsyncComponent = function(){
return {
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: new Promise(function(resolve, reject){
setTimeout(function(){
resolve(resCom)
}, 2000);
});,
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
// PS: 组件加载超时时间,超时表示加载失败,会展示ErrorComponent。
// 比如在这里当我们把 Promise 中的 setTimeout 改为 4000的时候,则会展示 ErrorComponent
timeout: 3000
}
}
var vm = new Vue({
el: "#app",
// 注意这里与之前的写法不同之处,是因为我们把这个方法提出去赋值给了AsyncComponent的变量
components:{
'async-example': AsyncComponent
}
})
10)动态组件和异步组件
1、上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is 特性来实现
解析 DOM 模板时的注意事项:有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
<table>
<blog-post-row></blog-post-row>
</table>
- 这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is 特性给了我们一个变通的办法:
- <table>
<tr is="blog-post-row"></tr>
</table>
2、为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。
Vue 只有在这个组件需要被渲染时才会触发该工厂函数,且会把结果缓存起来供未来重渲染
- 如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。
- 这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
组件之间的循环引用
Vue异步组件处理路由组件加载状态的解决方案
- 在大型单页面应用中,处于对性能的考虑和首屏加载速度的要求,我们一般都会使用webpack的代码分割和vue-router的路由懒加载功能将我们的代码分成一个个模块,并且只在需要的时候才从服务器加载一个模块。
- 但是这种解决方案也有其问题,当网络环境较差时,我们去首次访问某个路由模块,由于加载该模块的资源需要一定的时间,那么该段时间内,我们的应用就会处于无响应的状态,用户体验极差。
- 【解决方案】这种情况,我们一方面可以缩小路由模块代码的体积,静态资源使用cdn存储等方式缩短加载时间,另一方面则可以路由组件上使用异步组件,显示loading和error等状态,使用户能够得到清晰明了的操作反馈。
- 【具体实现】声明方法,基于Vue动态组件工厂函数来返回一个Promise对象
调用:
11)keep-alive
当组件切换时,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题
注意这个 <keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。
组件中的name选项有什么作用?
当项目使用keep-alive时,可搭配组件name进行缓存过滤
//假设我们有个组件命名为detail,
//其中dom加载完毕后我们在钩子函数mounted中进行数据加载
export default {
name:'Detail'
},
mounted(){
this.getInfo();
},
methods:{
getInfo(){
axios.get('/xx/detail.json',{
params:{
id:this.$route.params.id
}
}).then(this.getInfoSucc)
}
}
/**
在App.vue中使用了keep-alive导致
我们第二次进入的时候页面不会重新请求,
即不会再次触发mounted函数。
有两个解决方案:
一个增加activated()函数,每次进入新页面的时候再获取一次数据。
还有个方案就是在keep-alive中增加一个过滤exclude:
*/
<div id="app">
<keep-alive exclude="Detail">
<router-view/>
</keep-alive>
</div>
3. 当你用vue-tools时:该调试工具里显示的组见名称是由vue中组件name决定的
作用
在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。
原理
在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
- 生命周期函数
1. activated
在 keep-alive 组件激活时调用
该钩子函数在服务器端渲染期间不被调用
2. deactivated
在 keep-alive 组件停用时调用
该钩子在服务器端渲染期间不被调用
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
根据条件缓存页面
12)import() 中的表达式
let path = '@/components/Message.vue';
let com = ()=>import(path)
let path = '@/components/Message.vue';
copyPath = path.substring(2,path.length)
let com = ()=>import(`@/copyPath`) 复制代码
1、import无法导入变量字符串作为路径
导入失败写法:let path = '@/components/Message.vue';
let com = ()=>import(path)
导入成功写法:let path = '@/components/Message.vue';
copyPath = path.substring(2,path.length)
let com = ()=>import(`@/${copyPath}`)
webpack目前不支持完全意义上的动态导入。
可以通过字符串模板功能帮助webpack识别主要路径,这样编译时会编译src下的模块,但组件的只有在被渲染时才加载,从而实现异步导入。
2、立即执行函数
异步组件的导入处理成立即执行函数,会在页面调用时导入的components函数作为变量直接被执行导入过程,避免导入的函数再次调用。
import() 必须至少包含一些关于模块的路径信息。打包可以限定于一个特定的目录或文件集,以便于在使用动态表达式时 - 包括可能在 import() 调用中请求的每个模块。例如, import(`./locale/${language}.json`) 会把 .locale 目录中的每个 .json 文件打包到新的 chunk 中。在运行时,计算完变量 language 后,就可以使用像 english.json 或 german.json 的任何文件。
13)Vue的computed和watch的细节全面分析
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName:{//fullName不可在data里面定义,
get(){//回调函数 当需要读取当前属性值是执行,根据相关数据计算并返回当前属性的值
return this.firstName + ' ' + this.lastName
},
set(val){//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据
//val就是fullName的最新属性值
console.log(val)
const names = val.split(' ');
console.log(names)
this.firstName = names[0];
this.lastName = names[1];
}
}
}
watch:{ secondChange:{ handler(oldVal,newVal){ console.log(oldVal) console.log(newVal) }, deep:true } },
2.console.log打印的结果,发现oldVal和newVal值是一样的,所以深度监听虽然可以监听到对象的变化,但是无法监听到具体对象里面那个属性的变化
3.oldVal和newVal值一样的原因是它们索引同一个对象/数组。Vue 不会保留修改之前值的副本
vm.$watch的深度监听
4.深度监听对应的函数名必须为handler,否则无效果,因为watcher里面对应的是对handler的调用
方法二:watch如果想要监听对象的单个属性的变化,必须用computed作为中间件转化,因为computed可以取到对应的属性值
data(){
return{
'first':{
second:0
}
}
},
computed:{
secondChange(){
return this.first.second
}
},
watch:{
secondChange(){
console.log('second属性值变化了')
}
},
传入的值想作为局部变量来使用,直接使用会
props:['listShop'],
data(){
return{}
},
created(){
this.listShop=30
}
报错
这个错误是说的避免直接修改父组件传入的值,因为会改变父组件的值,贴上官网介绍
简单数据类型解决方案:
所以可以在data中重新定义一个变量,改变指向,但是也只是针对简单数据类型,因为复杂数据类型栈存贮的是指针,
props:['listShop'],
data(){
return{
listShopChild:this.listShop
}
},
created(){
this.listShopChild=30
}
这样就可以愉快的更改传入的简单数据类型的数据啦!不会有任何报错,也不会影响父组件!
解决方案2
直接用computed改变
computed:{
listShopChild(){
return this.listShop
}
}
3.5 存在的问题
注意:此时用computed时,如果是数组this.$set(arr,1,true)对应的值耶不更新,
这个很坑,这个bug我找个很久
如果传入的值只是在data定义,并未在methods或生命周期钩子更改,直接改变也会报错
所以还是可以先用局部变量接收,再修改,这个坑比较多
应用4
监听vuex的state或者getters值的变化
computed:{
stateDemo(){
return this.$store.state.demoState;
}
}
watch:{
stateDemo(){
console.log('vuex变化啦')
}
}
javascript - Vue的computed和watch的细节全面分析 - 个人文章 - SegmentFault 思否
14)父子组件的生命周期执行流程
- 虽然updated函数会在数据变化时被触发,但却不能准确的判断是那个属性值被改变,所以在实际情况中用computed或watch函数来监听属性的变化,并做一些其他的操作。
- 所有的生命周期钩子自动绑定 this 上下文到实例中,所以不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()),会导致this指向父级。
- 在使用vue-router时有时需要使用来缓存组件状态,这个时候created钩子就不会被重复调用了,如果我们的子组件需要在每次加载或切换状态的时候进行某些操作,可以使用activated钩子触发。
- 父子组件的钩子并不会等待请求返回,请求是异步的,VUE设计也不能因为请求没有响应而不执行后面的钩子。所以,我们必须通过v-if来控制子组件钩子的执行时机
【2】如果子组件接受父组件传递过来的值经过转换展示,将prop赋值给子组件的data对象的属性,再用watch监听prop,prop改变后即时更新data对象上的属性。
注意:对象深层嵌套watch中加deep:true。
缺点: watch深度监听,如果监听的对象数据比较多,性能有损耗。
其实从上面看,就可以总结出来子孙组件的生命周期。
- 如果子组件被v-if控制,且初始值为false,那么子组件一开始就不会被渲染,没有执行生命周期。
- 如果子组件被v-if控制,且初始值为true,或者子组件被v-show控制,不论初始值,或者,子组件在父组件的created,beforemount阶段显示值为true,那么,子组件总是会在父组件beforemount之后开始执行生命周期。
- 如果子组件在父组件的mounted阶段被渲染出来,这里会分两种情况,
-
- 第一种是v-show,因为v-show无论为true还是false,它都会存在dom节点,在父组件生命周期的beforemount之后,子组件已经开始执行自己的生命周期,直到父组件mounted阶段之后,这一轮生命周期已经完成,data已经有了,页面也已经渲染完毕。所以,当v-show的子组件变为true显示后,会触发父组件的更新函数。
- 第二种是v-if,因为v-if如果为false,是不会存在节点的,也就一开始不会显示,也就不会执行生命周期,所以,当v-if的子组件被渲染显示后,也会触发父组件的更新函数,不同的是,v-if子组件会在父组件的beforeupdate之后开始执行它的生命周期。
子组件修改父组件传递过来的props
【1】传递的数据类型是基础类型,子组件修改父组件传递过来的props,报错。
【2】传递的数据类型是引用类型,子组件将父组件传递过来的props作为本地数据,子组件修改父组件传递过来的props,父组件也会随之改变。
总结:
应用场景
在vue中父子组件是通过props传递数据的。通常有以下几种场景:
- 子组件展示父组件传递过来的props,一般是字符串
- 子组件接受父组件传递过来的props,作为本地数据使用
- 子组件接受父组件的传递过来的props,进行转换后展示(计算得到某个值)
- 子组件修改父组件传递过来的props
【1】子组件接受父组件传递过来的props仅作为展示时:无论什么数据类型,父组件改变,子组件随之改变。
对于这个场景,子组件只是展示,子组件随父组件变化是符合场景需求的
【2】子组件接受父组件传递过来的props作为本地数据时:prop为基础类型的值,父组件改变,子组件不变;prop为引用类型的值,父组件改变,子组件随之改变。
对于这个场景,子组件是在父组件的数据基础上进行加工,父组件数据改变,子组件是希望同步响应的,解决的方法:使用watch来监听prop。产生这个问题的原因是,因为生命周期的关系,组件的data属性,在reated,只在初始化的时候赋值了一次。
【3】子组件接受父组件传递过来的props作为计算属性时:无论什么数据类型,父组件改变,子组件随之改变。
【4】子组件修改父组件,基础类型的值报错;引用类型的值父组件也改变
注意 在父组件传递接口的数据给子组件时,一定要在子组件标签上加上v-if="传递的接口数据"
在父组件的created中发请求获取数据,通过prop传递给子组件。子组件在created或者mounted中拿父组件传递过来的数据 这样处理是有问题的。
在父组件调用接口传递数据给子组件时,接口响应显然是异步的。这会导致无论你在父组件哪个钩子发请求,在子组件哪个钩子接收数据。都是取不到的。当子组件的mounted都执行完之后,此时可能父组件的请求才返回数据。会导致,从父组件传递给子组件的数据是undefined。
解决方法1:
在渲染子组件的时候加上一个条件,data1是父组件调用接口返回的数据。当有数据的时候在去渲染子组件。这样就会形成天然的阻塞。在父组件的created中的请求返回数据后,才会执行子组件的created,mounted。最后执行父组件的mounted。
<div class="test">
<children v-if="data1" :data="data1" ></children>
</div>复制代码
解决方法2:
在子组件中 watch 监听,父组件获取到值,这个值就会变化,自然是可以监听到的
从父组件点击调用接口并显示子组件,子组件拿到数据并监听在watch中调用方法并显示
props和data响应式、watch初始化..谁先执行【Vue父子组件生命执行周期】
可以看到首先是进行初始化生命周期,初始化事件中心,初始化渲染等操作,在created之前,这也就说明了为什么在created的时候无法进行dom操作
4、 总结:
- 执行顺序beforeCreate ->inject -> Props -> Methods -> Data -> Computed -> Watch ->provide-> created
15)$nextTick理解
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
所以就衍生出了这个获取更新后的DOM的Vue方法。所以放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码;
Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后再下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。所以如果用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,过于耗费资源。
Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObserver,如果都不支持,就会采用setTimeout代替。
事实上,在执行this.showText= true;时,div仍然还是没有被创建出来,直到下一个Vue事件循环时,才开始创建。 $nextTick就是用来指导什么时候DOM更新完成的
常用:created中dom操作