为 React 赋能的 concent 是什么,何以值得一试?

welcome star

你的star将是我最大的精神鼓励,欢迎star🥺🥺🥺javascript

concent是一个专为react提供状态管理服务的框架,提炼现有各大框架的精华,以及社区公认的最佳实践,经过良好的模块设计,既保证react的最佳性能又容许用户很是灵活的解耦UI逻辑与业务逻辑的关系,从总体上提升代码的可读性可维护性可扩展性css

concent携带如下特性vue

  • 核心api少且简单,功能强大,上手容易,入侵小,容易调试
  • 提供全局模块化的单一数据源
  • 支持0入侵的方式,渐进式的重构已有react代码
  • 对组件扩展了事件总线、computed、watch、双向绑定等特性
  • 完美支持function组件
  • 基于引用定位和状态广播,支持细粒度的状态订阅,渲染效率出众
  • 支持中间件,能够扩展你的个性化插件处理数据变动
  • 支持react 0.10+任意版本;

为用户提供更温馨和简单的react编码体验java

精心的模块设计理念

state

concent对模块的定义是通过对实际业务场景反复思考和推敲,最终得出的答案,首先,数据是模块的灵魂,承载着对你的功能模块的最基础的字符描述,离开数据,一切上层业务功能都是空谈,因此state是模块里的必包含的定义。react

reducer

修改数据的方式灵活度是concent提供给用户惊喜之一,由于concent的核心是经过接管setState作状态管理,因此用户接入concent那一刻能够无需当即改造现有的代码就可以享受到状态管理的好处,一样的,concent也支持用户定义reducer函数修改状态,这也是推荐的最佳实践方式,能够完全解耦UI渲染与业务逻辑,由于reducer函数本质上只是setState的变种写法,因此强调的是老是返回须要更新的片断状态,并且因为concent支持reducer函数之间相互调用,任意组合,因此能够容许用户按需任意切割reducer函数对状态的更新粒度,而后造成链式调用关系,而后经过dispatch句柄来触发reducer函数git

cc-dispatch

若是链式调用层级过深,会形成不少次渲染,从上图中能够看出有3个函数返回新的片断状态,形成3次渲染,因此concent也一样提供lazyDispatch句柄来让用户能够有多一种选择来触发对reducer函数的调用,concent会在调动过程当中自动缓存当前调用链上全部属于同一个模块的状态并作合并,直到调用链结束,才作一次性的提交github

cc-lazy-dispatch

computed

computed提供一个入口定义须要对发生变化的key作计算的函数,一般来讲,大部分state的数据并不是是UI渲染直接须要的数据,咱们一般须要对其作一些格式化或者转换操做,可是这些操做其实没有必要再每次渲染前都作一遍,computed将只对发生了变化的key计算并将其结果缓存起来。web

watch

watchcomputed最大的不一样是,不须要返回一个具体的结果,一般用于在关心某些key变化时,作一些异步操做,就能够对这些key定义watch函数vuex

init

咱们知道state的定义是同步的,init容许用户有一次对state作异步获取并改写的机会,注意,若是此时存在着该模块的实例,改写了模块的状态后,concent会自动将这些状态广播到对应的实例上,一样的,若是不存在,在有些的该模块的实例生成时,这些实例将同步到模块最新的状态,因此当咱们有一些状态不是须要依赖实例挂载上且触发componentDidMount来获取的时候,就能够将状态的初始化提高到模块的inittypescript

cc-lazy-dispatch

灵活的模块和组件映射关系

模块是先于组件存在的概念,当咱们有了模块的定义后,即可以对组件提供强有力的支持,concent里经过register函数将react组件注册为concent组件(也称之为concent类)

cc-lazy-dispatch

注册的时候,能够指定专属的模块,理论来讲,咱们应该保持组件与模块干净的对应关系,即一个组件专属于某个模块,消费的是该模块的数据,操做的所属模块的reducer函数,可是实际场景可能有很多组件都是跨多个模块消费和修改数据的,因此concent也容许用户经过connect定义来指定组件链接的其余模块,惟一不一样的是调用句柄默认带的上下文是指向本身专属模块的,若是须要调用其余模块的方法,则须要显示指定模块名

