开源不易,感激你的反对,❤ 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
非常简单,只须要在根组件之前,先应用run
api启动即可,因而处咱们没有模块定义,间接调用就能够了。
import { run } from 'concent';run();// 先启动,在renderReactDOM.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.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
- 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的疑难,能够扫码加群征询,会尽力答疑解惑,帮忙你理解更多????。