开源不易,感谢你的支持,❤ star concent^_^
[Concent速成]是一个帮助新手极速入门concent
的系列文章,0障碍地学习和理解concent状态管理思路。
虽然学习和使用过redux
和mbox
之类的状态管理库,阅读此篇文章会更容易理解,但是没有使用过任何状态管理库的用户也能极速入门concent
,真正的0障碍学会使用它并接入到你的react应用里。
注意上面强调了0障碍,包括了学会使用和接入应用两个方面,为了达到此目的,api要足够简单,简单到什么程度呢?简单到无以复加,简单到和react
保持100%一致,让新手无需理解额外的概览,以react组件的编写方式就能接入状态管理,但是呢也保留了更高级的抽象接口,让老手可以按照redux
的模式去组织代码。
来吧,展示!本期讲解的关键api,包括一个3个顶层apirun
、useConcent
、register
,一个实例上下文apisetState
,学会使用这4个api,你就已经会使用concent做为你的状态管理方案了。
所有的框架都会以Hello world
作为引导,我们此处也不例外,看看concent
版本的Hello world
是多么的简单。
concent和redux一样,有一个全局单一的状态树,是一个普通的json对象,不过第一层key规划为模块名,来帮助用户按照业务场景将状态切分为多个模块,便于分开管理。
此处我们需要用到run
接口启动concent并载入模块配置,配置一个名为hello
的模块,并为其定义状态
import { run } from 'concent'; run({ hello: { state: { greeting: 'Hello world' }, }, });
定义好了模块,我们的组件需要消费模块的状态,对于类组件,使用register
即可
import { register } from 'concent'; @register('hello') class HelloCls extends React.Component{ state = { greeting: '' }; changeGreeting = (e)=> this.setState({greeting: e.target.value}) render(){ return <input value={this.state.greeting} onChange={this.changeGreeting} /> } }
上诉代码用register
接口将HelloCls
组件注册属于hello
模块,concent将向当前组件this
上注入一个实例上下文ctx
,用于读取数据和调用修改方法,同时也默默替换了this上的state
和setState
,方便用户可以0改动原组件的代码,仅使用register
装饰一下类组件即可接入状态管理,这就是0障碍学会使用并接入到react应用的基础,对于初学者来说,你会写react组件,就已经会使用了concent,没有任何额外的学习成本。
this.state === this.ctx.state; // true this.setState === this.ctx.setState; // true
上述代码里,还声明了一个类成员变量state
等于 { greeting: '' }
,因为greeting
和模块状态里的重名了,所以首次渲染之前它的值将被替换为模块里的Hello world
,实际上这里可以不声明这个类成员变量state
,写上它只是为了保证删除register
装饰器这个组件也能正常工作,而不会得到一个undefined
的greeting
初始值。
使用useConcent
接口注册当前组件所属模块,useConcent
会返回当前组件的实例上下文对象ctx
,等同于上面类组件的this.ctx
,我们只需要解构它取出state
和setState
即可。
import { useConcent } from 'concent'; function HelloFn(){ const { state, setState } = useConcent('hello'); const changeGreeting = (e)=> setState({greeting: e.target.value}) return <input value={state.greeting} onChange={changeGreeting} /> }
最后我们看下完整的代码吧,我们发现顶层无Provider
之类的组件包裹根组件,因为concent没有依赖React Context api
实现状态管理,而是自己独立维护了一个独立的全局上下文,所以你在已有的项目里接入concent是非常容易的,即插即用,无需任何额外的改造。
由于HelloCls
和HelloFn
组件都属于hello
模块,它们中的任意一个实例修改模块状态,concent会将其存储到store,并同步到其它同属于hello
模块的实例上,状态共享就是这么简单。
import ReactDOM from 'react-dom'; import { run } from 'concent'; import { register, useConcent } from 'concent'; run({/** 略 */}); @register('hello') class HelloCls extends React.Component{/** 略 */} function HelloFn(){/** 略 */} const App = ()=>( <div> <HelloCls /> <HelloFn /> </div> ); ReactDOM.render(App, document.getElementById('root'));
点我查看源码
无论是类组件还是函数组件,拿到state
对象已被转换为一个Proxy
代理对象,负责收集当前渲染的数据依赖,所以如果是有条件判断的读取状态,推荐采用延迟解构的写法,让每一次渲染都锁定最小的依赖列表,减少冗余渲染,获得更好的性能。
function HelloFn(){ const { state, setState, syncBool } = useConcent({module:'hello', state:{show:true}}); const changeGreeting = (e)=> setState({greeting: e.target.value}); // 当show为true时,当前实例的依赖是['greeting'],其他任意地方修改了greeting值都会触发当前实例重渲染 // 当show为false时,当前实例无依赖,其他任意地方修改了greeting值不会影响当前实例重渲染 return ( <> {state.show?<input value={state.greeting} onChange={changeGreeting} />:'no input'} <button onClick={syncBool('show')}>toggle show</button> </> ); }
当组件需要消费多个模块的数据时,可使用connect
参数来声明要连接的多个模块。
如下面示例,连接bar和baz两个模块,通过ctx.connectedState
获取目标模块状态:
@register({connect:['bar', 'baz']}) class extends React.Component{ render(){ const { bar, baz } = this.ctx.connectedState; } }
从connectedState
拿到的模块状态依然存在着依赖收集行为,所以如果存在条件渲染语句,推荐延迟解构写法
通过调用实例上下文apictx.setModuleState
修改目标模块状态
changeName = e=> this.ctx.setModuleState('bar', {name: e.target.value})
此文仅仅演示了最最基础的api用法,帮助你快速上手concent,如果你已经是老司机,特别是vue3 one piece
已宣布正式发布的这个关头,如果你非常的不屑一顾这样笨拙的代码组织方式,暂先不要急着否定它,且打开官网看一下其他特性,一定有你喜欢的亮点,包括为react 量身定制的composition api,模块级别的reducer
、computed
、watch
、lifecycle
等等新特性,后面的速成会一一提到。
Concent携带一整套完整的方案,支持渐进式的开发react组件,即不干扰react本身的开发哲学和组件形态,同时也能够获得巨大的性能收益,这意味着我们可以至下而上的增量式的迭代,状态模块的划分,派生数据的管理,事件模型的分类,业务代码的分隔都可以逐步在开发过程勾勒和剥离出来,其过程是丝滑柔顺的,也允许我们至上而下统筹式的开发,一开始吧所有的领域模型和业务模块抽象的清清楚楚,同时在迭代过程中也能非常快速的灵活调整而影响整个项目架构.