@register('Foo', {module:'foo', connect:{bar:'*'}})
class Foo extends Component(){
  onNameChange = (name)=>{
    this.$$dispatch('changeName', name);//默认调用的foo模块reducer里的changeName方法

    this.$$dispatch('bar/changeName', name);//指定bar模块, 调用bar模块的reducer里的changeName方法修改bar模块的数据
  }
}
复制代码

cc-ccclass-module
对于 CcClass来讲,由于调用 setState就可以修改store,因此数据是直接注入到 state里的,对于其余模块的数据,是注入到 connectedState,这样既保持了所属模块和其余模块的数据隔离,又可以让用户很是方便消费多个模块的数据。
cc-class-and-instance-state

因此总体来讲,组件与store之间将构成一张关系明确和清晰的结构网,有利于用户为大型的react工程初期整齐的划分业务模块,中期灵活的调整模块定义

cc-class-and-store

更友好的function支持

hook提案落地后,现有的react社区,已经从class component慢慢转向function component写法,可是正如Vue Function-based API RFC所说,hook显而易见的要建立不少临时函数和产生大量闭包的问题,以及经过提供辅助函数useMemo/useCallback等来解决过分更新或者捕获了过时值等问题,提出了setup方案,每一个组件实例只会在初始化时调用一次 ,状态经过引用储存在 setup() 的闭包内。

综合上述的setup思路和好处,concent针对react的函数组件引入setup机制并对其作了更优的改进,一样在在组件实例化时只调用一次,能够定义各类方法并返回,这些方法将收集在上下文的settings对象里,还额外的容许setup里定义effectcomputedwatch函数(固然,这些是实例级别的computedwatch了)

在线示例

UI定义

const AwardPanelUI = (props) => {
  return (
    <div style={stBox}>
      {/** 其余略 */}
      <div>displayBonus: {props.displayBonus}</div>
    </div>
  );
};
复制代码

setup定义

const setup = ctx => {
  //定义反作用,第二位参数写空数组,表示只在组件初次挂载完毕后执行一次
  ctx.defineEffect(ctx => {
    ctx.dispatch('init');
    //返回清理函数,组件卸载时将触发此函数
    return () => ctx.dispatch('track', 'user close award panel')
  }, []);

  /** 也支持函数式写法
    ctx.defineWatch(ctx=>{
      return {...}
    });
   */
  ctx.defineWatch({
    //key inputCode的值发生变化时,触发此观察函数
    'inputCode':(nevVal)=> ctx.setState({msg:'inputCode 变为 '+nevVal })
  });
  ctx.defineComputed({
    //key inputCode的值发生变化时,触发此计算函数
    'inputCode':(newVal)=>`${newVal}_${Date.now()}`
  });

  //定义handleStrChange方法
  const handleStrChange = (e) => {
    const inputCode = e.currentTarget.value;

    //两种写法等效
    ctx.dispatch('handleInputCodeChange', inputCode);
    // ctx.reducer.award.handleInputCodeChange(inputCode);
  }

  //定义init函数
  const init = ctx.reducer.award.init;
  //const init = ()=> ctx.dispatch('init');

  //setup会将返回结果放置到settings
  return { handleStrChange, init };
}
复制代码

mapProps定义

//函数组件每次渲染前,mapProps都会被调用,帮助用户组装想要的props数据
const mapProps = ctx => {
  //将bonus的计算结果取出
  const displayBonus = ctx.moduleComputed.bonus;
  //将settings里的 handleStrChange方法、init方法 取出
  const { handleStrChange, init } = ctx.settings;
  //将inputCode取出
  const { inputCode, awardList, mask, msg } = ctx.moduleState;

  //从refConnectedComputed获取实例对模块key的计算值
  const { inputCode:cuInputCode } = ctx.refComputed.award;

  //该返回结果会映射到组件的props上
  return { msg, cuInputCode, init, mask, inputCode, awardList, displayBonus, handleStrChange }
}
复制代码

