关于javascript:Concent小课堂认识组合api换个姿势撸更清爽的react

12次阅读

共计 5321 个字符,预计需要花费 14 分钟才能阅读完成。

开源不易,感激你的反对,❤ star me if you like concent ^_^

这里有一份收集中的状态治理清单,欢送有趣味的敌人理解 ^_^
awesome-state

序言

composition api(组合 api)和 optional api(可选 api)两种组织代码的形式,置信大家在 vue3 各种相干的介绍文里曾经理解到不少了,它们能够同时存在,并非强制你只能应用哪一种,但组合 api 两大劣势确实让开发者们更偏向于应用它来代替可选 api。

  • 以函数为根底单位来打包可复用逻辑,并注入到任意组件,让视图和业务解耦更优雅
  • 让雷同性能的业务更加严密的搁置到一起,不被割裂开,进步开发与保护体验

以上两点在 react 里均被 hook 优雅的解决了,那么相比 hook,组合 api 还具备什么劣势呢?这里就不卖关子了,置信已有小伙伴在尤大大介绍组合 api 时曾经晓得,组合 api 是动态定义的,解决了hook 必须每次渲染都从新生成长期闭包函数的性能问题,也没有了 hook 里闭包旧值陷阱,人工检测依赖等编码体验问题。

然而,react 是 all in js 的编码方式,所以只有咱们敢想、敢做,所有优良的编程模型都能够吸纳进来,接下来咱们用原生 hook 和 concent 的 setup 并通过实例和解说,来彻底解决尤大提到的这个对于 hook 的痛点吧 ^_^

react hook

咱们在此先设计一个传统的计数器,要求如下

  • 有一个小数,一个大数
  • 有两组加、减按钮,别离对小数大数做操作,小数按钮加减 1,大数按钮加减 100
  • 计数器首次挂载时拉取欢送问候语
  • 当小数达到 100 时,按钮变为红色,否则变为绿色
  • 当大数达到 1000 时,按钮变为紫色,否则变为绿色
  • 当大数达到 10000 时,上报大数的数字
  • 计算器卸载时,上报以后的数字

为了实现此需要,咱们须要用到以下 5 把钩子

useState

过完需要,咱们须要用到第一把钩子 useState 来做组件首次渲染的状态初始化

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 用户来说,其实只需一个钩子开启一个传送门,即可在另一个空间外部实现所有业务逻辑,而且这些逻辑同样能够复用到类组件上。

敬爱的客官看了这么多,还不连忙上手试试,以下提供了两种写法的链接,供你把玩????

    • 原始 hook Counter
    • setup Counter

    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'});
    • shared Counter

    往期文章

    • 细聊 Concent & Recoil , 摸索 react 数据流的新开发模式
    • redux、mobx、concent 个性大比拼, 看后生如何对局前辈

    ❤ star me if you like concent ^_^

    Edit on CodeSandbox

    Edit on StackBlitz

    如果有对于 concent 的疑难,能够扫码加群征询,会尽力答疑解惑,帮忙你理解更多????。

    正文完
     0