[Concent 速成] (1) 定义和共享模块状态

fantasticsoul[Concent 速成] (1) 定义和共享模块状态插图

开源不易,感谢你的支持,❤ star concent^_^

序言

**[Concent 速成]**是一个帮助新手极速入门concent的系列文章,0 障碍地学习和理解 concent 状态管理思路。

虽然学习和使用过reduxmbox之类的状态管理库,阅读此篇文章会更容易理解,但是没有使用过任何状态管理库的用户也能极速入门concent,真正的0 障碍学会使用它并接入到你的 react 应用里。

注意上面强调了0 障碍,包括了学会使用和接入应用两个方面,为了达到此目的,api 要足够简单,简单到什么程度呢?简单到无以复加,简单到和react保持 100%一致,让新手无需理解额外的概览,以 react 组件的编写方式就能接入状态管理,但是呢也保留了更高级的抽象接口,让老手可以按照redux的模式去组织代码。

来吧,展示!本期讲解的关键 api,包括一个 3 个顶层 apirunuseConcentregister,一个实例上下文 apisetState,学会使用这 4 个 api,你就已经会使用 concent 做为你的状态管理方案了。

Hello world

所有的框架都会以Hello world作为引导,我们此处也不例外,看看concent版本的Hello world是多么的简单。

run 定义模块

concent 和 redux 一样,有一个全局单一的状态树,是一个普通的 json 对象,不过第一层 key 规划为模块名,来帮助用户按照业务场景将状态切分为多个模块,便于分开管理。

此处我们需要用到run接口启动 concent 并载入模块配置,配置一个名为hello的模块,并为其定义状态

import { run } from 'concent';

run({
  hello: {
    state: { greeting: 'Hello world' },
  },
});

register 注册类组件

定义好了模块,我们的组件需要消费模块的状态,对于类组件,使用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 上的statesetState,方便用户可以 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装饰器这个组件也能正常工作,而不会得到一个undefinedgreeting初始值。

useConcent 注册函数组件

使用useConcent接口注册当前组件所属模块,useConcent会返回当前组件的实例上下文对象ctx,等同于上面类组件的this.ctx,我们只需要解构它取出statesetState即可。

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 是非常容易的,即插即用,无需任何额外的改造。

由于HelloClsHelloFn组件都属于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参数来声明要连接的多个模块。

使用 connect 参数连接多个模块

如下面示例,连接 bar 和 baz 两个模块,通过ctx.connectedState获取目标模块状态:

@register({connect:['bar', 'baz']})
class extends React.Component{
  render(){
    const { bar, baz } = this.ctx.connectedState;
  }
}

connectedState拿到的模块状态依然存在着依赖收集行为,所以如果存在条件渲染语句,推荐延迟解构写法

使用 setModuleState 修改状态

通过调用实例上下文 apictx.setModuleState修改目标模块状态

changeName = e=> this.ctx.setModuleState('bar', {name: e.target.value})

结语

此文仅仅演示了最最基础的 api 用法,帮助你快速上手 concent,如果你已经是老司机,特别是vue3 one piece已宣布正式发布的这个关头,如果你非常的不屑一顾这样笨拙的代码组织方式,暂先不要急着否定它,且打开官网看一下其他特性,一定有你喜欢的亮点,包括为 react 量身定制的composition api,模块级别的reducercomputedwatchlifecycle等等新特性,后面的速成会一一提到。

Concent 携带一整套完整的方案,支持渐进式的开发 react 组件,即不干扰 react 本身的开发哲学和组件形态,同时也能够获得巨大的性能收益,这意味着我们可以至下而上的增量式的迭代,状态模块的划分,派生数据的管理,事件模型的分类,业务代码的分隔都可以逐步在开发过程勾勒和剥离出来,其过程是丝滑柔顺的,也允许我们至上而下统筹式的开发,一开始吧所有的领域模型和业务模块抽象的清清楚楚,同时在迭代过程中也能非常快速的灵活调整而影响整个项目架构.

我发现我不喜欢用 react-hooks

cl903254852:react-hooks 更适合写逻辑简单的小组件,遇到逻辑复杂的组件相较 class component 更难维护(不要跟我说逻辑拆分什么的)。 举个例子,我维护同事写的页面,如果是 class component 我一眼就能看明白整个组件结构,一下就能瞄到 componentDidMount 这是我个人的感想whincwu142:可…

与哪些运算符>>兼容 - java

我这里没有什么代码int b=3; b=b >> 1; System.out.println(b); 它可以完美工作,但是当我将变量b更改为byte,short,float,double时,它包含错误,但是对于变量int和long来说,它可以完美工作,为什么它不能与其他变量一起工作? 参考方案 位移位运算符(例如>>)与任何整数类型兼…

想问下 React 的主流生态

ryougifujino:一直在用 Vue,都是官方的生态觉得很清晰。 最近想练习一下 React 及其周边生态,但是有点无从下手的感觉。 1 、想问下主流生态有哪些呢? 2 、dva 、umijs 、redux 、mobx 这些分别有什么用,和 Vue 生态里的哪些东西对应呢? 3 、还有 ant-design 那套也是挺懵逼的,ant-design-pr…

>> Python中的运算符 - python

>>运算符做什么?例如,以下操作10 >> 1 = 5有什么作用? 参考方案 它是右移运算符,将所有位“右移”一次。二进制10是1010移到右边变成0101这是5

后端代码狗想不那么系统的学习 React.求推荐学习路线和学习资料

chaleaoch:有点 JQuery 经验,不用太系统,也不用什么都会.大概懂就行.除了 JS 还需要其他前置学习条件吗? 譬如 typescript?其实我还真挺想学习一下 typescript 的.比较严谨啊. N 年前想做全栈工程师来着, 但是因为接受不了(记性不好)JS 语法的各种陷阱放弃了.谢谢大佬.murmur:不学 redux,你会发现 re…