開源不易,感謝你的支持,❤ star me if you like concent ^_^vue
這裏有一份收集中的狀態管理清單,歡迎有興趣的朋友瞭解^_^
awesome-statereact
composition api
(組合api) 和 optional api
(可選api) 兩種組織代碼的方式,相信你們在vue3
各類相關的介紹文裏已經瞭解到很多了,它們能夠同時存在,並不是強制你只能使用哪種,但組合api兩大優點的確讓開發者們更傾向於使用它來替代可選api。git
以上兩點在react裏均被hook
優雅的解決了,那麼相比hook
,組合api還具備什麼優點呢?這裏就不賣關子了,相信已有小夥伴在尤大大介紹組合api時已經知道,組合api是靜態定義的,解決了hook
必需每次渲染都從新生成臨時閉包函數的性能問題,也沒有了hook
裏閉包舊值陷阱,人工檢測依賴等編碼體驗問題。github
可是,react是all in js的編碼方式,因此只要咱們敢想、敢作,一切優秀的編程模型均可以吸納進來,接下來咱們用原生hook
和concent的setup
並經過實例和講解,來完全解決尤大提到的這個關於hook
的痛點吧^_^編程
咱們在此先設計一個傳統的計數器,要求以下redux
爲了完成此需求,咱們須要用到如下5把鉤子segmentfault
過完需求,咱們須要用到第一把鉤子useState
來作組件首次渲染的狀態初始化api
function Counter() { const [num, setNum] = useState(6); const [bigNum, setBigNum] = useState(120); }
如需使用緩存函數,則要用到第二把鉤子useCallback
,此處咱們使用這把鉤子來定義加減函數數組
const addNum = useCallback(() => setNum(num + 1), [num]); const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);
如需用到緩存的計算結果,則要用到第三把鉤子useMemo
,此處咱們使用這把鉤子來計算按鈕顏色緩存
const numBtnColor = useMemo(() => { return num > 100 ? 'red' : 'green'; }, [num]); const bigNumBtnColor = useMemo(() => { return bigNum > 1000 ? 'purple' : 'green'; }, [bigNum]);
處理函數的反作用則需用到第四把鉤子useEffect
,此處咱們用來處理一下兩個需求
useEffect(() => { if (bigNum > 10000) api.report('reach 10000') }, [bigNum]) useEffect(() => { return ()=>{ api.reportStat(num, bigNum) } }, [])
上面使用清理函數的useEffect
寫法在IDE是會被警告的,由於內部使用了num, bigNum
變量(不寫依賴會陷入閉包舊值陷阱),因此要求咱們聲明依賴
但是若是爲了不IDE警告,咱們改成以下方式顯然不是咱們表達的本意,咱們只是想組件卸載時報告一下數字,而不是每一輪渲染都觸發清理函數
useEffect(() => { return ()=>{ api.reportStat(num, bigNum) } }, [num, bigNum])
這個時候咱們須要第5把鉤子useRef
,來幫忙咱們固定依賴了,因此正確的寫法是
const ref = useRef();// ref是一個固定的變量,每一輪渲染都指向同一個值 ref.current = {num, bigNum};// 幫咱們記住最新的值 useEffect(() => { return () => { const {num, bigNum} = ref.current; reportStat(num, bigNum); }; }, [ref]);
使完5把鉤子,咱們完整的組件以下
function Counter() { const [num, setNum] = useState(88); const [bigNum, setBigNum] = useState(120); const addNum = useCallback(() => setNum(num + 1), [num]); const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]); const numBtnColor = useMemo(() => { return num > 100 ? "red" : "green"; }, [num]); const bigNumBtnColor = useMemo(() => { return bigNum > 1000 ? "purple" : "green"; }, [bigNum]); useEffect(() => { if (bigNum > 10000) report("reach 10000"); }, [bigNum]); const ref = useRef(); ref.current = {num, bigNum}; useEffect(() => { return () => { const {num, bigNum} = ref.current; reportStat(num, bigNum); }; }, [ref]); // render ui ... }
固然咱們能夠基於hook
可定製的特性,將這段代碼單獨抽象爲一個鉤子,這樣的話只需將數據和方法導出,以便讓多種ui表達的Counter組件能夠複用,同時也作到ui與業務隔離,利於維護。
function useMyCounter(){ // .... 略 return { num, bigNum. addNum, addNumBig, numBtnColor, bigNumBtnColor} }
hook
函數在每一輪渲染期間必定是須要所有從新執行一遍的,因此不可避免的在每一輪渲染期間都會產生大量的臨時閉包函數,若是咱們能省掉他們,的確能幫gc減輕一些回收壓力的,如今咱們來看看使用setup
改造完畢後的Counter會是什麼樣子吧。
使用concent
很是簡單,只須要在根組件以前,先使用run
api啓動便可,所以處咱們沒有模塊定義,直接調用就能夠了。
import { run } from 'concent'; run();// 先啓動,在render ReactDOM.render(<App />, rootEl)
接着咱們將以上邏輯稍加改造,所有包裹到setup
內部,注意哦,setup函數內部的邏輯只會被執行一次
function setup(ctx) {// 渲染上下文 const { initState, computed, effect, state, setState } = ctx; // 初始化數據 initState({ num: 6, bigNum: 120 }); // 定義計算函數 computed({ // 參數列表解構時就肯定了計算的輸入依賴 numBtnColor: ({ num }) => num > 100 ? 'red' : 'green', bigNumBtnColor: ({ bigNum }) => bigNum > 1000 ? 'purple' : 'green', }); // 定義反作用 effect(() => { if (state.bigNum > 10000) api.report('reach 10000') }, ['bigNum']) effect(() => { return () => { api.reportStat(state.num, state.bigNum) } }, []); return {// 導出方法 addNum: () => setState({ num: state.num + 1 }), addNumBig: () => setState({ bigNum: state.bigNum + 100 }), } }
initState
用於初始化狀態,替代了useState
,當咱們的組件狀態較大時依然能夠不用考慮如何切分狀態粒度。
computed
用於定義計算函數,從參數列表裏解構時就肯定了計算的輸入依賴,相比useMemo
,更直接與優雅。
effect
的用於和useEffect
如出一轍,使用體驗僅僅是依賴出傳入key名稱便可,同時effect
內部將函數組件和類組件的生命週期進行了統一封裝,用戶能夠將業務不作任何修改便遷移到類組件身上
如今,你能夠在任意函數組件內部使用useConcent
裝配咱們定義好的setup
,它會返回一個渲染上下文(也可稱爲實例上下文),方便咱們按需獲取目標數據和方法,針對此示例,咱們能夠導出state
(數據),settings
(setup打包返回的法法),refComputed
(實例的計算函數結果容器)這3個key來使用便可。
import { useConcent } from 'concent'; function NewCounter() { const { state, settings, refComputed } = useConcent(setup); // const { num, bigNum } = state; // const { addNum, addNumBig } = settings; // const { numBtnColor, bigNumBtnColor } = refComputed; }
咱們上面提到setup
一樣能夠裝配給類組件,使用register
便可,須要注意的是裝配後的類組件,能夠從this.ctx
上直接獲取concent
爲其生成的渲染上下文,同時呢this.state
和this.ctx.state
是等效的,this.setState
和this.ctx.setState
也是等效的,方便用戶代碼0改動便可接入concent
使用。
import { register } from 'concent'; @register(setup) class NewClsCounter extends Component{ render(){ const { state, settings, refComputed } = this.ctx; } }
對比原生hook,setup
將業務邏輯固定在只會被執行一次的函數內部,提供了更友好的api,且同時完美兼容類組件與函數組件,讓用戶能夠逃離hook
的使用規則煩惱(想一想看 useEffect 配合 useRef,是否是都有不小的認知成本?),而不是將這些約束學習障礙轉嫁給用戶, 同時對gc也更加友好了,相信你們都已默認了hook
是react
的一個重要發明,可是其實它不是針對用戶的,而是針對框架的,用戶實際上是不須要了解那些燒腦的細節與規則的,而對於concent用戶來講,其實只需一個鉤子開啓一個傳送門,便可在另外一個空間內部實現全部業務邏輯,並且這些邏輯一樣能夠複用到類組件上。
親愛的客官看了這麼多,還不趕忙上手試試,如下提供了兩種寫法的連接,供你把玩😀
上訴兩個hook Counter若是想作狀態共享,咱們須要改造代碼接入redux
或者自建Context
,可是在concent
的開發模式下,setup
無需任何改造,僅僅只須要提早聲明一個模塊,而後註冊組件內屬於該模塊便可,這種絲滑般的遷移過程可讓用戶靈活應對各類複雜場景。
import { run } from 'concent'; run({ counter:{ state: { num:88, bigNum: 120 }, }, //reducer: {...}, // 如操做數據流程複雜,可再將業務提高到此處 }) // 對於函數組件 useConcent({setup}); // ---> 改成 useConcent({setup, module:'counter'}) // 對於函數組件 @register({setup}); // ---> 改成 @register({setup, module:'counter'});
往期文章