开源不易,感谢你的支持,❤ 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'});
往期文章