2020-7-3 seo達(dá)人
序言
之前發(fā)表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何對(duì)局前輩,吸引了不少感興趣的小伙伴入群開(kāi)始了解和使用 concent,并獲得了很多正向的反饋,實(shí)實(shí)在在的幫助他們提高了開(kāi)發(fā)體驗(yàn),群里人數(shù)雖然還很少,但大家熱情高漲,技術(shù)討論氛圍濃厚,對(duì)很多新鮮技術(shù)都有保持一定的敏感度,如上個(gè)月開(kāi)始逐漸被提及得越來(lái)越多的出自facebook的狀態(tài)管理方案 recoil,雖然還處于實(shí)驗(yàn)狀態(tài),但是相必大家已經(jīng)私底下開(kāi)始欲欲躍試了,畢竟出生名門(mén),有fb背書(shū),一定會(huì)大放異彩。
不過(guò)當(dāng)我體驗(yàn)完recoil后,我對(duì)其中標(biāo)榜的更新保持了懷疑態(tài)度,有一些誤導(dǎo)的嫌疑,這一點(diǎn)下文會(huì)單獨(dú)分析,是否屬于誤導(dǎo)讀者在讀完本文后自然可以得出結(jié)論,總之本文主要是分析Concent與Recoil的代碼風(fēng)格差異性,并探討它們對(duì)我們將來(lái)的開(kāi)發(fā)模式有何新的影響,以及思維上需要做什么樣的轉(zhuǎn)變。
數(shù)據(jù)流方案之3大流派
目前主流的數(shù)據(jù)流方案按形態(tài)都可以劃分以下這三類(lèi)
redux流派
redux、和基于redux衍生的其他作品,以及類(lèi)似redux思路的作品,代表作有dva、rematch等等。
mobx流派
借助definePerperty和Proxy完成數(shù)據(jù)劫持,從而達(dá)到響應(yīng)式編程目的的代表,類(lèi)mobx的作品也有不少,如dob等。
Context流派
這里的Context指的是react自帶的Context api,基于Context api打造的數(shù)據(jù)流方案通常主打輕量、易用、概覽少,代表作品有unstated、constate等,大多數(shù)作品的核心代碼可能不超過(guò)500行。
到此我們看看Recoil應(yīng)該屬于哪一類(lèi)?很顯然按其特征屬于Context流派,那么我們上面說(shuō)的主打輕量對(duì)
Recoil并不適用了,打開(kāi)其源碼庫(kù)發(fā)現(xiàn)代碼并不是幾百行完事的,所以基于Context api做得好用且強(qiáng)大就未必輕量,由此看出facebook對(duì)Recoil是有野心并給予厚望的。
我們同時(shí)也看看Concent屬于哪一類(lèi)呢?Concent在v2版本之后,重構(gòu)數(shù)據(jù)追蹤機(jī)制,啟用了defineProperty和Proxy特性,得以讓react應(yīng)用既保留了不可變的追求,又享受到了運(yùn)行時(shí)依賴(lài)收集和ui更新的性能提升福利,既然啟用了defineProperty和Proxy,那么看起來(lái)Concent應(yīng)該屬于mobx流派?
事實(shí)上Concent屬于一種全新的流派,不依賴(lài)react的Context api,不破壞react組件本身的形態(tài),保持追求不可變的哲學(xué),僅在react自身的渲染調(diào)度機(jī)制之上建立一層邏輯層狀態(tài)分發(fā)調(diào)度機(jī)制,defineProperty和Proxy只是用于輔助收集實(shí)例和衍生數(shù)據(jù)對(duì)模塊數(shù)據(jù)的依賴(lài),而修改數(shù)據(jù)入口還是setState(或基于setState封裝的dispatch, invoke, sync),讓Concent可以0入侵的接入react應(yīng)用,真正的即插即用和無(wú)感知接入。
即插即用的核心原理是,Concent自建了一個(gè)平行于react運(yùn)行時(shí)的全局上下文,精心維護(hù)這模塊與實(shí)例之間的歸屬關(guān)系,同時(shí)接管了組件實(shí)例的更新入口setState,保留原始的setState為reactSetState,所有當(dāng)用戶調(diào)用setState時(shí),concent除了調(diào)用reactSetState更新當(dāng)前實(shí)例ui,同時(shí)智能判斷提交的狀態(tài)是否也還有別的實(shí)例關(guān)心其變化,然后一并拿出來(lái)依次執(zhí)行這些實(shí)例的reactSetState,進(jìn)而達(dá)到了狀態(tài)全部同步的目的。
Recoil初體驗(yàn)
我們以常用的counter來(lái)舉例,熟悉一下Recoil暴露的四個(gè)高頻使用的api
atom,定義狀態(tài)
selector, 定義派生數(shù)據(jù)
useRecoilState,消費(fèi)狀態(tài)
useRecoilValue,消費(fèi)派生數(shù)據(jù)
定義狀態(tài)
外部使用atom接口,定義一個(gè)key為num,初始值為0的狀態(tài)
const numState = atom({
key: "num",
default: 0
});
定義派生數(shù)據(jù)
外部使用selector接口,定義一個(gè)key為numx10,初始值是依賴(lài)numState再次計(jì)算而得到
const numx10Val = selector({
key: "numx10",
get: ({ get }) => {
const num = get(numState);
return num * 10;
}
});
定義異步的派生數(shù)據(jù)
selector的get支持定義異步函數(shù)
需要注意的點(diǎn)是,如果有依賴(lài),必需先書(shū)寫(xiě)好依賴(lài)在開(kāi)始執(zhí)行異步邏輯
const delay = () => new Promise(r => setTimeout(r, 1000));
const asyncNumx10Val = selector({
key: "asyncNumx10",
get: async ({ get }) => {
// !!!這句話不能放在delay之下, selector需要同步的確定依賴(lài)
const num = get(numState);
await delay();
return num * 10;
}
});
消費(fèi)狀態(tài)
組件里使用useRecoilState接口,傳入想要獲去的狀態(tài)(由atom創(chuàng)建而得)
const NumView = () => {
const [num, setNum] = useRecoilState(numState);
const add = ()=>setNum(num+1);
return (
<div>
{num}<br/>
<button onClick={add}>add</button>
</div>
);
}
消費(fèi)派生數(shù)據(jù)
組件里使用useRecoilValue接口,傳入想要獲去的派生數(shù)據(jù)(由selector創(chuàng)建而得),同步派生數(shù)據(jù)和異步派生數(shù)據(jù),皆可通過(guò)此接口獲得
const NumValView = () => {
const numx10 = useRecoilValue(numx10Val);
const asyncNumx10 = useRecoilValue(asyncNumx10Val);
return (
<div>
numx10 :{numx10}<br/>
</div>
);
};
渲染它們查看結(jié)果
暴露定義好的這兩個(gè)組件, 查看在線示例
export default ()=>{
return (
<>
<NumView />
<NumValView />
</>
);
};
頂層節(jié)點(diǎn)包裹React.Suspense和RecoilRoot,前者用于配合異步計(jì)算函數(shù)需要,后者用于注入Recoil上下文
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<React.Suspense fallback={<div>Loading...</div>}>
<RecoilRoot>
<Demo />
</RecoilRoot>
</React.Suspense>
</React.StrictMode>,
rootElement
);
Concent初體驗(yàn)
如果讀過(guò)concent文檔(還在持續(xù)建設(shè)中...),可能部分人會(huì)認(rèn)為api太多,難于記住,其實(shí)大部分都是可選的語(yǔ)法糖,我們以counter為例,只需要使用到以下兩個(gè)api即可
run,定義模塊狀態(tài)(必需)、模塊計(jì)算(可選)、模塊觀察(可選)
運(yùn)行run接口后,會(huì)生成一份concent全局上下文
setState,修改狀態(tài)
定義狀態(tài)&修改狀態(tài)
以下示例我們先脫離ui,直接完成定義狀態(tài)&修改狀態(tài)的目的
import { run, setState, getState } from "concent";
run({
counter: {// 聲明一個(gè)counter模塊
state: { num: 1 }, // 定義狀態(tài)
}
});
console.log(getState('counter').num);// log: 1
setState('counter', {num:10});// 修改counter模塊的num值為10
console.log(getState('counter').num);// log: 10
我們可以看到,此處和redux很類(lèi)似,需要定義一個(gè)單一的狀態(tài)樹(shù),同時(shí)第一層key就引導(dǎo)用戶將數(shù)據(jù)模塊化管理起來(lái).
引入reducer
上述示例中我們直接掉一個(gè)呢setState修改數(shù)據(jù),但是真實(shí)的情況是數(shù)據(jù)落地前有很多同步的或者異步的業(yè)務(wù)邏輯操作,所以我們對(duì)模塊填在reducer定義,用來(lái)聲明修改數(shù)據(jù)的方法集合。
import { run, dispatch, getState } from "concent";
const delay = () => new Promise(r => setTimeout(r, 1000));
const state = () => ({ num: 1 });// 狀態(tài)聲明
const reducer = {// reducer聲明
inc(payload, moduleState) {
return { num: moduleState.num + 1 };
},
async asyncInc(payload, moduleState) {
await delay();
return { num: moduleState.num + 1 };
}
};
run({
counter: { state, reducer }
});
然后我們用dispatch來(lái)觸發(fā)修改狀態(tài)的方法
因dispatch會(huì)返回一個(gè)Promise,所以我們需要用一個(gè)async 包裹起來(lái)執(zhí)行代碼
import { dispatch } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await dispatch("counter/inc");// 同步修改
console.log(getState("counter").num);// log 2
await dispatch("counter/asyncInc");// 異步修改
console.log(getState("counter").num);// log 3
})()
注意dispatch調(diào)用時(shí)基于字符串匹配方式,之所以保留這樣的調(diào)用方式是為了照顧需要?jiǎng)討B(tài)調(diào)用的場(chǎng)景,其實(shí)更推薦的寫(xiě)法是
import { dispatch } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await dispatch(reducer.inc);// 同步修改
console.log(getState("counter").num);// log 2
await dispatch(reducer.asyncInc);// 異步修改
console.log(getState("counter").num);// log 3
})()
接入react
上述示例主要演示了如何定義狀態(tài)和修改狀態(tài),那么接下來(lái)我們需要用到以下兩個(gè)api來(lái)幫助react組件生成實(shí)例上下文(等同于與vue 3 setup里提到的渲染上下文),以及獲得消費(fèi)concent模塊數(shù)據(jù)的能力
register, 注冊(cè)類(lèi)組件為concent組件
useConcent, 注冊(cè)函數(shù)組件為concent組件
import { register, useConcent } from "concent";
@register("counter")
class ClsComp extends React.Component {
changeNum = () => this.setState({ num: 10 })
render() {
return (
<div>
<h1>class comp: {this.state.num}</h1>
<button onClick={this.changeNum}>changeNum</button>
</div>
);
}
}
function FnComp() {
const { state, setState } = useConcent("counter");
const changeNum = () => setState({ num: 20 });
return (
<div>
<h1>fn comp: {state.num}</h1>
<button onClick={changeNum}>changeNum</button>
</div>
);
}
注意到兩種寫(xiě)法區(qū)別很小,除了組件的定義方式不一樣,其實(shí)渲染邏輯和數(shù)據(jù)來(lái)源都一模一樣。
渲染它們查看結(jié)果
在線示例
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<div>
<ClsComp />
<FnComp />
</div>
</React.StrictMode>,
rootElement
);
對(duì)比Recoil,我們發(fā)現(xiàn)沒(méi)有頂層并沒(méi)有Provider或者Root類(lèi)似的組件包裹,react組件就已接入concent,做到真正的即插即用和無(wú)感知接入,同時(shí)api保留為與react一致的寫(xiě)法。
組件調(diào)用reducer
concent為每一個(gè)組件實(shí)例都生成了實(shí)例上下文,方便用戶直接通過(guò)ctx.mr調(diào)用reducer方法
mr 為 moduleReducer的簡(jiǎn)寫(xiě),直接書(shū)寫(xiě)為ctx.moduleReducer也是合法的
// --------- 對(duì)于類(lèi)組件 -----------
changeNum = () => this.setState({ num: 10 })
// ===> 修改為
changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx()
// --------- 對(duì)于函數(shù)組件 -----------
const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx
const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()
異步計(jì)算函數(shù)
run接口里支持?jǐn)U展computed屬性,即讓用戶定義一堆衍生數(shù)據(jù)的計(jì)算函數(shù)集合,它們可以是同步的也可以是異步的,同時(shí)支持一個(gè)函數(shù)用另一個(gè)函數(shù)的輸出作為輸入來(lái)做二次計(jì)算,計(jì)算的輸入依賴(lài)是自動(dòng)收集到的。
const computed = {// 定義計(jì)算函數(shù)集合
numx10({ num }) {
return num * 10;
},
// n:newState, o:oldState, f:fnCtx
// 結(jié)構(gòu)出num,表示當(dāng)前計(jì)算依賴(lài)是num,僅當(dāng)num發(fā)生變化時(shí)觸發(fā)此函數(shù)重計(jì)算
async numx10_2({ num }, o, f) {
// 必需調(diào)用setInitialVal給numx10_2一個(gè)初始值,
// 該函數(shù)僅在初次computed觸發(fā)時(shí)執(zhí)行一次
f.setInitialVal(num * 55);
await delay();
return num * 100;
},
async numx10_3({ num }, o, f) {
f.setInitialVal(num * 1);
await delay();
// 使用numx10_2再次計(jì)算
const ret = num * f.cuVal.numx10_2;
if (ret % 40000 === 0) throw new Error("-->mock error");
return ret;
}
}
// 配置到counter模塊
run({
counter: { state, reducer, computed }
});
上述計(jì)算函數(shù)里,我們刻意讓numx10_3在某個(gè)時(shí)候報(bào)錯(cuò),對(duì)于此錯(cuò)誤,我們可以在run接口的第二位options配置里定義errorHandler來(lái)捕捉。
run({/**storeConfig*/}, {
errorHandler: (err)=>{
alert(err.message);
}
})
當(dāng)然更好的做法,利用concent-plugin-async-computed-status插件來(lái)完成對(duì)所有模塊計(jì)算函數(shù)執(zhí)行狀態(tài)的統(tǒng)一管理。
import cuStatusPlugin from "concent-plugin-async-computed-status";
run(
{/**storeConfig*/},
{
errorHandler: err => {
console.error('errorHandler ', err);
// alert(err.message);
},
plugins: [cuStatusPlugin], // 配置異步計(jì)算函數(shù)執(zhí)行狀態(tài)管理插件
}
);
該插件會(huì)自動(dòng)向concent配置一個(gè)cuStatus模塊,方便組件連接到它,消費(fèi)相關(guān)計(jì)算函數(shù)的執(zhí)行狀態(tài)數(shù)據(jù)
function Test() {
const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({
module: "counter",// 屬于counter模塊,狀態(tài)直接從state獲得
connect: ["cuStatus"],// 連接到cuStatus模塊,狀態(tài)從connectedState.{$moduleName}獲得
});
const changeNum = () => setState({ num: state.num + 1 });
// 獲得counter模塊的計(jì)算函數(shù)執(zhí)行狀態(tài)
const counterCuStatus = connectedState.cuStatus.counter;
// 當(dāng)然,可以更細(xì)粒度的獲得指定結(jié)算函數(shù)的執(zhí)行狀態(tài)
// const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus;
return (
<div>
{state.num}
<br />
{counterCuStatus.done ? moduleComputed.numx10 : 'computing'}
{/** 此處拿到錯(cuò)誤可以用于渲染,當(dāng)然也拋出去 */}
{/** 讓ErrorBoundary之類(lèi)的組件捕捉并渲染降級(jí)頁(yè)面 */}
{counterCuStatus.err ? counterCuStatus.err.message : ''}
<br />
{moduleComputed.numx10_2}
<br />
{moduleComputed.numx10_3}
<br />
<button onClick={changeNum}>changeNum</button>
</div>
);
}
![]https://raw.githubusercontent...
查看在線示例
更新
開(kāi)篇我說(shuō)對(duì)Recoli提到的更新保持了懷疑態(tài)度,有一些誤導(dǎo)的嫌疑,此處我們將揭開(kāi)疑團(tuán)
大家知道hook使用規(guī)則是不能寫(xiě)在條件控制語(yǔ)句里的,這意味著下面語(yǔ)句是不允許的
const NumView = () => {
const [show, setShow] = useState(true);
if(show){// error
const [num, setNum] = useRecoilState(numState);
}
}
所以用戶如果ui渲染里如果某個(gè)狀態(tài)用不到此數(shù)據(jù)時(shí),某處改變了num值依然會(huì)觸發(fā)NumView重渲染,但是concent的實(shí)例上下文里取出來(lái)的state和moduleComputed是一個(gè)Proxy對(duì)象,是在實(shí)時(shí)的收集每一輪渲染所需要的依賴(lài),這才是真正意義上的按需渲染和更新。
const NumView = () => {
const [show, setShow] = useState(true);
const {state} = useConcent('counter');
// show為true時(shí),當(dāng)前實(shí)例的渲染對(duì)state.num的渲染有依賴(lài)
return {show ? <h1>{state.num}</h1> : 'nothing'}
}
點(diǎn)我查看代碼示例
當(dāng)然如果用戶對(duì)num值有ui渲染完畢后,有發(fā)生改變時(shí)需要做其他事的需求,類(lèi)似useEffect的效果,concent也支持用戶將其抽到setup里,定義effect來(lái)完成此場(chǎng)景訴求,相比useEffect,setup里的ctx.effect只需定義一次,同時(shí)只需傳遞key名稱(chēng),concent會(huì)自動(dòng)對(duì)比前一刻和當(dāng)前刻的值來(lái)決定是否要觸發(fā)副作用函數(shù)。
conset setup = (ctx)=>{
ctx.effect(()=>{
console.log('do something when num changed');
return ()=>console.log('clear up');
}, ['num'])
}
function Test1(){
useConcent({module:'cunter', setup});
return <h1>for setup<h1/>
}
更多關(guān)于effect與useEffect請(qǐng)查看此文
current mode
關(guān)于concent是否支持current mode這個(gè)疑問(wèn)呢,這里先說(shuō)答案,concent是100%完全支持的,或者進(jìn)一步說(shuō),所有狀態(tài)管理工具,最終觸發(fā)的都是setState或forceUpdate,我們只要在渲染過(guò)程中不要寫(xiě)具有任何副作用的代碼,讓相同的狀態(tài)輸入得到的渲染結(jié)果冪,即是在current mode下運(yùn)行安全的代碼。
current mode只是對(duì)我們的代碼提出了更苛刻的要求。
// bad
function Test(){
track.upload('renderTrigger');// 上報(bào)渲染觸發(fā)事件
return <h1>bad case</h1>
}
// good
function Test(){
useEffect(()=>{
// 就算僅執(zhí)行了一次setState, current mode下該組件可能會(huì)重復(fù)渲染,
// 但react內(nèi)部會(huì)保證該副作用只觸發(fā)一次
track.upload('renderTrigger');
})
return <h1>bad case</h1>
}
我們首先要理解current mode原理是因?yàn)閒iber架構(gòu)模擬出了和整個(gè)渲染堆棧(即fiber node上存儲(chǔ)的信息),得以有機(jī)會(huì)讓react自己以組件為單位調(diào)度組件的渲染過(guò)程,可以懸停并再次進(jìn)入渲染,安排優(yōu)先級(jí)高的先渲染,重度渲染的組件會(huì)切片為多個(gè)時(shí)間段反復(fù)渲染,而concent的上下文本身是獨(dú)立于react存在的(接入concent不需要再頂層包裹任何Provider), 只負(fù)責(zé)處理業(yè)務(wù)生成新的數(shù)據(jù),然后按需派發(fā)給對(duì)應(yīng)的實(shí)例(實(shí)例的狀態(tài)本身是一個(gè)個(gè)孤島,concent只負(fù)責(zé)同步建立起了依賴(lài)的store的數(shù)據(jù)),之后就是react自己的調(diào)度流程,修改狀態(tài)的函數(shù)并不會(huì)因?yàn)榻M件反復(fù)重入而多次執(zhí)行(這點(diǎn)需要我們遵循不該在渲染過(guò)程中書(shū)寫(xiě)包含有副作用的代碼原則),react僅僅是調(diào)度組件的渲染時(shí)機(jī),而組件的中斷和重入針對(duì)也是這個(gè)渲染過(guò)程。
所以同樣的,對(duì)于concent
const setup = (ctx)=>{
ctx.effect(()=>{
// effect是對(duì)useEffect的封裝,
// 同樣在current mode下該副作用也只觸發(fā)一次(由react保證)
track.upload('renderTrigger');
});
}
// good
function Test2(){
useConcent({setup})
return <h1>good case</h1>
}
同樣的,依賴(lài)收集在current mode模式下,重復(fù)渲染僅僅是導(dǎo)致觸發(fā)了多次收集,只要狀態(tài)輸入一樣,渲染結(jié)果冪等,收集到的依賴(lài)結(jié)果也是冪等的。
// 假設(shè)這是一個(gè)渲染很耗時(shí)的組件,在current mode模式下可能會(huì)被中斷渲染
function HeavyComp(){
const { state } = useConcent({module:'counter'});// 屬于counter模塊
// 這里讀取了num 和 numBig兩個(gè)值,收集到了依賴(lài)
// 即當(dāng)僅當(dāng)counter模塊的num、numBig的發(fā)生變化時(shí),才觸發(fā)其重渲染(最終還是調(diào)用setState)
// 而counter模塊的其他值發(fā)生變化時(shí),不會(huì)觸發(fā)該實(shí)例的setState
return (
<div>num: {state.num} numBig: {state.numBig}</div>
);
}
最后我們可以梳理一下,hook本身是支持把邏輯剝離到用的自定義hook(無(wú)ui返回的函數(shù)),而其他狀態(tài)管理也只是多做了一層工作,引導(dǎo)用戶把邏輯剝離到它們的規(guī)則之下,最終還是把業(yè)務(wù)處理數(shù)據(jù)交回給react組件調(diào)用其setState或forceUpdate觸發(fā)重渲染,current mode的引入并不會(huì)對(duì)現(xiàn)有的狀態(tài)管理或者新生的狀態(tài)管理方案有任何影響,僅僅是對(duì)用戶的ui代碼提出了更高的要求,以免因?yàn)閏urrent mode引發(fā)難以排除的bug
為此react還特別提供了React.Strict組件來(lái)故意觸發(fā)雙調(diào)用機(jī)制, https://reactjs.org/docs/stri... 以引導(dǎo)用戶書(shū)寫(xiě)更符合規(guī)范的react代碼,以便適配將來(lái)提供的current mode。
react所有新特性其實(shí)都是被fiber激活了,有了fiber架構(gòu),衍生出了hook、time slicing、suspense以及將來(lái)的Concurrent Mode,class組件和function組件都可以在Concurrent Mode下安全工作,只要遵循規(guī)范即可。
摘取自: https://reactjs.org/docs/stri...
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
所以呢,React.Strict其實(shí)為了引導(dǎo)用戶寫(xiě)能夠在Concurrent Mode里運(yùn)行的代碼而提供的輔助api,先讓用戶慢慢習(xí)慣這些限制,循序漸進(jìn)一步一步來(lái),最后再推出Concurrent Mode。
結(jié)語(yǔ)
Recoil推崇狀態(tài)和派生數(shù)據(jù)更細(xì)粒度控制,寫(xiě)法上demo看起來(lái)簡(jiǎn)單,實(shí)際上代碼規(guī)模大之后依然很繁瑣。
// 定義狀態(tài)
const numState = atom({key:'num', default:0});
const numBigState = atom({key:'numBig', default:100});
// 定義衍生數(shù)據(jù)
const numx2Val = selector({
key: "numx2",
get: ({ get }) => get(numState) * 2,
});
const numBigx2Val = selector({
key: "numBigx2",
get: ({ get }) => get(numBigState) * 2,
});
const numSumBigVal = selector({
key: "numSumBig",
get: ({ get }) => get(numState) + get(numBigState),
});
// ---> ui處消費(fèi)狀態(tài)或衍生數(shù)據(jù)
const [num] = useRecoilState(numState);
const [numBig] = useRecoilState(numBigState);
const numx2 = useRecoilValue(numx2Val);
const numBigx2 = useRecoilValue(numBigx2Val);
const numSumBig = useRecoilValue(numSumBigVal);
Concent遵循redux單一狀態(tài)樹(shù)的本質(zhì),推崇模塊化管理數(shù)據(jù)以及派生數(shù)據(jù),同時(shí)依靠Proxy能力完成了運(yùn)行時(shí)依賴(lài)收集和追求不可變的完美整合。
run({
counter: {// 聲明一個(gè)counter模塊
state: { num: 1, numBig: 100 }, // 定義狀態(tài)
computed:{// 定義計(jì)算,參數(shù)列表里解構(gòu)具體的狀態(tài)時(shí)確定了依賴(lài)
numx2: ({num})=> num * 2,
numBigx2: ({numBig})=> numBig * 2,
numSumBig: ({num, numBig})=> num + numBig,
}
},
});
// ---> ui處消費(fèi)狀態(tài)或衍生數(shù)據(jù),在ui處結(jié)構(gòu)了才產(chǎn)生依賴(lài)
const { state, moduleComputed, setState } = useConcent('counter')
const { numx2, numBigx2, numSumBig} = moduleComputed;
const { num, numBig } = state;
所以你將獲得:
運(yùn)行時(shí)的依賴(lài)收集 ,同時(shí)也遵循react不可變的原則
一切皆函數(shù)(state, reducer, computed, watch, event...),能獲得更友好的ts支持
支持中間件和插件機(jī)制,很容易兼容redux生態(tài)
同時(shí)支持集中與分形模塊配置,同步與異步模塊加載,對(duì)大型工程的彈性重構(gòu)過(guò)程更加友好
藍(lán)藍(lán)設(shè)計(jì)( m.sillybuy.com )是一家專(zhuān)注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)
藍(lán)藍(lán)設(shè)計(jì)的小編 http://m.sillybuy.com