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方法
update() { if(this.lazy) { this.dirty = true } else if(this.sync) { /*同步执行则run直接渲染视图*/ this.run() } else { /*异步则推送到观察者队列中,下一个tick时调用*/ queueWatcher(this) } } // queueWatcher函数 // 将观察者对象push进队列,并记录观察者的id // 如果对应的观察者已存在,则跳过,避免重复的计算 export function queueWatcher(watcher: Watcher) { const id = watcher.id if(!has[id]) { has[id] = true if(!flushing) { /*如果没有被flush掉,直接push到队列中即可*/ queue.push(watcher) } else { var i = queue.length - 1; while (i > index && queue[i].id > watcher.id) { i--; } queue.splice(i + 1, 0, watcher); } // queue the flush if(!wating) { wating = true nextTick(flushSchedulerQueue) } } }
所有同步任务在主线程上执行,形成一个执行栈;
主线程之外,还有一个任务队列,这个队列用于存放异步任务, 只要异步任务有了运行结果,就在"任务队列"之中放置一个事件;
执行栈上的同步任务执行完毕后,主线程会读取任务队列中的任务执行,对应的异步任务结束等待状态,进入执行栈,开始执行;
主线程不断重复以上操作,形成事件循环.
https://www.jianshu.com/p/19efc25e2a57
2.vuex
> 配置 { state:{}, mutations:{}, getters:{}, actions:{} } > 使用 // 获取数据 store中的getters类似于组件中的computed作用 store.state.** {{$store.state.count}} store.getters.xx {{$store.getters.money}} // 修改数据 store.commit(**) methods: { // increase为store中mutations this.$store.dispatch("increase"); } // 提交mutation store.dispatch(***) methods: { // increaseAsync为store中actions this.$store.dispatch("increaseAsync"); } > 帮助方法 快速的将组件中的数据与vuex中数据对应起来 mapState mapGetters mapMutations mapActions import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; computed: { // ...mapState 将对象解构 ,mapState本身返回如{count:this.$store.state.count},将其解构 ...mapState(["count"]), ...mapGetters(["money"]) }, methods: { ...mapMutations(['increase']), ...mapActions(['increaseAsync']), // 直接修改数据用commit,操作action用dispatch inc() { // this.$store.commit("increase"); // increase为mapMutations解构出来的 this.increase(); }, incAsync() { // this.$store.dispatch("increaseAsync"); // increaseAsync为mapActions解构出来的 this.increaseAsync(); }, } > 模块化 modules 根据业务功能拆分独立的store-js文件放置于store文件夹下的modules文件夹,并在store文件夹下的index.js文件中引入modules中的各个分store文件; // store/mudules/user.js const state = {} const mutations = {} const actions = {} export default { state, mutations, actions } // store/index.js import Vue from 'vue'; import Vuex from 'vuex'; import user from './modules/user'; Vue.use(Vuex); const store = new Vuex.Store({ modules:{ user}, // 定义全局getters getters:{ roles:state => state.user.roles, token: state => state.user.token, permission_routes: state => state.permission.routes } }) export default store
3.vue-router
(1)vue-router使用推荐
1.配置路由 const router = new Router({ // history模式http://localhost:8080/page2 // hash模式http://localhost:8080/#/page2 mode: 'history', base: process.env.BASE_URL, routes: [ // 默认路由,重定向到某个路由 // { // path:'/', // redirect:'/home' // }, { path: '/', name: 'home', component: Home }, { path:'/dashboard', name:'dashboard', component:() => import('./views/Dashboard.vue'), // 路由级别的路由守卫 beforeEnter(to, from, next){ <!-- 判断条件相应处理 --> }, children:[ // 孩子path用相对路径,去掉最前面的/ { path:'static', component:() => import('./views/Page1.vue'), // 给组件传静态参 props:{foo:'bare'} }, { path:'page1/:foo', //将route.params中参数作为属性传进去名字为foo name:'page1', component:() => import('./views/Page1.vue'), props:true }, { // 定义name,方便$router控制路由跳转及传参 path:'page2/:id/:msg', name:'page2', component:() => import('./views/Page2.vue'), }, // 路由中设置了prop,则页面中必须有props接收 { // 定义name,方便$router控制路由跳转及传参 path:'page22/:id/:msg', name:'page22', component:() => import('./views/Page2.vue'), props:func }, ] }, { path:'/login', name:'login', component:() => import('./views/Login.vue'), } ] }) export default router; main.js中将其路由对象传递给Vue的实例,options中加入router:router: new Vue({ router }) 2.安放路由出口 <router-view /> 3.导航链接 <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> 4.传参 声明式的导航(<router-linkto='路由地址'></router-link>)和编程式的导航 (router.push(...))都可以传参 ps:$router/$route $router是路由操作对象,对要跳转的路由进行编写,而使用$route我们来从浏览器中读 取路由参数,即$router只写要跳转的路由,$route 只读(参数的获取) (1)params // params进行路由传参的时候只能由name引入 this.$router.push({ name: "about", params: { id: 11} }); this.$route.params.id (2)query // query传参的时候,以path,name引入都是OK的 this.$router.push({ path: '/describe', query: { id: id } }) next('/login?id='+to.path); this.$route.query.id (1)params //*.vue params进行路由传参的时候只能由name引入 <router-link v-bind:to="{name: 'cart',params: {id: 123}}"> </router-link> //index.js中的路由配置 { path: '/cart/:id', name: 'cart', component: Cart, } //Cart.vue 获取路由传参 <template> <div> <span>{{ $route.params.id }}</span> </div> </template> // 页面显示路径localhost:8088/#/cart/123 (2)query //query传参使用name进行引入 <router-link v-bind:to="{name: 'cart', query: {id: 123}}"> </router-link> //获取浏览器中路由的参数 <span>{{ $route.query.id }}</span> //query传参使用path进行引入 <router-link v-bind:to="{path: 'cart', query: {id: 123}}"> </router-link> //获取浏览器中路由的参数 <span>{{ $route.query.id }}< // 页面中显示路径 localhost:8088/#/cart?id=123
(2)vue权限控制
(****)
vue构造的时候会在data(和一些别的字段)上建立Observer对象, getter和setter被做了拦截, getter触发依赖收集, setter触发notify.
Watcher, 注册watch的时候会调用一次watch的对象, 这样触发了watch对象的getter, 把依赖收集到当前Watcher的deps里, 当任何dep的setter被触发就会notify当前Watcher来调用Watcher的update()方法.
PS :感谢&参考