链接函数组件

const AwardPanel = connectDumb({setup, mapProps, module:'award'})(AwardPanelUI);
复制代码

hook真的是答案吗

有了setup的支持,能够将这些要用到方法提高为静态的上下文api,而不须要反复重定义,也不存在大量的临时闭包问题,同时基于函数式的写法,能够更灵活的拆分和组合你的U代码与业务代码,同时这些setup函数,通过进一步抽象,还能够被其余地方复用。

同时函数式编程也更利于typescript作类型推导,concent对函数组件友好支持,让用户能够在classfunction之间按需选择,concent还容许定义state来作局部状态管理,因此通过connectDumb包裹的function组件,既可以读写本地状态,又可以读写store状态,还有什么更好的理由非要使用hook不可呢?

const AwardPanel = connectDumb({
  //推荐写为函数式写法,由于直接声明对象的话,concent也会对其作深克隆操做
  //state:()=>({localName:1});
  state:{localName:1},
  setup, 
  mapProps, 
  connect:{award:'*'}
})(AwardPanelUI);

//code in setup
const setup = ctx =>{
  const changeLocalName = name => ctx.setState({localName});
  return {changeLocalName};
}

//code in mapProps
const mapProps = ctx =>{
  const localName = ctx.state.localName;
  return {localName}; 
}
复制代码

更加注重使用体验的架构

concent接入react应用是很是轻松和容易的,对于已存在的react应用,不须要你修改现有的react应用任何代码,只须要先将concent启动起来,就可使用了,不须要在顶层包裹Provider之类的组件来提供全局上下文,由于启动concent以后,concent自动就维护着一个本身的全局上下文,因此你能够理解concentreact应用是一个平行的关系,而非嵌套或者包裹的关系,惟一注意的是在渲染react应用以前,优先将concent启动就能够了。

cc-struct

分离式的模块配置

concent并不是要求用户在启动时就配置好各个模块的定义,容许用户定义某些组件时,调用configure函数配置模块,这将极大提升定义page model或者component model的编码体验。

.
|____page
| |____Group
| | |____index.js
| | |____model//定义page model
| |   |____reducer.js //可选
| |   |____index.js
| |   |____computed.js //可选
| |   |____state.js //必包含
| |   |____watch.js //可选
| |   |____init.js //可选
| |
| |____...//各类page组件定义
|
|____App.css
|____index.js
|____utils
| |____...
|____index.css
|____models// 各类业务model的定义
| |____home
| | |____reducer.js
| | |____index.js
| | |____computed.js
| | |____state.js
|
|____components
| |____Nav.js
|
|____router.js
|____logo.png
|____assets
| |____...
|____run-cc.js //启动concent,在入口index.js里第一行就调用
|____App.js
|____index.js //项目入口文件
|____services
| |____...

复制代码

以上图代码文件组织结构为例,page组件Group包含了一个本身的model,在model/index.js里完成定义模块到concent的动做,

// code in page/Group/model/index.js
import state form './state';
import * as reducer form './reducer';
import * as computed form './computed';
import * as watch form './watch';
import init form './init';
import {configure} from 'concent';

//配置模块到`concent`里,命名为'group'
configure('group', {state, reducer, computed, watch, init});
复制代码

在Group组件对外暴露前,引入一下model就能够了

import './model';

@register('GroupUI', {module:'group'})
export default class extends Component {

}
复制代码

这种代码组织方式为用户发布携带完整model定义的concent组件到npm成为了可能,其余用户只需安装它的concent应用里,安装了该组件就能直接使用该组件,甚至不使用组件的UI逻辑,只是注册他新写的组件到该组件携带的模块里,完彻底全复用模块的除了ui的其余全部定义。

模块克隆

对于已有的模块,有的时候咱们想彻底的复用里面的全部定义可是运行时是完全隔离的,若是用最笨的方法,就是彻底copy目标模块下的全部代码,而后起一个新的名字,配置到concent就行了,但是若是有10个、20个甚至更多的组件想复用逻辑可是保持运行时隔离怎么办呢?显然复制多份代码是行不通的,concent提供cloneModule函数帮助你完成此目的,实际上cloneModule函数只是对state作了一个深拷贝,其余的由于都是函数定义,因此只是让新模块指向那些函数的引用。

