Vue的生命周期
beforeCreate
實(shí)例組件剛創(chuàng)建钧舌,元素DOM和數(shù)據(jù)都還沒(méi)有初始化担汤,暫時(shí)不知道能在這個(gè)周期里面進(jìn)行生命操作。created
數(shù)據(jù)data已經(jīng)初始化完成延刘,方法也已經(jīng)可以調(diào)用漫试,但是DOM為渲染。在這個(gè)周期里面如果進(jìn)行請(qǐng)求是可以改變數(shù)據(jù)并渲染碘赖,由于DOM未掛載驾荣,請(qǐng)求過(guò)多或者占用時(shí)間過(guò)長(zhǎng)會(huì)導(dǎo)致頁(yè)面線上空白。beforeMount
DOM未完成掛載普泡,數(shù)據(jù)也初始化完成播掷,但是數(shù)據(jù)的雙向綁定還是顯示{{}},這是因?yàn)閂ue采用了Virtual DOM(虛擬Dom)技術(shù)撼班。先占住了一個(gè)坑歧匈。mounted
數(shù)據(jù)和DOM都完成掛載,在上一個(gè)周期占位的數(shù)據(jù)把值給渲染進(jìn)去砰嘁。一般請(qǐng)求會(huì)放在這個(gè)地方件炉,因?yàn)檫@邊請(qǐng)求改變數(shù)據(jù)之后剛好能渲染。beforeUpdate
只要是頁(yè)面數(shù)據(jù)改變了都會(huì)觸發(fā)矮湘,數(shù)據(jù)更新之前斟冕,頁(yè)面數(shù)據(jù)還是原來(lái)的數(shù)據(jù),當(dāng)你請(qǐng)求賦值一個(gè)數(shù)據(jù)的時(shí)候會(huì)執(zhí)行這個(gè)周期缅阳,如果沒(méi)有數(shù)據(jù)改變不執(zhí)行磕蛇。updated
只要是頁(yè)面數(shù)據(jù)改變了都會(huì)觸發(fā),數(shù)據(jù)更新完畢,頁(yè)面的數(shù)據(jù)是更新完成的秀撇。beforeUpdate和updated要謹(jǐn)慎使用超棺,因?yàn)轫?yè)面更新數(shù)據(jù)的時(shí)候都會(huì)觸發(fā),在這里操作數(shù)據(jù)很影響性能和容易死循環(huán)呵燕。beforeDestroy
這個(gè)周期是在組件銷毀之前執(zhí)行棠绘,在我項(xiàng)目開發(fā)中,覺(jué)得這個(gè)其實(shí)有點(diǎn)類似路由鉤子beforeRouterLeave,都是在路由離開的時(shí)候執(zhí)行虏等,只不過(guò)beforeDestroy無(wú)法阻止路由跳轉(zhuǎn)弄唧,但是可以做一些路由離開的時(shí)候操作,因?yàn)檫@個(gè)周期里面還可以使用data和method霍衫。比如一個(gè)倒計(jì)時(shí)組件,如果在路由跳轉(zhuǎn)的時(shí)候沒(méi)有清除侯养,這個(gè)定時(shí)器還是在的敦跌,這時(shí)候就可以在這個(gè)里面清除計(jì)時(shí)器。Destroyed
說(shuō)實(shí)在的逛揩,我還真的不知道這個(gè)周期跟beforeDestroy有什么區(qū)別柠傍,我在這個(gè)周期里面調(diào)用data的數(shù)據(jù)和methods的方法都能調(diào)用,所以我會(huì)覺(jué)得跟beforeDestroy是一樣的辩稽。
數(shù)據(jù)雙向綁定
Object.defineProperty
是ES5新增的一個(gè)API惧笛,其作用是給對(duì)象的屬性增加更多的控制
Object.defineProperty(obj, prop, descriptor)
參數(shù) obj: 需要定義屬性的對(duì)象(目標(biāo)對(duì)象)
prop: 需被定義或修改的屬性名(對(duì)象上的屬性或者方法)
對(duì)于setter和getter,我的理解是它們是一對(duì)勾子(hook)函數(shù)逞泄,當(dāng)你對(duì)一個(gè)對(duì)象的某個(gè)屬性賦值時(shí)患整,則會(huì)自動(dòng)調(diào)用相應(yīng)的setert函數(shù);而當(dāng)獲取屬性時(shí)喷众,則調(diào)用getter函數(shù)各谚。這也是實(shí)現(xiàn)雙向數(shù)據(jù)綁定的關(guān)鍵。
代碼實(shí)現(xiàn):
<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父子組件傳遞參數(shù)
第一種就是普通的
// 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
到千,因?yàn)閂ue規(guī)定prop是不能直接修改的昌渤,默認(rèn)是傳值,類似形參
// 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路由傳遞參數(shù)方法
- 通過(guò)name傳值
routes: [
{
path: '/Message',
name: 'Message',
component: resolve => require(['../components/page/Message.vue'], resolve)
}
]
// vue頁(yè)面中
this.$router.push({name: 'Message', params: {pa: aa})
// 接收頁(yè)面
console.log(this.$route.params.pa)
- 通過(guò)
<router-link>
<router-link :to="{name:'hi1',params:{username:'jspang'}}">Hi頁(yè)面1</router-link>
{path:'/hi1',name:'hi1',component:Hi1}
{{$route.params.username}}
- 利用url傳遞參數(shù)
{
path:'/params/:newsId/:newsTitle',
component:Params
}
<router-link to="/params/198/jspang website is very good">params</router-link>
Vue自定義組件
Vue自定義指令
// 和自定義過(guò)濾器一樣,我們這里定義的是全局指令
Vue.directive('focus',{
inserted(el) {
el.focus()
}
})
<div id='app'>
<input type="text">
<input type="text" v-focus placeholder="我有v-focus,所以,我獲取了焦點(diǎn)">
</div>
這里放了兩個(gè) input ,但是后面的 input 才使用了我們的自定義 v-focus 指令,所以看到了是后面那個(gè)文本框獲取了焦點(diǎn),而不是前面一個(gè)憔四。
看到上面這個(gè)例子膀息,可以總結(jié)幾點(diǎn)
- 使用 Vue.directive() 來(lái)新建一個(gè)全局指令,(指令使用在HTML元素屬性上的)
- Vue.directive('focus') 第一個(gè)參數(shù)focus是指令名,指令名在聲明的時(shí)候,不需要加 v-
- 在使用指令的HTML元素上,<input type="text" v-focus placeholder="我有v-focus,所以,我獲取了焦點(diǎn)"/> 我們需要加上 v-.
- Vue.directive('focus',{}) 第二個(gè)參數(shù)是一個(gè)對(duì)象,對(duì)象內(nèi)部有個(gè) inserted() 的函數(shù),函數(shù)有 el 這個(gè)參數(shù).
- el 這個(gè)參數(shù)表示了綁定這個(gè)指令的 DOM元素,在這里就是后面那個(gè)有 placeholder 的 input
- el 就等價(jià)于 document.getElementById('el.id')....可以利用 $(el) 無(wú)縫連接 jQuery
下面說(shuō)下指令的生命周期
Vue.directive('gqs',{
bind() {
// 當(dāng)指令綁定到 HTML 元素上時(shí)觸發(fā).**只調(diào)用一次**
console.log('bind triggerd')
},
inserted() {
// 當(dāng)綁定了指令的這個(gè)HTML元素插入到父元素上時(shí)觸發(fā)(在這里父元素是 `div#app`)**.但不保證,父元素已經(jīng)插入了 DOM 文檔.**
console.log('inserted triggerd')
},
update() {
// 所在組件的`VNode`更新時(shí)調(diào)用.
console.log('updated triggerd')
},
componentUpdated() {
// 指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
console.log('componentUpdated triggerd')
},
unbind() {
// 只調(diào)用一次了赵,指令與元素解綁時(shí)調(diào)用.
console.log('unbind triggerd')
}
})
vuex工作原理和組成
首先要說(shuō)的是潜支,vuex作為一個(gè)插件,在vue中使用斟览,就會(huì)調(diào)用他的install方
// src/store.js
export function install (_Vue) {
if (Vue && _Vue === Vue) {
return
}
Vue = _Vue
applyMixin(Vue)
}
其代碼比較簡(jiǎn)單毁腿,調(diào)用了一下applyMixin方法,該方法主要作用就是在所有組件的beforeCreate生命周期注入了設(shè)置this.$store這樣一個(gè)對(duì)象
下面來(lái)看一下他的構(gòu)造函數(shù)
// 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)
// 重點(diǎn)方法 ,重置VM
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
除了一堆初始化外已烤,我們注意到了這樣一行代碼resetStoreVM(this, state)
他就是整個(gè)vuex的關(guān)鍵
// src/store.js
function resetStoreVM (store, state, hot) {
// 省略無(wú)關(guān)代碼
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
去除了一些無(wú)關(guān)代碼后我們發(fā)現(xiàn)鸠窗,其本質(zhì)就是將我們傳入的state作為一個(gè)隱藏的vue組件的data,也就是說(shuō),我們的commit操作胯究,本質(zhì)上其實(shí)是修改這個(gè)組件的data值稍计,結(jié)合上文的computed,修改被defineReactive代理的對(duì)象值后,會(huì)將其收集到的依賴的watcher中的dirty設(shè)置為true,等到下一次訪問(wèn)該watcher中的值后重新獲取最新值裕循。
這樣就能解釋了為什么vuex中的state的對(duì)象屬性必須提前定義好臣嚣,如果該state中途增加一個(gè)屬性,因?yàn)樵搶傩詻](méi)有被defineReactive剥哑,所以其依賴系統(tǒng)沒(méi)有檢測(cè)到硅则,自然不能更新。
由上所說(shuō)株婴,我們可以得知store._vm.$data.$$state === store.state, 我們可以在任何含有vuex框架的工程驗(yàn)證這一點(diǎn)怎虫。
后一句話結(jié)束vuex工作原理,vuex中的store本質(zhì)就是沒(méi)有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通過(guò)hash與History interface兩種方式實(shí)現(xiàn)前端路由困介,更新視圖但不重新請(qǐng)求頁(yè)面”是前端路由原理的核心之一大审,目前在瀏覽器環(huán)境中這一功能的實(shí)現(xiàn)主要有兩種方式
- hash ---- 利用URL中的hash(“#”)
- 利用History interface在 HTML5中新增的方法
那么,我們要選擇用哪種方式呢座哩?
在vue-router中徒扶,它提供mode參數(shù)來(lái)決定采用哪一種方式,選擇流程如下:
mode 參數(shù):
默認(rèn)hash
history 注:如果瀏覽器不支持history新特性,則采用hash方式
如果不在瀏覽器環(huán)境則使用abstract(node環(huán)境下)
mode: 'hash'
http://localhost:8080/#/recommend
mode: 'history'
http://localhost:8080/recommend
// 根據(jù)mode確定history實(shí)際的類并實(shí)例化
// 根據(jù)mode確定history實(shí)際的類并實(shí)例化
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真是身懷絕技根穷,會(huì)很多東西姜骡。特別是替換路由特別厲害。還可以通過(guò)不同的方式缠诅,一個(gè)是push
溶浴,一個(gè)是replace
.
1 $router.push() //調(diào)用方法
2 HashHistory.push() //根據(jù)hash模式調(diào)用,設(shè)置hash并添加到瀏覽器歷史記錄(添加到棧頂)(window.location.hash= XXX)
3 History.transitionTo() //監(jiān)測(cè)更新,更新則調(diào)用History.updateRoute()
4 History.updateRoute() //更新路由
5 {app._route= route} //替換當(dāng)前app路由
6 vm.render() //更新視圖
HTML5History
History interface是瀏覽器歷史記錄棧提供的接口管引,通過(guò)back(), forward(), go()等方法士败,我們可以讀取瀏覽器歷史記錄棧的信息,進(jìn)行各種跳轉(zhuǎn)操作褥伴。
從HTML5開始谅将,History interface有進(jìn)一步修煉:pushState(), replaceState() 這下不僅是讀取了,還可以對(duì)瀏覽器歷史記錄棧進(jìn)行修改
vue的seo問(wèn)題
由于傳統(tǒng)的搜索引擎只會(huì)從 HTML 中抓取數(shù)據(jù)重慢,導(dǎo)致前端渲染的頁(yè)面無(wú)法被抓取饥臂。前端渲染常使用的 SPA 會(huì)把所有 JS 整體打包,無(wú)法忽視的問(wèn)題就是文件太大似踱,導(dǎo)致渲染前等待很長(zhǎng)時(shí)間隅熙。特別是網(wǎng)速差的時(shí)候稽煤,讓用戶等待白屏結(jié)束并非一個(gè)很好的體驗(yàn)。
解決方案:
- vue ssr
- nuxt.js
- prerender-spa-plugin插件
由于項(xiàng)目只是改善少數(shù)頁(yè)面的seo囚戚,所以使用預(yù)渲染方式酵熙,預(yù)渲染插件使用vue官方推薦的prerender-spa-plugin
。
生命周期內(nèi)create和mounted的區(qū)別
created:在模板渲染成html前調(diào)用驰坊,即通常初始化某些屬性值匾二,然后再渲染成視圖。
mounted:在模板渲染成html后調(diào)用拳芙,通常是初始化頁(yè)面完成后察藐,再對(duì)html的dom節(jié)點(diǎn)進(jìn)行一些需要的操作。
其實(shí)兩者比較好理解舟扎,通常created使用的次數(shù)多分飞,而mounted通常是在一些插件的使用或者組件的使用中進(jìn)行操作,比如插件chart.js的使用: var ctx = document.getElementById(ID);通常會(huì)有這一步睹限,而如果你寫入組件中浸须,你會(huì)發(fā)現(xiàn)在created中無(wú)法對(duì)chart進(jìn)行一些初始化配置,一定要等這個(gè)html渲染完后才可以進(jìn)行邦泄,那么mounted就是不二之選。
Vue實(shí)現(xiàn)登陸攔截
Step1: requireAuth屬性
requireAuth屬性作用是表明該路由是否需要登錄驗(yàn)證裂垦,在進(jìn)行全局?jǐn)r截時(shí)顺囊,我們將通過(guò)該屬性判斷路由的跳轉(zhuǎn),該屬性包含在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) { // 判斷跳轉(zhuǎn)的路由是否需要登錄
if (store.state.token) { // vuex.state判斷token是否存在
next() // 已登錄
} else {
next({
path: '/login',
query: {redirect: to.fullPath} // 將跳轉(zhuǎn)的路由path作為參數(shù)蕉拢,登錄成功后跳轉(zhuǎn)到該路由
})
}
} else {
next()
}
})