【教程】Pastate.js 響應(yīng)式 react 框架(八) 原理和API文檔

這是 Pastate.js 響應(yīng)式 react state 管理框架系列教程罚勾,歡迎關(guān)注,持續(xù)更新献雅。

原理剖析

Pastate 的核心是一個(gè)管理不變式和響應(yīng)式 state 的 pastore, 下面來(lái)詳細(xì)介紹 pastore 的內(nèi)容睦霎。

帶路徑信息的 immutable state

Pastore 內(nèi)部使用一種獨(dú)特的帶路徑信息且不可變(immutable)的 state 作為應(yīng)用的數(shù)據(jù)源, 掛載在 store 的 imState 屬性上。在實(shí)現(xiàn)上犀勒,Pastore 把非空數(shù)據(jù)源都轉(zhuǎn)化為包裝類(lèi)型,具體流程如下圖,以 string 類(lèi)型的節(jié)點(diǎn)為例:

state 包裝

其中 __store__ 為 state 所在的 store 實(shí)例的引用贾费,__xpath__ 為該 state 節(jié)點(diǎn)在當(dāng)前 store 的 state 的路徑钦购,如 __xpath__ == '.foo.bar.baz'

為什么要使用 immutable 數(shù)據(jù)褂萧?

Pastate 使用一種自定義的具有 immutable 特性的數(shù)據(jù)類(lèi)型押桃, 而不是 immutable.js 的數(shù)據(jù)類(lèi)型。 Immutable 特性指的是數(shù)據(jù)是不可變的导犹,新的數(shù)據(jù)只能根據(jù)老的不可變數(shù)據(jù)去重新組合而成唱凯,下面舉個(gè)例子:

/** 可變數(shù)據(jù) */
let mutableData = {
    foo: 'good',
    bar: 'very good'
}
// 可以使用直接賦值的模式去改變數(shù)據(jù)
mutableData.foo = 'so good'
newData = mutableData

/** 不可變數(shù)據(jù) */
let immutableData = {
    foo: 'good',
    bar: 'very good'
}
// 不可修改老數(shù)據(jù),需要根據(jù)老數(shù)據(jù)去構(gòu)建新數(shù)據(jù)
newData = Object.assgin({}, immutableData, {foo: 'so good'}) // 可以使用 ES6 的 Object.assgin 函數(shù)
newData = {...immutableData, foo: 'so good'} // 也可以使用 ES7 的對(duì)象擴(kuò)展符

immutableData 實(shí)現(xiàn)了兩種效果:

  1. 可以把 state 變化過(guò)程的每個(gè)狀態(tài)保存起來(lái)谎痢,這為應(yīng)用的調(diào)試帶來(lái)了很多新的方法磕昼,如時(shí)間旅行調(diào)試
  2. 對(duì)象內(nèi)部的數(shù)據(jù)發(fā)生改變時(shí),該對(duì)象的引用需要進(jìn)行更新节猿,這符合函數(shù)式編程的要求票从,可以啟用 react 的 pure render ( PureComponent ) 按需快速渲染功能,使應(yīng)用的性能得到極大提高

為什么要帶路徑信息滨嘱?

如果我們手動(dòng)實(shí)現(xiàn) immutable state 的更新邏輯纫骑,會(huì)非常麻煩,假設(shè)有這樣一個(gè) state :

let state = {
    foo: {
        bar: {
            baz: {
                name: 'Peter'
            }
        }
    },
}

// 我們手動(dòng)來(lái)修改 name 屬性的值

// 1. 如果是 mutable data
state.foo.bar.baz.name = 'Tom'
newData = state

// 2. 如果是 immutable data 
newData = Object.assgin({}, state, 
    Object.assgin({}, state.foo, 
        Object.assgin({}, state.foo.bar, 
            Object.assgin({}, state.foo.bar.baz, {
                name: 'Tom'
            })
        )
    )
)

