爲 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.

相關文章
相關標籤/搜索