[Concent小課堂]認識組合api,換個姿式擼更清爽的react

開源不易,感謝你的支持,❤ 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的痛點吧^_^編程

react hook

咱們在此先設計一個傳統的計數器,要求以下redux

  • 有一個小數,一個大數
  • 有兩組加、減按鈕,分別對小數大數作操做,小數按鈕加減1,大數按鈕加減100
  • 計數器初次掛載時拉取歡迎問候語
  • 當小數達到100時,按鈕變爲紅色,不然變爲綠色
  • 當大數達到1000時,按鈕變爲紫色,不然變爲綠色
  • 當大數達到10000時,上報大數的數字
  • 計算器卸載時,上報當前的數字

爲了完成此需求,咱們須要用到如下5把鉤子segmentfault

useState

過完需求,咱們須要用到第一把鉤子useState來作組件首次渲染的狀態初始化api

function Counter() {
  const [num, setNum] = useState(6);
  const [bigNum, setBigNum] = useState(120);
}

useCallback

如需使用緩存函數,則要用到第二把鉤子useCallback,此處咱們使用這把鉤子來定義加減函數數組

const addNum = useCallback(() => setNum(num + 1), [num]);
  const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);

useMemo

如需用到緩存的計算結果,則要用到第三把鉤子useMemo,此處咱們使用這把鉤子來計算按鈕顏色緩存

const numBtnColor = useMemo(() => {
    return num > 100 ? 'red' : 'green';
  }, [num]);
  const bigNumBtnColor = useMemo(() => {
    return bigNum > 1000 ? 'purple' : 'green';
  }, [bigNum]);

useEffect

處理函數的反作用則需用到第四把鉤子useEffect,此處咱們用來處理一下兩個需求

  • 當大數達到10000時,上報大數的數字
  • 計算器卸載時,上報當前的數字
useEffect(() => {
    if (bigNum > 10000) api.report('reach 10000')
  }, [bigNum])
  useEffect(() => {
    return ()=>{
      api.reportStat(num, bigNum)
    }
  }, [])

useRef

上面使用清理函數的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}
}

concent setup

hook函數在每一輪渲染期間必定是須要所有從新執行一遍的,因此不可避免的在每一輪渲染期間都會產生大量的臨時閉包函數,若是咱們能省掉他們,的確能幫gc減輕一些回收壓力的,如今咱們來看看使用setup改造完畢後的Counter會是什麼樣子吧。

使用concent很是簡單,只須要在根組件以前,先使用runapi啓動便可,所以處咱們沒有模塊定義,直接調用就能夠了。

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

initState用於初始化狀態,替代了useState,當咱們的組件狀態較大時依然能夠不用考慮如何切分狀態粒度。

computed

computed用於定義計算函數,從參數列表裏解構時就肯定了計算的輸入依賴,相比useMemo,更直接與優雅。

effect

effect的用於和useEffect如出一轍,使用體驗僅僅是依賴出傳入key名稱便可,同時effect內部將函數組件和類組件的生命週期進行了統一封裝,用戶能夠將業務不作任何修改便遷移到類組件身上

裝配setup

如今,你能夠在任意函數組件內部使用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.statethis.ctx.state是等效的,this.setStatethis.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也更加友好了,相信你們都已默認了hookreact的一個重要發明,可是其實它不是針對用戶的,而是針對框架的,用戶實際上是不須要了解那些燒腦的細節與規則的,而對於concent用戶來講,其實只需一個鉤子開啓一個傳送門,便可在另外一個空間內部實現全部業務邏輯,並且這些邏輯一樣能夠複用到類組件上。

親愛的客官看了這麼多,還不趕忙上手試試,如下提供了兩種寫法的連接,供你把玩😀

  • one more thing

    上訴兩個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'});

    往期文章

    ❤ star me if you like concent ^_^

    Edit on CodeSandbox
    https://codesandbox.io/s/concent-guide-xvcej

    Edit on StackBlitz
    https://stackblitz.com/edit/cc-multi-ways-to-wirte-code

    相關文章
    相關標籤/搜索