可見(jiàn)九孩,在手動(dòng)更新 immutble data 的深層嵌套數(shù)據(jù)時(shí)先馆,非常麻煩!而且如果有多個(gè)相同或不同嵌套深度的 state 節(jié)點(diǎn)需要更改躺彬,且這些更改時(shí)在運(yùn)行上連續(xù)進(jìn)行的但是在代碼上是封裝開(kāi)的煤墙,容易導(dǎo)致重復(fù)進(jìn)行沒(méi)意義的引用更新:

let state = {
    foo: {
        bar: {
            baz: {
                name: 'Peter'
            },
            id: '1234'
        }
    },
}

// 我們封裝 name 的修改邏輯
function changeName(){
    Object.assgin..., Object.assgin..., Object.assgin..., Object.assgin...
}
// 我們封裝 age 的修改邏輯
function changeAge(){
    Object.assgin..., Object.assgin..., Object.assgin...
}

function handleClick(){
    shouldChangeName && changeName()
    shouldChangeAge && changeAge() 
}

上面的代碼,在 handleClick 的實(shí)現(xiàn)邏輯中宪拥,是否修改 name 或 age 是有可能單獨(dú)或同時(shí)發(fā)生的仿野,一般我們會(huì)把修改邏輯獨(dú)立封裝并按需調(diào)用。如果某個(gè)情況下需要連續(xù)運(yùn)行 changeName 和 changeAge她君, 那么在 changeAge 中的 3 個(gè) Object.assgin 是重復(fù)的脚作,沒(méi)有實(shí)際意義。

你可能會(huì)考慮使用 immutable.js 庫(kù)的數(shù)據(jù)結(jié)構(gòu)缔刹,immutable.js 提供完善的 immutable 數(shù)據(jù)類(lèi)型和操作方式支持球涛,但在 react 項(xiàng)目中使用 immutable.js 有以下不足:

  • immutable.js 的數(shù)據(jù)格式與原生 JS 格式互不兼容,在渲染時(shí)校镐,經(jīng)常需要在合適的位置使用 xxx.get() 或 xxx.toJS() 獲取原生 js 格式(使用 toJS() 會(huì)使所有子節(jié)點(diǎn)的引用更新亿扁,如果直接使用 fromJS(state).toJS() 只是對(duì) state 進(jìn)行 "深層復(fù)制", 不能實(shí)現(xiàn)用 immutable 數(shù)據(jù)來(lái)實(shí)現(xiàn)快速按需渲染的效果)
  • immutable.js 的數(shù)據(jù)操作模式是基于"節(jié)點(diǎn)名稱(chēng) / 節(jié)點(diǎn)名稱(chēng)數(shù)組" 來(lái)索引節(jié)點(diǎn)的,如imState.setIn(['info', 'basic', 'name'], 'new Name')鸟廓,而不是用原生 JS 對(duì)象屬性模式來(lái)索引節(jié)點(diǎn), 如 state.info.basic.name从祝。因此編輯器無(wú)法分析可選屬性襟己,也無(wú)法索引結(jié)果的類(lèi)型,這使得編程起來(lái)效率低牍陌,而且比較容易出錯(cuò)擎浴。
  • immutable.js 確實(shí)完備,但過(guò)于龐大復(fù)雜毒涧,學(xué)習(xí)成本高贮预,許多功能在一般的實(shí)際開(kāi)發(fā)中不需要使用到,對(duì)初學(xué)者不太友好链嘀。

因此,Pastate 在每個(gè) state 節(jié)點(diǎn)附加了 節(jié)點(diǎn)路徑信息 档玻,并實(shí)現(xiàn) state 的自動(dòng)化 immutable 且按需更新引用的功能怀泊!讓我們可以享受 immutable 數(shù)據(jù)的優(yōu)勢(shì)的同時(shí),拋開(kāi)使用 immutable 數(shù)據(jù)的復(fù)雜性误趴。

pastate imState 內(nèi)部操作機(jī)制