基于cloneModule能够在运行时任意时间调用的特性,你甚至能够写一个工厂函数,动态创解绑定了新模块的组件!

//makeComp.js
import existingModule from './demoModel';
import { register, cloneModule } from 'concent';

const module_comp_= {};//记录某个模块有没有对应的组件

class Comp extends Component(){
  //......
}

export makeComp(module, CompCcClassName){
  let TargetComp = module_comp_[module];
  if(TargetComp) return TargetComp;

  //先基于已有模块克隆新模块
  cloneModule(module, existingModule);

  //由于module是不能重复的,ccClassName也是不能重复的,
  //全部用户若是没有显式指定ccClassName值的话,能够默认ccClassName等于module值
  const ccClassName = CompCcClassName || module;

  //注册Comp到新模块里
  TargetComp = register(ccClassName, {module})(Comp);
  //缓存起来
  module_comp_[module] = TargetComp;

  return TargetComp;
}

复制代码

concent组件工做流程

concent组件并不是魔改了react组件,只是在react组件上作了一层语法糖封装,整个react组件的生命周期依然须要被你了解,而concentDumb将原生命周期作了巧妙的抽象,才得以使用defineEffectdefineWatchdefineComputed等有趣的功能而无需在关注类组件的生命周期,无需再和this打交道,让函数组件和类组件拥有彻底对等的功能。

cc-process

对比主流状态管理方案

咱们知道,现有的状态框架,主要有两大类型,一个是redux为表明的基于对数据订阅的模式来作状态全局管理,一种是以mobx为表明的将数据转变为可观察对象来作主动式的变动拦截以及状态同步。

vs redux

咱们先聊一聊redux,这个当前react界状态管理的一哥。

redux难以言语的reducer

写过redux的用户,或者redux wrapper(如dvarematch等)的用户,都应该很清楚 redux的一个约定:reducer必需是纯函数,若是状态改变了,必需解构原state返回一个新的state

// fooReducer.js
export default (state, action)=>{
  switch(action.type){
    case 'FETCH_BOOKS':
      return {...state, ...action.payload};
    default:
      return state;
  }
}
复制代码

纯函数没有反作用,容易被测试的特色被提起过不少次,咱们写着写着,对于actionCreatorreducer,有了两种流派的写法,

  • 一种是将异步的请求逻辑以及请求后的数据处理逻辑,都放在actionCreator写完了,而后将数据封装为payload,调用dispatch, 讲数据派发给对应的reducer

此种流派代码,慢慢变成reducer里全是解构payload而后合成新的state并返回的操做,业务逻辑所有在 actionCreator里了,此种有一个有一个严重的弊端,由于业务逻辑所有在actionCreator里,reducer函数里的type值所有变成了一堆相似CURD的命名方式,saveXXModelupdateXXModelXXFieldsetXXXdeleteXXX等看起来已经和业务逻辑全然没有任何关系的命名,大量的充斥在reducer函数里,而咱们的状态调试工具记录的type值偏偏全是这些命名方式,你在调试工具里看到变迁过程对应的type列表,只是获取到了哪些数据被改变了的信息,但全然不知这些状态是从哪些地方派发了payload致使了变化,甚至想知道是那些UI视图的什么交互动做致使了状态的改变,也只能从代码的reducertype关键字做为搜索条件开始,反向查阅其余代码文件。

  • 还有一种是让actionCreator尽量的薄,派发同步的action就直接return,异步的action使用thunk函数或者redux-saga等第三方库作处理,拿到数据都尽量早的作成action对象,派发到reducer函数里,

