自己開發(fā)一款游戲怎么做搜索引擎seo優(yōu)化
最近,接到小程序需求,并且是在以前公司老項目上改造,打開項目,發(fā)現(xiàn)卻不是我想象中的那樣,不是上來就是 Page({}),而是create(store,{}),納尼???這什么玩意,怎么沒見過
1、初見Westore
接上,于是乎打開了create函數(shù)(后面得知本項目引用的1.0版本)如下
export default function create(store, option) {let updatePath = nullif (arguments.length === 2) { ...Page(option)} else {...Component(store)}
}
咋眼一看,難不成是自己寫了一套狀態(tài)管理?直覺告訴我,這應(yīng)該不是前輩寫的,應(yīng)該是某個三方庫,于是乎去搜索了一番,果然是騰訊官網(wǎng)針對小程序優(yōu)化而出的,鏈接在這里,感興趣的小伙伴可以去看看哦
2、按文檔理解
大概是說,以store去驅(qū)動視圖,性能有所提高,能解決小程序跨頁面通訊,傳值等問題,反正巴拉巴拉一大堆(小程序不是有g(shù)lobalData嗎,說實話,我還沒理解它這個的好處),結(jié)合自己理解再簡單總結(jié)下吧
- 經(jīng)過改造后,相比小程序更新視圖的setData,Westore的update性能更好,為啥呢?update底層實質(zhì)還是調(diào)用的setData,只是再調(diào)用直接走了一次diff,只更新變動的,舉個栗子:
data: {info: {a: 'xxx',b: 'xxx'...}
}我們一般在更新一些數(shù)據(jù)時,可能會直接 setData({ info: newInfo }),而實際 newInfo 只是某個屬性改變了
當(dāng)使用 update() 時,會直接找出不同,差量的去更新
update()-------> setData({ 'info.a': '改變了哦' })
- 通過 store ,可以設(shè)置一些函數(shù)屬性,這個就類似vue的計算屬性了
- 剩下的就是關(guān)于 store 全局?jǐn)?shù)據(jù)的一個概念,就不累贅了
當(dāng)然,這里只是簡單說下體會最明顯的幾點
3、簡單分析下流程
- 映入眼簾的是 create ,那么需要知道它干了啥
- 函數(shù)屬性是怎么實現(xiàn)的
- 憑什么 update 就比 setData好
3.1 淺析create
export default function create(store, option) {let updatePath = null// 在這一步,區(qū)分是組件還是頁面if (arguments.length === 2) { if (option.data && Object.keys(option.data).length > 0) {// 記錄data中的keyupdatePath = getUpdatePath(option.data)// 頁面data的值初始值替換為 store中的值syncValues(store.data, option.data)}// 保留store源數(shù)據(jù),同時給當(dāng)前store新增幾個方法,尤其是updateif (!originData) {originData = JSON.parse(JSON.stringify(store.data))globalStore = storestore.instances = {}store.update = update...// 給全局的 globalStore 添加一個 methodextendStoreMethod(store)}getApp().globalData && (getApp().globalData.store = store)//option.data = store.dataconst onLoad = option.onLoad// walk 為了解決當(dāng)定義在store里面屬性是一個方法時// 會通過 Object.defineProperty 攔截一下該屬性的get過程(也就是緩存下函數(shù),改變this環(huán)境執(zhí)行一下)walk(store.data)// 解決函數(shù)屬性初始化不能顯示的問題,要求必須在data中聲明使用// 這段代碼是同步store.data到option.data,只有經(jīng)過walk方法后store.data中的函數(shù)才能變成屬性,才能被小程序page方法渲染if (option.data && Object.keys(option.data).length > 0) {updatePath = getUpdatePath(option.data)console.log('updatePath',updatePath)syncValues(store.data, option.data)}option.onLoad = function (e) {// 給當(dāng)前實例添加 store 、更新路徑、update方法、執(zhí)行onLoad、同步data、// 走小程序 setDatathis.store = storethis._updatePath = updatePath...this.setData(this.data)}// 解決執(zhí)行navigateBack或reLaunch時清除store.instances對應(yīng)頁面的實例const onUnload = option.onUnloadoption.onUnload = function () {onUnload && onUnload.call(this)store.instances[this.route] = []}Page(option)} else {組件邏輯,先不看}
}
create,接收兩個參數(shù)
store ---- 可以理解為頁面的數(shù)據(jù)(或者共享時公有的)
option — 則是小程序原有選項
代碼開頭則通過實參個數(shù),區(qū)分了當(dāng)前是組件,還是頁面(這里以頁面為例),同時記錄下頁面data路徑,也就是 getUpdatePath 函數(shù)
function getUpdatePath(data) {const result = {}dataToPath(data, result)return result
}function dataToPath(data, result) {Object.keys(data).forEach(key => {result[key] = trueconst type = Object.prototype.toString.call(data[key])if (type === OBJECTTYPE) {_objToPath(data[key], key, result)} else if (type === ARRAYTYPE) {_arrayToPath(data[key], key, result)}})
}
如上,getUpdatePath 目的就是把各個屬性記錄下來,如
data: {a: '123',b: { c: '456' }
}
getUpdatePath(data) 后
{a: true,'b.c': true
}
有了這個后,是為了方便后續(xù)判斷要更新的key在不在data中
還有一步 syncValues,這個函數(shù)就是把store中的值,同步到 data 中,這就是為什么頁面需要列出store中的屬性的原因(這里是v1,貌似proxy那個版本不需要了)
接著就是給store添加一些方法(如update),以及源數(shù)據(jù)保留等
來到 walk 函數(shù)
function walk(data) {Object.keys(data).forEach(key => {const obj = data[key]const tp = type(obj)if (tp == FUNCTIONTYPE) {setProp(key, obj)} else if (tp == OBJECTTYPE) {Object.keys(obj).forEach(subKey => {// 值,key vipInfo.age_walk(obj[subKey], key + '.' + subKey)})} else if (tp == ARRAYTYPE) {obj.forEach((item, index) => {_walk(item, key + '[' + index + ']')})}})
}function _walk(obj, path) {const tp = type(obj)if (tp == FUNCTIONTYPE) {setProp(path, obj)} else if (tp == OBJECTTYPE) {Object.keys(obj).forEach(subKey => {_walk(obj[subKey], path + '.' + subKey)})} else if (tp == ARRAYTYPE) {obj.forEach((item, index) => {_walk(item, path + '[' + index + ']')})}
}function setProp(path, fn) {const ok = getObjByPath(path)fnMapping[path] = fnObject.defineProperty(ok.obj, ok.key, {enumerable: true,get: () => {return fnMapping[path].call(globalStore.data)},set: () => {console.warn('Please using store.method to set method prop of data!')}})
}
看到這種名字的函數(shù),第一反應(yīng)就是逐個遍歷的過程,這個函數(shù)雖然拆成了幾個函數(shù),但目的其實很簡單,只有當(dāng) tp == FUNCTIONTYPE 時,才會跳出這個過程,走 setProp 函數(shù),看到這里可能還是有點迷糊,那就加一個函數(shù)屬性,豁然開朗
data: {vipInfo: {age: '25',getAge(){return this.vipInfo.age}}
}
經(jīng)過 walk 后
vipInfo: {age: '25',getAge: undefined
}
// getAge 變成了一個屬性,并且通過攔截的方式,當(dāng)get的時候再執(zhí)行開始定義的函數(shù)
// 這也就能解釋如何實現(xiàn) 函數(shù)屬性的了
剩下幾部就是對onLoad函數(shù)的改寫,以及一些頁面卸載,實例銷戶的過程,最終還是走的小程序Page函數(shù)
以上步驟,可以知道主要是
1、同步 store 中的值到 小程序 data 中
2、記錄每個屬性的路徑
3、當(dāng) store 中有函數(shù)屬性時,通過響應(yīng)攔截方式,將其轉(zhuǎn)變?yōu)?屬性(同時再次同步一次值)
4、給store添加一些api
5、對 onLoad 方法進行改寫,包括 onUnload
6、走小程序 Page 過程
3.2 那就看看 update
function update(patch) {return new Promise(resolve => {//defineFnProp(globalStore.data)// 可以傳路徑,也可以不傳if (patch) {for (let key in patch) {updateByPath(globalStore.data, key, patch[key])}}// diff 后直接找出差異的數(shù)據(jù)let diffResult = diff(globalStore.data, originData)if (Object.keys(diffResult)[0] == '') {diffResult = diffResult['']}// 是否是全局?jǐn)?shù)據(jù)const updateAll = matchGlobalData(diffResult)let array = []if (Object.keys(diffResult).length > 0) {for (let key in globalStore.instances) {globalStore.instances[key].forEach(ins => {if(updateAll || globalStore.updateAll || ins._updatePath){// 獲取需要更新的字段const needUpdatePathList = getNeedUpdatePathList(diffResult, ins._updatePath)console.log('needUpdatePathList',needUpdatePathList)if (needUpdatePathList.length) {...// 值差量更新,并且包裝成 數(shù)組 Promise 形式array.push( new Promise(cb => {ins.setData.call(ins, _diffResult, cb)}) )}}})}// 數(shù)據(jù)更新的回調(diào)globalStore.onChange && globalStore.onChange(diffResult)...Promise.all(array).then(e=>{resolve(diffResult)})})
}
可以看到,update 就比較殘暴了,通過 diff ,找出變動的數(shù)據(jù),接著是對應(yīng)實例更新問題,最后把需要更新的數(shù)據(jù)包裝成 Promise 的形式,最終通過 setData 實現(xiàn)
4、總結(jié)
以上就是筆者對整個過程的分析,從簡單來看,可以理解為重點對 setData 進行了 diff 的優(yōu)化,用法是上顯得直觀,官方也給出了 多頁面時幾種情況 store 的拆分,不過筆者還沒想好應(yīng)該怎么寫,跟優(yōu)雅