Pastore 內(nèi)部實(shí)現(xiàn)了一個(gè)異步操作(operation)列表霹琼,并定義了一套 operation 壓入方法:

  • set: 設(shè)置值 set(imState.foo.bar, newValue)
  • merge: 合入新值 merge(imState.foo, {bar: newValue})
  • update: 更新值 update(imState.foo.bar, n => n+ '!')

這三個(gè)方法會(huì)向 store 提交一個(gè) operation, store 把收到 operation 后,暫時(shí)存在一個(gè) operation 隊(duì)列中凉当,在下一個(gè)事件循環(huán)(event loop)時(shí)對(duì) operation 隊(duì)列進(jìn)行統(tǒng)一處理枣申,再批量處理中實(shí)現(xiàn)按需引用更新,具體流程圖如下:

operation 生成和處理流程

在每個(gè) operation 被 push 的前后及在 operation 被 reduce 的前后看杭,pastate 都設(shè)計(jì)了相應(yīng)的生命周期函數(shù)忠藤,你可以通過(guò)這些生命周期函數(shù)接收、過(guò)濾或控制operation楼雹,實(shí)現(xiàn)自定義邏輯模孩。

  • stateWillAddOperation 將要增加 operation 時(shí)會(huì)被調(diào)用
  • stateWillReduceOperations 將要執(zhí)行 operations 時(shí)會(huì)被調(diào)用
  • stateWillApplyOperation 將要執(zhí)行一個(gè) operation 時(shí)會(huì)被調(diào)用
  • stateDidAppliedOperation 執(zhí)行完一個(gè) operation 后會(huì)被調(diào)用
  • stateDidReducedOperations 執(zhí)行完 operations 時(shí)執(zhí)行

詳見(jiàn) API文檔。

響應(yīng)式 state 鏡像

直接使用 set(imState.foo.bar, newValue)merge(imState.foo, {bar: newValue}) 等來(lái)進(jìn)行 immutable state 的操作雖然已經(jīng)挺方便贮缅,但和我們平時(shí)修改 js 變量的模式還是不太一樣榨咐,因此 pastate 為 immutable state 構(gòu)建了一套響應(yīng)式的鏡像 state 來(lái)自動(dòng)調(diào)用 set、merge谴供、或 update 來(lái)生成 operation, 使得我們可以用賦值符號(hào) = 或數(shù)組操作參數(shù)如 push 块茁、pop 等來(lái)間接地對(duì) immutable state 進(jìn)行操作,對(duì)于處學(xué)者來(lái)說(shuō)無(wú)需任何學(xué)習(xí)成本桂肌。 Pastate 內(nèi)部使用 Object.defineProperty 的方式為響應(yīng)式 state 節(jié)點(diǎn)定義相應(yīng)的 getter 和 setter, 以實(shí)現(xiàn)通過(guò)賦值或數(shù)組函數(shù)發(fā)起 set, merge 或 update operation数焊,對(duì)接 pastate 異步 operation 處理引擎,流程圖如下:

響應(yīng)式流程

視圖響應(yīng)引擎

當(dāng) pastore 完成一批 operations 的 reduce 過(guò)程時(shí)崎场,會(huì)對(duì)外發(fā)出一個(gè)視圖更新信號(hào)昌跌,通過(guò) pastore 的 dispatch 成員函數(shù)發(fā)出。Pastate 內(nèi)部實(shí)現(xiàn)了 pastate-redux 連接器, 默認(rèn)使用 redux 作為 state 發(fā)生改變時(shí)的響應(yīng)器照雁,并由 react-redux 去連接 react 組件組件蚕愤,從而實(shí)現(xiàn) pastate-react答恶。同時(shí), pastate 使用 redux 的基本規(guī)則實(shí)現(xiàn)多模塊邏輯萍诱,因此悬嗓,你可以使用 redux 生態(tài)系統(tǒng)相關(guān)組件或開(kāi)發(fā)工具類(lèi)配合 pastate 應(yīng)用,我們非常擁抱 redux 生態(tài)系統(tǒng)裕坊。