此种模式下,咱们的actionCreator薄了,作的事情如其名字同样,只是负责产生action对象,同时由于咱们对数据处理逻辑在reducer里了,咱们的type值能够根据调用方的动机或者场景来命名了,如formatTimestamphandleNameChangedhandelFetchedBasicData等,可是因为redux的架构致使,你的ui触发的动做避免不了的要要通过两个步骤,一步通过actionCreator生成action,第2步进过通过reducer处理payload合成新的state,因此actionCreator的命名和reducerType的命名一般为了方便之后阅读时可以带入上下文信息颇有可能会变成同样的命名,如fetchProductList,在actionCreator里出现一遍,而后在reducerType又出现一遍

concent化繁为简的reducer

concent里reducer担任的角色就是负责返回一个新的片断视图,因此你能够认为它就是一个partialStateGenerator函数,你能够声明其为普通函数

//code in fooReducer.js
function fetchProductList(){
}
复制代码

也能够是async函数或者generator函数

async function fetchProductList(){
}
复制代码

若是,你的函数须要几步请求才能完成所有的渲染,可是每一步都须要及时触发视图更新,concent容许你自由组合函数,若是同属于一个模块里的reducer函数,相互之间还能够直接基于函数签名来调用

function _setProductList(dataList){
  return {dataList};
}

//获取产品列表计基础数据
async function fetchProductBasicData(payload, moduleState, ctx){
  const dataList = await api.fetchProductBasicData();
  return {dataList};//返回数据,触发渲染
  // or ctx.dispatch(_setProductList, dataList);
}

//获取产品列表计统计数据,统计数据较慢,分批拉取 (伪代码)
async function fetchProductStatData(payload, moduleState, ctx){
  const dataList = moduleState.dataList;
  //作分批拉取统计数据的ids,逻辑略...... 
  const batchIdsList = [];
  const len = batchIds.length;

  for(let i=0; i<len; i++){
    const ids = batchIdsList[i];
    const statDataList = await api.fetchProductBasicData(ids);

    //逻辑略...... 游标开始和结束,改变对应的data的统计数据
    let len = statDataList.length;
    for(let j=0; j<len; j++){
      dataList[j+cursor].stat = statDataList[j];//赋值统计数据
    }
    await ctx.dispatch(_setProductList, dataList);//修改dataList数据,触发渲染
  }
}

//一个完整的产品列表既包含基础数据、也包含统计数据,分两次拉取,其中统计数据须要屡次分批拉取
async function fetchProductList(payload, moduleState, ctx){
  await ctx.dispatch(fetchProductBasicData);
  await ctx.dispatch(fetchProductStatData);
}
复制代码

如今你只须要视图实例里调用$$dispatch触发更新便可

//属于product模块的实例调用
this.$$dispatch('fetchProductList');

//属于其余模块的实例调用
this.$$dispatch('product/fetchProductList');
复制代码

能够看到,这样的代码组织方式,更符合调用者的使用直觉,没有多余的操做,相互调用或者多级调用,能够按照开发者最直观的思路组织代码,而且很方便后期不停的调整后者重构模块里的reducer。

concent强调返回欲更新的片断状态,而不是合成新的状态返回,从工做原理来讲,由于concent类里标记了观察key信息,reducer提交的状态越小、越精准,就越有利于加速查找到关心这些key值变化的实例,还有就是concent是容许对key定义watchcomputed函数的,保持提交最小化的状态不会触发一些冗余的watchcomputed函数逻辑;从业务层面上来讲,你返回的新状态是须要符合函数名描述的,咱们直观的解读一段函数时,大致知道作了什么处理,最终返回一个什么新的片断状态给concent,是符合线性思惟的^_^,剩下的更新UI的逻辑就交给concent吧。

可能读者留意到了,redux所提倡的纯函数容易测试、无反作用的优点呢?在concent里可以体现吗,其实这一点担忧彻底没有必要,由于你观察上面的reducer示例代码能够发现,函数有无反作用,彻底取决于你声明函数的方式,async(or generator)就是反作用函数,不然就是纯函数,你的ui里能够直接调用纯函数,也能够调用反作用函数,根据你的使用场景具体决定,函数名就是type,没有了actionCreator是否是世界清静了不少?

