Vue的生命周期
beforeCreate
实例组件刚创建,元素DOM和数据都还没有初始化,暂时不知道能在这个周期里面进行生命操作。created
数据data已经初始化完成,方法也已经可以调用,但是DOM为渲染。在这个周期里面如果进行请求是可以改变数据并渲染,由于DOM未挂载,请求过多或者占用时间过长会导致页面线上空白。beforeMount
DOM未完成挂载,数据也初始化完成,但是数据的双向绑定还是显示{{}},这是因为Vue采用了Virtual DOM(虚拟Dom)技术。先占住了一个坑。mounted
数据和DOM都完成挂载,在上一个周期占位的数据把值给渲染进去。一般请求会放在这个地方,因为这边请求改变数据之后刚好能渲染。beforeUpdate
只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,当你请求赋值一个数据的时候会执行这个周期,如果没有数据改变不执行。updated
只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的。beforeUpdate和updated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和容易死循环。beforeDestroy
这个周期是在组件销毁之前执行,在我项目开发中,觉得这个其实有点类似路由钩子beforeRouterLeave,都是在路由离开的时候执行,只不过beforeDestroy无法阻止路由跳转,但是可以做一些路由离开的时候操作,因为这个周期里面还可以使用data和method。比如一个倒计时组件,如果在路由跳转的时候没有清除,这个定时器还是在的,这时候就可以在这个里面清除计时器。Destroyed
说实在的,我还真的不知道这个周期跟beforeDestroy有什么区别,我在这个周期里面调用data的数据和methods的方法都能调用,所以我会觉得跟beforeDestroy是一样的。
数据双向绑定
Object.defineProperty
是ES5新增的一个API,其作用是给对象的属性增加更多的控制
Object.defineProperty(obj, prop, descriptor)
参数 obj: 需要定义属性的对象(目标对象)
prop: 需被定义或修改的属性名(对象上的属性或者方法)
对于setter和getter,我的理解是它们是一对勾子(hook)函数,当你对一个对象的某个属性赋值时,则会自动调用相应的setert函数;而当获取属性时,则调用getter函数。这也是实现双向数据绑定的关键。
代码实现:
<body>
<div id="app">
<input type="text" id="txt">
<p id="show-txt"></p>
</div>
</body>
<script>
var obj = {}
Object.defineProperty(obj, 'txt', {
set: function (val) {
document.getElementById('txt').value = val
document.getElementById('show-txt').innerHTML = val
}
})
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value
})
</script>
Vue父子组件传递参数
第一种就是普通的
// parent
<template>
<child name="son" @changeValue="changeValue"></child>
</template>
<script>
module.exports = {
methods: {
changeValue(val) {
console.log(val)
}
}
}
</script>
// child
<script>
module.exports = {
props:{
name: {
required: true,
type: String
}
},
data: {},
methods: {
changeValue() {
this.$emit('changeValue', '123')
}
}
}
</script>
第二种是我用的比较多的.sync
,因为Vue规定prop是不能直接修改的,默认是传值,类似形参
// parent
<template>
<child name.sync="son"></child>
</template>
<script>
module.exports = {
methods: {
changeValue(val) {
console.log(val)
}
}
}
</script>
// child
<script>
module.exports = {
props:{
name: {
required: true,
type: String
}
},
data: {},
methods: {
changeValue() {
this.$emit('update:name', '123')
}
}
}
</script>
Vue路由传递参数方法
- 通过name传值
routes: [
{
path: '/Message',
name: 'Message',
component: resolve => require(['../components/page/Message.vue'], resolve)
}
]
// vue页面中
this.$router.push({name: 'Message', params: {pa: aa})
// 接收页面
console.log(this.$route.params.pa)
- 通过
<router-link>
<router-link :to="{name:'hi1',params:{username:'jspang'}}">Hi页面1</router-link>
{path:'/hi1',name:'hi1',component:Hi1}
{{$route.params.username}}
- 利用url传递参数
{
path:'/params/:newsId/:newsTitle',
component:Params
}
<router-link to="/params/198/jspang website is very good">params</router-link>
Vue自定义组件
Vue自定义指令
// 和自定义过滤器一样,我们这里定义的是全局指令
Vue.directive('focus',{
inserted(el) {
el.focus()
}
})
<div id='app'>
<input type="text">
<input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点">
</div>
这里放了两个 input ,但是后面的 input 才使用了我们的自定义 v-focus 指令,所以看到了是后面那个文本框获取了焦点,而不是前面一个。
看到上面这个例子,可以总结几点
- 使用 Vue.directive() 来新建一个全局指令,(指令使用在HTML元素属性上的)
- Vue.directive('focus') 第一个参数focus是指令名,指令名在声明的时候,不需要加 v-
- 在使用指令的HTML元素上,<input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点"/> 我们需要加上 v-.
- Vue.directive('focus',{}) 第二个参数是一个对象,对象内部有个 inserted() 的函数,函数有 el 这个参数.
- el 这个参数表示了绑定这个指令的 DOM元素,在这里就是后面那个有 placeholder 的 input
- el 就等价于 document.getElementById('el.id')....可以利用 $(el) 无缝连接 jQuery
下面说下指令的生命周期
Vue.directive('gqs',{
bind() {
// 当指令绑定到 HTML 元素上时触发.**只调用一次**
console.log('bind triggerd')
},
inserted() {
// 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
console.log('inserted triggerd')
},
update() {
// 所在组件的`VNode`更新时调用.
console.log('updated triggerd')
},
componentUpdated() {
// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
console.log('componentUpdated triggerd')
},
unbind() {
// 只调用一次,指令与元素解绑时调用.
console.log('unbind triggerd')
}
})
vuex工作原理和组成
首先要说的是,vuex作为一个插件,在vue中使用,就会调用他的install方
// src/store.js
export function install (_Vue) {
if (Vue && _Vue === Vue) {
return
}
Vue = _Vue
applyMixin(Vue)
}
其代码比较简单,调用了一下applyMixin方法,该方法主要作用就是在所有组件的beforeCreate生命周期注入了设置this.$store这样一个对象
下面来看一下他的构造函数
// src/store.js
constructor (options = {}) {
const {
plugins = [],
strict = false
} = options
// store internal state
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
// 重点方法 ,重置VM
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
除了一堆初始化外,我们注意到了这样一行代码resetStoreVM(this, state)
他就是整个vuex的关键
// src/store.js
function resetStoreVM (store, state, hot) {
// 省略无关代码
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
去除了一些无关代码后我们发现,其本质就是将我们传入的state作为一个隐藏的vue组件的data,也就是说,我们的commit操作,本质上其实是修改这个组件的data值,结合上文的computed,修改被defineReactive代理的对象值后,会将其收集到的依赖的watcher中的dirty设置为true,等到下一次访问该watcher中的值后重新获取最新值。
这样就能解释了为什么vuex中的state的对象属性必须提前定义好,如果该state中途增加一个属性,因为该属性没有被defineReactive,所以其依赖系统没有检测到,自然不能更新。
由上所说,我们可以得知store._vm.$data.$$state === store.state, 我们可以在任何含有vuex框架的工程验证这一点。
后一句话结束vuex工作原理,vuex中的store本质就是没有template的隐藏着的vue组件
Vue-Router的原理
使用
const routes = [
{
path: '/',
redirect: '/recommend'
},
{
path: '/recommend',
component: () => import('../components/recommend/view.vue')
},
{
path: '/singer',
component: () => import('../components/singer/view.vue')
},
{
path: '/rank',
component: () => import('../components/rank/view.vue')
},
{
path: '/search',
component: () => import('../components/search/view.vue')
}
]
export default routes
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
export default new Router({
// mode: 'history',
routes
})
vue-router通过hash与History interface两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式
- hash ---- 利用URL中的hash(“#”)
- 利用History interface在 HTML5中新增的方法
那么,我们要选择用哪种方式呢?
在vue-router中,它提供mode参数来决定采用哪一种方式,选择流程如下:
mode 参数:
默认hash
history 注:如果浏览器不支持history新特性,则采用hash方式
如果不在浏览器环境则使用abstract(node环境下)
mode: 'hash'
http://localhost:8080/#/recommend
mode: 'history'
http://localhost:8080/recommend
// 根据mode确定history实际的类并实例化
// 根据mode确定history实际的类并实例化
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
HashHistory
HashHistory真是身怀绝技,会很多东西。特别是替换路由特别厉害。还可以通过不同的方式,一个是push
,一个是replace
.
1 $router.push() //调用方法
2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
3 History.transitionTo() //监测更新,更新则调用History.updateRoute()
4 History.updateRoute() //更新路由
5 {app._route= route} //替换当前app路由
6 vm.render() //更新视图
HTML5History
History interface是浏览器历史记录栈提供的接口,通过back(), forward(), go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。
从HTML5开始,History interface有进一步修炼:pushState(), replaceState() 这下不仅是读取了,还可以对浏览器历史记录栈进行修改
vue的seo问题
由于传统的搜索引擎只会从 HTML 中抓取数据,导致前端渲染的页面无法被抓取。前端渲染常使用的 SPA 会把所有 JS 整体打包,无法忽视的问题就是文件太大,导致渲染前等待很长时间。特别是网速差的时候,让用户等待白屏结束并非一个很好的体验。
解决方案:
- vue ssr
- nuxt.js
- prerender-spa-plugin插件
由于项目只是改善少数页面的seo,所以使用预渲染方式,预渲染插件使用vue官方推荐的prerender-spa-plugin
。
生命周期内create和mounted的区别
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
其实两者比较好理解,通常created使用的次数多,而mounted通常是在一些插件的使用或者组件的使用中进行操作,比如插件chart.js的使用: var ctx = document.getElementById(ID);通常会有这一步,而如果你写入组件中,你会发现在created中无法对chart进行一些初始化配置,一定要等这个html渲染完后才可以进行,那么mounted就是不二之选。
Vue实现登陆拦截
Step1: requireAuth属性
requireAuth属性作用是表明该路由是否需要登录验证,在进行全局拦截时,我们将通过该属性判断路由的跳转,该属性包含在meta属性中:
routes = [
{
name: 'detail',
path: '/detail',
meta: {
requireAuth: true
}
},
{
name: 'login',
path: '/login'
}
]
Step2: router.beforeEach
router.beforeEach((from, to, next) => {
if (to.meta.requireAuth) { // 判断跳转的路由是否需要登录
if (store.state.token) { // vuex.state判断token是否存在
next() // 已登录
} else {
next({
path: '/login',
query: {redirect: to.fullPath} // 将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
} else {
next()
}
})