vue实践推荐
1.vue
(1)组件
props: { status: { type: String, required: true, validator: function (value) { // 校验方法 return *** } } }
props: { greetingText: String } <WelcomeMessage greeting-text="hi"/>
<WelcomeMessage greeting-text="hi" name="hh" />
(2)js文件-scss文件
(3)watch
watch:{ searchText:{ handler:'getList', immediate:true, <!-- 是否深度监听,如对象 --> deep:true } }
监听属性主要是监听某个值发生变化后,对新值去进行逻辑处理。
Watcher 初始化watch vm.$watch
Wacher充当一个中介的角色,数据发生变化的时候通知它,它再通知其他地方;
(4)data 响应式数据
data () { return { foo: 'bar' } } vm.$set( target, key, value ) Vue.set(vm.obj,'k1','v1') this.$set(this.obj,'k1','v1') this.obj = Object.assign({}, this.obj) this.obj = Object.assign({}, this.obj,{'k1','v1'})
this.$set(this.foo,'aProperty','aavalue') Vue.set(obj, 'newProp', 123) 或 state.obj = {...state.obj, newProp:123}
(5)computed计算属性
computed:{ newPrice:function(){ return this.price='¥' + this.price + '元'; } } computed:{ reverseNews:function(){ return this.newsList.reverse(); } } 把复杂计算属性分割为尽可能多的更简单的属性; computed: { basePrice: function () { return this.manufactureCost / (1 - this.profitMargin) }, discount: function () { return this.basePrice * (this.discountPercent || 0) }, finalPrice: function () { return this.basePrice - this.discount } }
(6)指令
<ul> <li v-for="todo in todos" :key="todo.id"> {{ todo.text }} </li> </ul>
<div v-if="error" key="search-status"> {{ error }} </div> <div v-else key="search-results"> {{ results }} </div>
(7)样式
(8)渲染
(9)组件通信
<input :value="value" v-bind="$attrs" @input="$emit('input', $event.target.value)" >
(10)路由切换组件不变的问题
当页面切换同一个路由但不同参数的地址时,组件的生命周期钩子并不会重新触发:vue-router会识别出两个路由使用同一个组件从而进行复用,而不会重新构建组件,因此组件生命周期钩子也不会被触发。想要重新触发可以选择:
a.路由导航守卫beforeRouteUpdate,可以在当前路由改变且组件被复用时调用,只需要将每次切换路由时需要处理的逻辑放在该守卫里即可如获取新数据更新状态并渲染视图。
b.观察$route对象变化,,可能会导致依赖追踪的内存消耗
watch:{ '$route'(to,from){ // 对路由变化作出响应 } }
ps:如果共用组件页面有共用信息,可以只观察变化的那部分,而不用整个页面信息都重刷
watch:{ '$route.query.id'(){ // 请求个人信息 }, '$route.query.page'(){ // 请求不同页面列表数据 } }
c.为router-view组件添加标识属性key,利用虚拟dom在渲染时通过key来对比两个节点是否相同的原理;可以使得每次切换路由时key都不一样,让虚拟dom认为router-view组件是一个新节点,从而销毁组件再重建组件。但浪费性能。
(11) v-model 双向绑定
https://juejin.im/post/5d70aed76fb9a06b04721ec3
(12)keep-alive
Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构;它将满足条件的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染,还未缓存过则进行缓存。
生命周期函数
1. activated
在 keep-alive 组件激活时调用
该钩子函数在服务器端渲染期间不被调用
2. deactivated
在 keep-alive 组件停用时调用
该钩子在服务器端渲染期间不被调用
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
https://blog.csdn.net/fu983531588/article/details/90321827
<keep-alive> <component v-bind:is="currentTabComponent" class="tab"></component> </keep-alive>
https://cn.vuejs.org/v2/api/#keep-alive
<keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> -------------------------------------------------------- export default { data() { return {}; }, mounted() {}, methods: {}, beforeRouteLeave(to, from, next) { if (to.path == "/index") { to.meta.keepAlive = true; } else { to.meta.keepAlive = false; } next(); } }; // keepalive组件选项 var KeepAlive = { name: 'keep-alive', // 抽象组件的标志 abstract: true, // keep-alive允许使用的props props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created: function created () { // 缓存组件vnode this.cache = Object.create(null); // 缓存组件名 this.keys = []; }, destroyed: function destroyed () { // destroyed钩子中销毁所有cache中的组件实例 for (var key in this.cache) { pruneCacheEntry(this.cache, key, this.keys); } }, mounted: function mounted () { var this$1 = this; // 动态include和exclude // 对include exclue的监听 this.$watch('include', function (val) { pruneCache(this$1, function (name) { return matches(val, name); }); }); this.$watch('exclude', function (val) { pruneCache(this$1, function (name) { return !matches(val, name); }); }); }, // keep-alive的渲染函数 render: function render () { // 拿到keep-alive下插槽的值 var slot = this.$slots.default; // 第一个vnode节点 var vnode = getFirstComponentChild(slot); // 拿到第一个组件实例 var componentOptions = vnode && vnode.componentOptions; // keep-alive的第一个子组件实例存在 if (componentOptions) { // check pattern //拿到第一个vnode节点的name var name = getComponentName(componentOptions); var ref = this; var include = ref.include; var exclude = ref.exclude; // 通过判断子组件是否满足缓存匹配 if ((include && (!name || !matches(include, name))) ||(exclude && name && matches(exclude, name))) { return vnode } var ref$1 = this; var cache = ref$1.cache; var keys = ref$1.keys; var key = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '') : vnode.key; // 再次命中缓存 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } else { // 初次渲染时,将vnode缓存 cache[key] = vnode; keys.push(key); // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } // 为缓存组件打上标志 vnode.data.keepAlive = true; } // 将渲染的vnode返回 return vnode || (slot && slot[0]) } };
https://juejin.im/post/5d8871c851882509630338c4
https://juejin.im/post/5da42574f265da5b991d6173
https://segmentfault.com/q/1010000011537852
https://segmentfault.com/a/1190000011978825
https://github.com/answershuto/learnVue/blob/master/vue-src/core/components/keep-alive.js
(13) vue初始化
https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA
vue整体生命周期分为4阶段:初始化(new Vue() 到created之前) / 模板编译(created到beforeMount之前) / 挂载(beforeMount到mounted) / 卸载(beforeDestroy到destroyed)
初始化目的:在vue.js实例上初始化一些属性-事件-响应式数据如props-methods-data-computed-watch-provide-inject等
https://blog.csdn.net/a419419/article/details/90764860
https://blog.csdn.net/qq_20143169/article/details/83745727
(14)vue 同步更新 异步更新
异步更新队列指的是当状态发生变化时,Vue异步执行DOM更新。
Vue的dom更新是异步的,当数据发生变化,vue并不是里面去更新dom,而是开启一个队列。
Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run;数据变化时,根据响应式触发setter->Dep->Watcher->update->patch;
比如我们调用一个方法,同时涉及多个数据的操作改变,vue会把这一些列操作推入到一个队列中,相当于JavaScript的同步任务,在执行过程中可能会出现一些产生任务队列的异步任务,比如定时器、回调等。
在vue里面任务队列也叫事件循环队列。我们都知道JavaScript是循环往复的执行任务队列。Vue也一样,在一个同步任务过程中是不会去更新渲染视图,而是在同步任务(事件循环队列)执行完毕之后,在主线程的同步执行完毕,读取任务队列时更新视图。
需要先渲染数据然后操作dom,这时候就要使用vue提供的nextTick函数(当你需要数据先渲染,然后去操作渲染完成之后的dom,要把操作dom的逻辑写在这个函数里面):Vue.$nextTick(callback);
将状态改变之后想获取更新后的DOM,往往我们获取到的DOM是更新前的旧DOM,我们需要使用vm.$nextTick方法异步获取DOM;
data() { return { message: '数据更新前no' } }, edit(){ this.message = '数据更新后have'; console.log(document.getElementById('message').innerHTML); //数据更新前no this.$nextTick(() => { // 这个函数就是有了数据之后,渲染完成之后会执行,也就是说当你需要数据先渲染, // 然后去操作渲染完成之后的dom,要把操作dom的逻辑写在这个函数里面 console.log(document.getElementById('message').innerHTML); // 数据更新后have1 }); this.message = '数据更新后have1'; }
// 多次修改了状态,但其实Vue只会渲染一次
Vue优先将渲染操作推迟到本轮事件循环的最后,如果执行环境不支持会降级到下一轮;Vue的变化侦测机制决定了它必然会在每次状态发生变化时都会发出渲染的信号,但Vue会在收到信号之后检查队列中是否已经存在这个任务,保证队列中不会有重复。如果队列中不存在则将渲染操作添加到队列中;之后通过异步的方式延迟执行队列中的所有渲染的操作并清空队列,当同一轮事件循环中反复修改状态时,并不会反复向队列中添加相同的渲染操作;在使用Vue时,修改状态后更新DOM都是异步的。
当某个响应式数据发生变化的时候,它的setter函数就会通知闭包中的Dep,Dep则会触发对应的Watcher对象的update方法
1.vue
(1)组件
props: { status: { type: String, required: true, validator: function (value) { // 校验方法 return *** } } }
props: { greetingText: String } <WelcomeMessage greeting-text="hi"/>
<WelcomeMessage greeting-text="hi" name="hh" />
(2)js文件-scss文件
(3)watch
watch:{ searchText:{ handler:'getList', immediate:true, <!-- 是否深度监听,如对象 --> deep:true } }
监听属性主要是监听某个值发生变化后,对新值去进行逻辑处理。
Watcher 初始化watch vm.$watch
Wacher充当一个中介的角色,数据发生变化的时候通知它,它再通知其他地方;
(4)data 响应式数据
data () { return { foo: 'bar' } } vm.$set( target, key, value ) Vue.set(vm.obj,'k1','v1') this.$set(this.obj,'k1','v1') this.obj = Object.assign({}, this.obj) this.obj = Object.assign({}, this.obj,{'k1','v1'})
this.$set(this.foo,'aProperty','aavalue') Vue.set(obj, 'newProp', 123) 或 state.obj = {...state.obj, newProp:123}
(5)computed计算属性
computed:{ newPrice:function(){ return this.price='¥' + this.price + '元'; } } computed:{ reverseNews:function(){ return this.newsList.reverse(); } } 把复杂计算属性分割为尽可能多的更简单的属性; computed: { basePrice: function () { return this.manufactureCost / (1 - this.profitMargin) }, discount: function () { return this.basePrice * (this.discountPercent || 0) }, finalPrice: function () { return this.basePrice - this.discount } }
(6)指令
<ul> <li v-for="todo in todos" :key="todo.id"> {{ todo.text }} </li> </ul>
<div v-if="error" key="search-status"> {{ error }} </div> <div v-else key="search-results"> {{ results }} </div>
(7)样式
(8)渲染
(9)组件通信
<input :value="value" v-bind="$attrs" @input="$emit('input', $event.target.value)" >
(10)路由切换组件不变的问题
当页面切换同一个路由但不同参数的地址时,组件的生命周期钩子并不会重新触发:vue-router会识别出两个路由使用同一个组件从而进行复用,而不会重新构建组件,因此组件生命周期钩子也不会被触发。想要重新触发可以选择:
a.路由导航守卫beforeRouteUpdate,可以在当前路由改变且组件被复用时调用,只需要将每次切换路由时需要处理的逻辑放在该守卫里即可如获取新数据更新状态并渲染视图。
b.观察$route对象变化,,可能会导致依赖追踪的内存消耗
watch:{ '$route'(to,from){ // 对路由变化作出响应 } }
ps:如果共用组件页面有共用信息,可以只观察变化的那部分,而不用整个页面信息都重刷
watch:{ '$route.query.id'(){ // 请求个人信息 }, '$route.query.page'(){ // 请求不同页面列表数据 } }
c.为router-view组件添加标识属性key,利用虚拟dom在渲染时通过key来对比两个节点是否相同的原理;可以使得每次切换路由时key都不一样,让虚拟dom认为router-view组件是一个新节点,从而销毁组件再重建组件。但浪费性能。
(11) v-model 双向绑定
https://juejin.im/post/5d70aed76fb9a06b04721ec3
(12)keep-alive
Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构;它将满足条件的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染,还未缓存过则进行缓存。
生命周期函数
1. activated
在 keep-alive 组件激活时调用
该钩子函数在服务器端渲染期间不被调用
2. deactivated
在 keep-alive 组件停用时调用
该钩子在服务器端渲染期间不被调用
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
https://blog.csdn.net/fu983531588/article/details/90321827
<keep-alive> <component v-bind:is="currentTabComponent" class="tab"></component> </keep-alive>
https://cn.vuejs.org/v2/api/#keep-alive
<keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> -------------------------------------------------------- export default { data() { return {}; }, mounted() {}, methods: {}, beforeRouteLeave(to, from, next) { if (to.path == "/index") { to.meta.keepAlive = true; } else { to.meta.keepAlive = false; } next(); } }; // keepalive组件选项 var KeepAlive = { name: 'keep-alive', // 抽象组件的标志 abstract: true, // keep-alive允许使用的props props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created: function created () { // 缓存组件vnode this.cache = Object.create(null); // 缓存组件名 this.keys = []; }, destroyed: function destroyed () { // destroyed钩子中销毁所有cache中的组件实例 for (var key in this.cache) { pruneCacheEntry(this.cache, key, this.keys); } }, mounted: function mounted () { var this$1 = this; // 动态include和exclude // 对include exclue的监听 this.$watch('include', function (val) { pruneCache(this$1, function (name) { return matches(val, name); }); }); this.$watch('exclude', function (val) { pruneCache(this$1, function (name) { return !matches(val, name); }); }); }, // keep-alive的渲染函数 render: function render () { // 拿到keep-alive下插槽的值 var slot = this.$slots.default; // 第一个vnode节点 var vnode = getFirstComponentChild(slot); // 拿到第一个组件实例 var componentOptions = vnode && vnode.componentOptions; // keep-alive的第一个子组件实例存在 if (componentOptions) { // check pattern //拿到第一个vnode节点的name var name = getComponentName(componentOptions); var ref = this; var include = ref.include; var exclude = ref.exclude; // 通过判断子组件是否满足缓存匹配 if ((include && (!name || !matches(include, name))) ||(exclude && name && matches(exclude, name))) { return vnode } var ref$1 = this; var cache = ref$1.cache; var keys = ref$1.keys; var key = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '') : vnode.key; // 再次命中缓存 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } else { // 初次渲染时,将vnode缓存 cache[key] = vnode; keys.push(key); // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } // 为缓存组件打上标志 vnode.data.keepAlive = true; } // 将渲染的vnode返回 return vnode || (slot && slot[0]) } };
https://juejin.im/post/5d8871c851882509630338c4
https://juejin.im/post/5da42574f265da5b991d6173
https://segmentfault.com/q/1010000011537852
https://segmentfault.com/a/1190000011978825
https://github.com/answershuto/learnVue/blob/master/vue-src/core/components/keep-alive.js
(13) vue初始化
https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA
vue整体生命周期分为4阶段:初始化(new Vue() 到created之前) / 模板编译(created到beforeMount之前) / 挂载(beforeMount到mounted) / 卸载(beforeDestroy到destroyed)
初始化目的:在vue.js实例上初始化一些属性-事件-响应式数据如props-methods-data-computed-watch-provide-inject等
https://blog.csdn.net/a419419/article/details/90764860
https://blog.csdn.net/qq_20143169/article/details/83745727
(14)vue 同步更新 异步更新
异步更新队列指的是当状态发生变化时,Vue异步执行DOM更新。
Vue的dom更新是异步的,当数据发生变化,vue并不是里面去更新dom,而是开启一个队列。
Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run;数据变化时,根据响应式触发setter->Dep->Watcher->update->patch;
比如我们调用一个方法,同时涉及多个数据的操作改变,vue会把这一些列操作推入到一个队列中,相当于JavaScript的同步任务,在执行过程中可能会出现一些产生任务队列的异步任务,比如定时器、回调等。
在vue里面任务队列也叫事件循环队列。我们都知道JavaScript是循环往复的执行任务队列。Vue也一样,在一个同步任务过程中是不会去更新渲染视图,而是在同步任务(事件循环队列)执行完毕之后,在主线程的同步执行完毕,读取任务队列时更新视图。
需要先渲染数据然后操作dom,这时候就要使用vue提供的nextTick函数(当你需要数据先渲染,然后去操作渲染完成之后的dom,要把操作dom的逻辑写在这个函数里面):Vue.$nextTick(callback);
将状态改变之后想获取更新后的DOM,往往我们获取到的DOM是更新前的旧DOM,我们需要使用vm.$nextTick方法异步获取DOM;
data() { return { message: '数据更新前no' } }, edit(){ this.message = '数据更新后have'; console.log(document.getElementById('message').innerHTML); //数据更新前no this.$nextTick(() => { // 这个函数就是有了数据之后,渲染完成之后会执行,也就是说当你需要数据先渲染, // 然后去操作渲染完成之后的dom,要把操作dom的逻辑写在这个函数里面 console.log(document.getElementById('message').innerHTML); // 数据更新后have1 }); this.message = '数据更新后have1'; }
// 多次修改了状态,但其实Vue只会渲染一次
Vue优先将渲染操作推迟到本轮事件循环的最后,如果执行环境不支持会降级到下一轮;Vue的变化侦测机制决定了它必然会在每次状态发生变化时都会发出渲染的信号,但Vue会在收到信号之后检查队列中是否已经存在这个任务,保证队列中不会有重复。如果队列中不存在则将渲染操作添加到队列中;之后通过异步的方式延迟执行队列中的所有渲染的操作并清空队列,当同一轮事件循环中反复修改状态时,并不会反复向队列中添加相同的渲染操作;在使用Vue时,修改状态后更新DOM都是异步的。
当某个响应式数据发生变化的时候,它的setter函数就会通知闭包中的Dep,Dep则会触发对应的Watcher对象的update方法