我面試最喜歡問的開放題:如何嚴(yán)謹(jǐn)二次封裝 localStorage?
在很多公司中,內(nèi)部都會(huì)封裝一些適用于公司內(nèi)部業(yè)務(wù)的方法庫來提高整個(gè)團(tuán)隊(duì)的開發(fā)效率,比如:
- 防抖節(jié)流
- 懶加載、虛擬滾動(dòng)
- dom增刪改查、移動(dòng)、拖拽
- 管理狀態(tài)
而在 Vue3 項(xiàng)目中,這種方法庫表現(xiàn)為:hooks庫,市面上有很多優(yōu)秀的庫,比如:vueuse。
最近我在面試中,喜歡問一道有關(guān)于 hooks 的開放問題:二次封裝一個(gè) loaclStorage 的 hooks 時(shí),需要考慮哪些問題呢?
其實(shí)這是一道很簡單的題,只不過想考考面試者在做業(yè)務(wù)的時(shí)候,會(huì)不會(huì)考慮更多的邊界情況~接下來說說我對(duì)這個(gè)問題的小小的理解(可能也不是很全面)。
注意命名,防止污染
比如我現(xiàn)在一個(gè)域名下有兩個(gè)子項(xiàng)目:
- A項(xiàng)目
- B項(xiàng)目
且這兩個(gè)項(xiàng)目都需要存儲(chǔ) userInfo,那要怎么防止這兩組數(shù)據(jù)互相污染呢?所以需要注意命名,在存儲(chǔ)的時(shí)候加上對(duì)應(yīng)的項(xiàng)目名前綴,或者其他標(biāo)識(shí)符,保證這組數(shù)據(jù)是唯一的
const PROJECT_NAME = 'test-project'
localStorage.setItem(
`${PROJECT_NAME}_userInfo`,
JSON.stringify({ name: 'lsx' })
)
注意版本,迭代防范
請(qǐng)看一個(gè)例子,假如我們存儲(chǔ)一段信息,類型是 string
// 存數(shù)據(jù)
const set = () => {
const info = get()
if (!info) {
localStorage.setItem(
`${PROJECT_NAME}_info`,
'info_string'
)
}
}
// 取數(shù)據(jù)
const get = () => {
const info = localStorage.getItem(
`${PROJECT_NAME}_info`
)
return info
}
然后項(xiàng)目上線了一段時(shí)間,但是這個(gè)時(shí)候,突然決定要換成 object 類型了,這時(shí)候?qū)?yīng)的存取方法也變了
// 存數(shù)據(jù)
const set = () => {
const info = get()
if (!info) {
localStorage.setItem(
`${PROJECT_NAME}_info`,
JSON.stringify({ name: 'lsx' })
)
}
}
// 取數(shù)據(jù)
const get = () => {
const info = localStorage.getItem(
`${PROJECT_NAME}_info`
)
return JSON.parse(info)
}
但是這樣其實(shí)是有隱患的,因?yàn)轫?xiàng)目已經(jīng)上線了一段時(shí)間,有些用戶已經(jīng)存過這個(gè)數(shù)據(jù)了,且存的是 string 類型,但是新版本上線之后,取數(shù)據(jù)卻用了 object 的方式去取數(shù)據(jù),這就導(dǎo)致了JSON.parse(字符串)會(huì)報(bào)錯(cuò),影響正常的業(yè)務(wù)邏輯~
所以最好是加一個(gè)版本號(hào),或者做一下錯(cuò)誤兼容,這樣就能避免了~
const PROJECT_NAME = 'test-project'
// 每次升級(jí)時(shí)改變版本號(hào),規(guī)則自己定
const VERSION = 1
// 存數(shù)據(jù)
localStorage.setItem(
`${PROJECT_NAME}_userInfo_${VERSION}`,
JSON.stringify({ name: 'lsx' })
)
// 取數(shù)據(jù)
localStorage.getItem(
`${PROJECT_NAME}_userInfo_${VERSION}`
)
時(shí)效性,私密性
時(shí)效性,那就是給存進(jìn)去的數(shù)據(jù)加一個(gè)時(shí)效,過了某個(gè)時(shí)間,這個(gè)數(shù)據(jù)就時(shí)效了,方法就是每次存數(shù)據(jù)進(jìn)去的時(shí)候,加一個(gè)時(shí)間戳:
// 原來
localStorage.setItem(
`${PROJECT_NAME}_userInfo`,
JSON.stringify({ name: 'lsx' })
)
const TIME_OUT = 3 * 60 * 60 * 1000
// 加時(shí)間戳
localStorage.setItem(
`${PROJECT_NAME}_userInfo`,
JSON.stringify({
data: { name: 'lsx' },
// 記錄當(dāng)前時(shí)間
time: new Date().getTime()
})
)
// 取數(shù)據(jù)時(shí)判斷時(shí)間戳
const get = () => {
let info = localStorage.getItem(
`${PROJECT_NAME}_userInfo_${VERSION}`
)
info = JSON.parse(info)
const now = new Date().getTime()
if (now - info.time >= TIME_OUT) {
localStorage.removeItem(
`${PROJECT_NAME}_userInfo_${VERSION}`
)
return null
}
return info
}
有一些數(shù)據(jù)我們不得不存在 localStorage 中,但是又不想被用戶看到,這時(shí)候就需要進(jìn)行加密了(加密規(guī)則自己定):
// 加密函數(shù)
const encrypt = (v) => {}
// 解密函數(shù)
const decrypt = (v) => {}
// 存數(shù)據(jù)
localStorage.setItem(
`${PROJECT_NAME}_userInfo_${VERSION}`,
// 加密
encrypt(JSON.stringify({ name: 'lsx' }))
)
// 取數(shù)據(jù) 解密
decrypt(localStorage.getItem(
`${PROJECT_NAME}_userInfo_${VERSION}`
))
兼容 SSR
SSR 就是服務(wù)端渲染,是在服務(wù)端運(yùn)行代碼,拼接成一個(gè)頁面,發(fā)送到瀏覽器去展示出來,所以在服務(wù)端是使用不了 localStorage 的,因?yàn)椴皇菫g覽器環(huán)境,所以你像封裝一個(gè)比較通用的 localStorage,得兼顧 SSR 的情況:
// 在 SSR 中使用對(duì)象替代 localStorage
const SSRStorage = {
map: {},
setItem(v) {
this.map[key] = v
},
getItem(key) {
return this.map[key]
}
}
let storage = null
// 判斷環(huán)境
if (!window) {
storage = SSRStorage
} else {
storage = window.localStorage
}