进一步挖掘reducer本质,上面提到过,对于concent来讲,reducer就是partialStateGenerator函数,因此若是讨厌走dispatch流派的你,能够直接定义一个函数,而后调用它,而非必须要放置在模块的reducer定义下。

function inc(payload, moduleState, ctx){
  ctx.dispatch('bar/recordLog');//这里不使用await,表示异步的去触发bar模块reducer里的recordLog方法
  return {count: moduleState.count +1 };
}

@register('Counter', 'counter')(Counter)
class Counter extends Component{
  render(){
    return <div onClick={()=> this.$$invoke(inc}>count: {this.state.count}</div>
  }
}
复制代码

concent不只书写体验友好,由于concent是以引用收集为基础来作状态管理,因此在concent提供的状态调试工具里能够精确的定位到每一次状态变动提交了什么状态,调用了哪些方法,以及由哪些实例触发。

cc-core

redux复杂的使用体验

尽管redux核心代码很简单,提供composeReducersbindActionCreators等辅助函数,做为桥接reactreact-redux提供connect函数,须要各类手写mapStateToPropsmapDispatchToProps等操做,整个流程下来,其实已经让代码显得很臃肿了,因此有了dvarematchredux wrapper作了此方面的改进,化繁为简,可是不管怎么包装,从底层上看,对于redux的更新流程来讲,任何一个action派发都要通过全部的reducerreducer返回的状态都要通过全部connect到此reducer对应状态上的全部组件,通过一轮浅比较(这也是为何redux必定要借助解构语法,返回一个新的状态的缘由),决定要不要更新其包裹的子组件。

const increaseAction = {
  type: 'increase'
};

const mapStateToProps = state => {
  return {value: state.count}
};

const mapDispatchToProps = dispatch => {
  return {
    onIncreaseClick: () => dispatch(increaseAction);
  }
};


const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

复制代码

concent简单直接的上手体验

注册成为concent类的组件,天生就有操做store的能力,并且数据将直接注入state

//Counter里直接可使用this.$$dispatch('increase')
class Counter extends Component{
  render(){
    return <div onClick={()=> this.$$dispatch('increase')}>count: {this.state.count}</div>
  }
}

const App = register('Counter', 'counter')(Counter);
复制代码

你能够注意到,concent直接将$$dispatch方法,绑定在this上,由于concent默认采用的是反向继承策略来包裹你的组件,这样产生更少的组件嵌套关系从而使得Dom层级更少。

store的state也直接注入了this上,这是由于从setState调用开始,就具有了将转态同步到store的能力,因此注入到state也是顺其天然的事情。

固然concent也容许用户在实例的state上声明其余非store的key,这样他们的值就是私有状态了,若是用户不喜欢state被污染,不喜欢反向继承策略,一样的也能够写为

class Counter extends Component{
  constructor(props, context){
    super(props, context);
    this.props.$$attach(this);
  }
  render(){
    return(
      <div onClick={()=> this.props.$$dispatch('increase')}>
        count: {this.props.$$connectedState.counter.count}
      </div>
    )
  }
}

const App = register('Counter', {connect:{counter:'*'}, isPropsProxy:true} )(Counter);
复制代码

vs mobx

mobx是一个函数响应式编程的库,提供的桥接库mobx-reactreact变成完全的响应式编程模式,由于mobx将定义的状态的转变为可观察对象,因此 用户只须要修改数据,mobx会自动将对应的视图更新,因此有人戏称mobxreact变成相似vue的编写体验,数据自动映射视图,无需显示的调用setState了。

本质上来讲,全部的mvvm框架都在围绕着数据和视图作文章,react把单项数据流作到了极致,mobxreact打上数据自动映射视图的补丁,提到自动映射,自动是关键,框架怎么感知到数据变了呢?mobx采用和vue同样的思路,采用push模式来作变化侦测,即对数据gettersetter作拦截,当用户修改数据那一刻,框架就知道数据变了,而react和咱们当下火热的小程序等采用的pull模式来作变化侦测,暴露setStatesetData接口给用户,让用户主动提交变动数据,才知道数据发生了变化。

concent本质上来讲没有改变react的工做模式,依然采用的是pull模式来作变化侦测,惟一不一样的是,让pull的流程更加智能,当用户的组件实例建立好的那一刻,concent已知道以下信息:

  • 实例属于哪一个模块
  • 实例观察这个模块的哪些key值变化
  • 实例还额外链接其余哪些模块

同时实例的引用将被收集到并保管起来,直到卸载才会被释放。

因此能够用0改形成本的方式,直接将你的react代码接入到concent,而后支持用户能够渐进式的分离你的ui和业务逻辑。

须要自动映射吗

这里咱们先把问题先提出来,咱们真的须要自动映射吗?

当应用愈来愈大,模块愈来愈多的时候,直接修改数据致使不少不肯定的额外因素产生而没法追查,因此vue提供了vuex来引导和规范用户在大型应用的修改状态的方式,而mobx也提供了mobx-state-tree来约束用户的数据修改行为,经过统一走action的方式来让整个修改流程可追查,可调试。

改形成本

因此在大型的应用里,咱们都但愿规范用户修改数据的方式,那么concent从骨子里为react而打造的优点将体现出来了,能够从setState开始享受到状态管理带来的好处,无需用户接入更多的辅助函数和大量的装饰器函数(针对字段级别的定义),以及完美的支持用户渐进式的重构,优雅的解耦和分离业务逻辑与ui视图,写出的代码始终仍是react味道的代码。

结语

concent围绕react提供一种了更温馨、更符合阅读直觉的编码体验,同时新增了更多的特性,为书写react组件带来更多的趣味性和实用性,无论是传统的class流派,仍是新兴的function流派,都可以在concent里享受到统一的编码体验。

依托于concent的如下3点核心工做原理:

  • 引用收集
  • 观察key标记
  • 状态广播

基于引用收集和观察key标记,就能够作到热点更新路径缓存,理论上来讲,某一个reducer若是返回的待更新片断对象形状是不变的,初次触发渲染的时候还有一个查找的过程(尽管已经很是快),后面的话相同的reducer调用均可以直接命中并更新,有点相似v8里的热点代码缓存,不过concent缓存的reducer返回数据形状和引用之间的关系,因此应用能够越运行越快,尤为是那种一个界面上百个组件,n个模块的应用,将体现出更大的优点,这是下一个版本concent正在作的优化项,为用户带来更快的性能表现和更好的编写体验是concent始终追求的目标。

彩蛋 Ant Design Pro powered by concent 🎉🎉🎉

尽管concent有一套本身的标准的开发方式,可是其灵活的架构设计很是的容易与现有的项目集成,此案例将concent接入到antd-pro(js版本的最后一个版本2.2.0),源代码业务逻辑没有作任何改动,只是作了以下修改,lint-staged验收经过:

  • 在src目录下加入runConcent脚本
  • models 所有替换为concent格式定义的,由于umi会自动读取model文件夹定义注入到dva里,因此全部concent相关的model都放在了model-cc文件夹下
  • 组件层的装饰器,所有用concent替换了dva,并作了少量语法的修改
  • 引入concent-plugin-loading插件,用于自动设置reducer函数的开始和结束状态
  • 引入react-router-concent,用于链接react-routerconcent
  • 引入concent-middleware-web-devtool(第一个可用版本,比较简陋^_^),用于查看状态concent状态变迁过程

注意,运行期项目后,能够打开console,输入sss,查看store,输入cc.dispatchcc.reducer.**直接触发调用,更多api请移步concent官网文档查看,更多antd-pro知识了解请移步antd-pro官网

如何运行

  • 下载源代码
git clone git@github.com:concentjs/antd-pro-concent.git
复制代码
  • 进入根目录,安装依赖包
npm i
复制代码
  • 运行和调试项目
npm start
复制代码

默认src目录下放置的是concent版本的源代码,如需运行dva版本,执行npm run start:old,切换为concent,执行npm run start:cc

其余

happy coding, enjoy concent ^_^
欢迎star


An out-of-box UI solution for enterprise applications as a React boilerplate.

相关文章
相关标签/搜索