另一方面包竹,你完全可以自行實(shí)現(xiàn)一套視圖響應(yīng)邏輯, 甚至實(shí)現(xiàn) pastore 與 vue 或 angular 對(duì)接,歡迎嘗試籍凝。

API 文檔

其他信息

在 typescript 中使用

Pastate 使用 typescript 進(jìn)行開(kāi)發(fā)周瞎,具有完善的類(lèi)型支持,可以直接引入 pastate 并直接在 React Typescript 項(xiàng)目中使用饵蒂。

在 react-native 中使用

我們正嘗試在 react-native 中使用 pastate, 你也可以加入嘗試声诸,如果發(fā)現(xiàn)什么問(wèn)題,歡迎提交 issue 告訴我們~

在 preact 或 nerv 中使用

Pastate 在原理上可以與類(lèi) react (react alternative) 框架如 preactnerv 配合使用退盯,但未進(jìn)行全面測(cè)試彼乌,如果你嘗試了,歡迎提交 issue 告訴我們~

相關(guān)資源

敬請(qǐng)期待渊迁,如果你基于 pastate 開(kāi)發(fā)一些項(xiàng)目慰照,歡迎提交 issue 告訴我們~

貢獻(xiàn)指引

  1. fork pastate 的 github 倉(cāng)庫(kù): https://github.com/BirdLeeSCUT/pastate
  2. clone 并安裝 npm 包
$ git clone <your repo>
$ cd pastate
$ yarn install
  1. 運(yùn)行 pastate 開(kāi)發(fā)包
$ yarn start
  1. 實(shí)時(shí)測(cè)試 pastate 開(kāi)發(fā)包
$ yarn test
  1. 編譯 pastate 開(kāi)發(fā)包
$ yarn dist

目標(biāo)代碼會(huì)編譯到 /dist 目錄下

  1. 提交代碼,發(fā)起 pull request琉朽。

英文翻譯

如果覺(jué)得 pastate 非常不錯(cuò)毒租,歡迎參與 pastate 文檔英文翻譯計(jì)劃,讓更多的國(guó)外友人也能?chē)L試 pastate 箱叁。如果你有意愿蝌衔,請(qǐng)?jiān)?github 聯(lián)系。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蝌蹂,一起剝皮案震驚了整個(gè)濱河市噩斟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孤个,老刑警劉巖剃允,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異齐鲤,居然都是意外死亡斥废,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)给郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)牡肉,“玉大人,你說(shuō)我怎么就攤上這事淆九⊥炒福” “怎么了毛俏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)饲窿。 經(jīng)常有香客問(wèn)我煌寇,道長(zhǎng),這世上最難降的妖魔是什么逾雄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任阀溶,我火速辦了婚禮,結(jié)果婚禮上鸦泳,老公的妹妹穿的比我還像新娘银锻。我一直安慰自己,他們只是感情好做鹰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布击纬。 她就那樣靜靜地躺著,像睡著了一般誊垢。 火紅的嫁衣襯著肌膚如雪掉弛。 梳的紋絲不亂的頭發(fā)上症见,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天喂走,我揣著相機(jī)與錄音,去河邊找鬼谋作。 笑死芋肠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遵蚜。 我是一名探鬼主播帖池,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吭净!你這毒婦竟也來(lái)了睡汹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寂殉,失蹤者是張志新(化名)和其女友劉穎囚巴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體友扰,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彤叉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了村怪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秽浇。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甚负,靈堂內(nèi)的尸體忽然破棺而出柬焕,到底是詐尸還是另有隱情审残,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布击喂,位于F島的核電站维苔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏懂昂。R本人自食惡果不足惜介时,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凌彬。 院中可真熱鬧沸柔,春花似錦、人聲如沸铲敛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伐蒋。三九已至工三,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間先鱼,已是汗流浹背俭正。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留焙畔,地道東北人掸读。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宏多,于是被迫代替她去往敵國(guó)和親儿惫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容