一年前端菜鳥,開始記錄一下自己學習的心得體會,歡迎指點,大神輕噴
最近碰到一個需求,需要儲存每個頁面的資料,重新整理之後也要有,因此寫了一個自動儲存資料的mixin
簡單的實現思路:
vue對data裡面的資料通過Object.defineProperty做了雙向繫結,我們可以通過Reflect.getOwnPropertyDescriptor去拿到vue定義的描述符物件,也就拿到了get,set函式,通過定義自己的get,set函式做一些想要的擴充套件,比如儲存資料,然後代理vue的get,set,重新defineProperty。
下面上程式碼:
// 先寫一個工具函式用來判斷物件和陣列
const createJudgeType = typeStr => obj => Object.prototype.toString.call(obj) === `[object ${type}]`
// 建立判斷物件和陣列的函式
const isObject = createJudgeType('Object')
const isArray = createJudgeType('Array') // 可以使用Array.isArray
// 定義陣列變異方法陣列後面會使用
const arrayMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse',
]
class AutoSave {
// 判斷__proto__能否使用
static hasProto = Reflect.has({}, '__proto__')
// 例項化
static of (vm) {
return new AutoSave(vm)
}
constructor (vm) {
this.vm = vm // 當前vue例項
this.arrayProto = null // 根據hasProto來決定是vue定義的陣列原型還是當前陣列本身
this.init()
}
init () {
// 先判斷是否需要這個功能
if (this.isKeepData()) {
const data = this.getData()
// 如果資料存在初始化資料否則儲存資料
data ? this.initData(data) : this.setData()
this.reactive(this.vm.$data, this.vm)
}
}
initData (str) {
// 初始化資料
const data = JSON.parse(str)
Object.assign(this.vm, data)
}
isKeepData () {
// 這裡可以做一些判斷來決定是否要儲存資料
// 比如通過路由meta配置變數,
return this.vm.$route.meta.keepData
}
getData () {
// 獲取當前路由對應的data
const routeName = this.vm.$route.name
return sessionStorage.getItem(routeName)
}
setData () {
// 通過當前路由名字作為鍵去儲存這個路由對應元件的data
const routeName = this.vm.$route.name
sessionStorage.setItem(routeName, JSON.stringify(this.vm.$data))
}
reactive (obj, vm) {
// 如果data是一個深層物件,只有第一次呼叫的時候會傳遞vm例項
// 原因是vue在vm例項上對data物件的每個屬性做了一層代理,即this.a實際上訪問的是this._data.a
const target = vm || obj
Object.entries(obj).forEach(([key, val]) => {
// 遍歷obj給每個屬性設定自己的代理
this.proxy(target, key, val)
//繼續判斷每個值
this.judgeType(val)
})
}
proxy (target, key, val) {
// 獲取vue的描述符物件,定義自己的描述符物件
const {
get: vGet,
set: vSet,
} = Reflect.getOwnPropertyDescriptor(target, key)
const that = this
const descriptor = {
enumerable: true,
configurable: true,
get: vGet.bind(target),
set (value) {
vSet.call(target, value)
that.setData()
},
}
Reflect.defineProperty(target, key, descriptor)
}
judgeType (val) {
//如果是物件遞迴呼叫reactive
isObject(val) && this.reactive(val)
//如果是陣列則代理陣列
isArray(val) && this.proxyArray(val)
}
def (target, key, val, enumerable) {
Reflect.defineProperty(target, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
})
}
createArrayProto (arr) {
// 如果__proto__可用 並且陣列新的原型物件已經存在直接返回
if (AutoSave.hasProto && this.arrayProto) {
return this.arrayProto
}
// 如果__proto__可用,獲取vue定義的陣列原型,否則拿到陣列本身,以此建立新的原型物件
// 並給這個原型物件定義自己的變異方法,這個變異方法裡面呼叫了儲存資料的功能和vue的變異方法
const vArrayProto = AutoSave.hasProto ? Reflect.getPrototypeOf(arr) : arr
this.arrayProto = Object.create(vArrayProto)
const that = this
arrayMethods.forEach(method => {
this.def(this.arrayProto, method, function (...args) {
const result = vArrayProto[method].call(this, ...args)
that.setData()
return result
})
})
return this.arrayProto
}
proxyArrayProto (target, src) {
/* eslint-disable */
// 如果__proto__可用,直接給當前陣列設定一個新的原型
target.__proto__ = src
}
proxyArrayMethods (target, src) {
// 如果__proto__不可用,在當前陣列上定義新的變異方法,這個變異方法裡面呼叫了儲存資料的功能和vue的變異方法
arrayMethods.forEach(method => {
this.def(target, method, src[method])
})
}
proxyArray (arr) {
// 根據__proto__是否可用來決定對陣列的處理方式
const handle = AutoSave.hasProto ? this.proxyArrayProto : this.proxyArrayMethods
handle(arr, this.createArrayProto(arr))
// 迴圈陣列遞迴判斷裡面的每一項
arr.forEach(item => this.judgeType(item))
}
}
export default {
beforeRouteEnter (to, from, next) {
next(vm => AutoSave.of(vm))
}
}
複製程式碼
由於beforeRouteEnter會在每次路由切換執行所以需要適當優化,比如換成created
應該有更好的解決方案,歡迎大神指點