DEV Community

幻魂
幻魂

Posted on • Updated on

Hello Concent, a funny way to develop react app.

star me if you like concent ^_^

concent

❤️ Build-in dependency collection, a predictable、zero-cost-use、progressive、high performance's react develop framework

hello-concent

review this gif source code or see a full demo

📦Quick start

Make sure you have installed nodejs

Install concent

Install concent with npm command.

$ cd cc-app
$ npm i --save concent

or yarn command

$ yarn add concent

Define module

Use run to define a module.

import { run } from 'concent';

run({
  counter: {// declare a moudle named 'counter'
    state: { num: 1, numBig: 100 }, // define state
  },
  // you can also put another module here.
});

Cosume state & change state

Use register to specify a module for class component, or useConcentfor function component.

it will let concent konw which module current component belong to.

import { register, useConcent } from 'concent';

@register('counter')
class DemoCls extends React.Component{
  // now setState can commit state to store 
  // and broadcast state to other refs which also belong to counter module
  inc = ()=> this.setState({num: this.state.num + 1})
  render(){
    // here if read num, it means current ins render dep keys is ['num']
    const { num } = this.state;
    // render logic
  }
}

function DemoFn(){
  const { state, setState } = useConcent('counter');
  const inc = ()=> setState({num: state.num + 1});
  // render logic
}

Attention that state is a proxy object, for helping concent collect every instantce's dep keys in every render period, that makes exact update become true

Initialize component

There is no need to wrap with the root component with a Provider, you can just initialize the concent component any where you want, here you can view the demo.

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <div>
      <ClsComp />
      <FnComp />
    </div>
  </React.StrictMode>,
  rootElement
);

Define reducer

If you have many logic code before changing state, we recommend put them to reducer

concent emphasize user should always return a partial state instead of whole state, it will let concent working in best performance mode, so just return what you changed in the reducer function.

run({
  counter: {
    state: { /** ... */},
    reducer: {
      inc(payload, moduleState) {
        return { num: moduleState.num + 1 };
      },
      async asyncInc(payload, moduleState) {
        await delay();
        return { num: moduleState.num + 1 };
      }
    }
  },
});

now you can call reducer funtion in your component instead of setState

//  --------- for class component -----------
changeNum = () => this.setState({ num: 10 })
// ===> modify as below (attention that mr is alias of moduleReducer)
changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynInc(10)

// of course you can call dispatch, but we think moduleReducer is better
//this.ctx.dispatch('inc', 10); // or this.ctx.dispatch('asynInc', 10)

//  --------- for function component -----------
const { state, mr } = useConcent("counter");// useConcent returns ref ctx
const changeNum = () => mr.inc(20); // or ctx.mr.asynInc(10)

infact concent allow user change with top api setStatedispatch and reducer.

  • with setState
import { getState, setState } from "concent";

console.log(getState('counter').num);// log: 1
setState('counter', {num:10});// change counter module's num state
console.log(getState('counter').num);// log: 10
  • with dispatch dispatch return a promise, so we should wrap the logic with async
import { getState, dispatch } from "concent";

(async ()=>{
  console.log(getState("counter").num);// log 1
  await dispatch("counter/inc");// call counter reducer inc method
  console.log(getState("counter").num);// log 2
  await dispatch("counter/asyncInc");// call counter reducer asyncInc method
  console.log(getState("counter").num);// log 3
})()
  • with reducer infact concent collect all module's reducer to its internal reducer map to let user call reducer method directly!
import { getState, reducer as ccReducer } from "concent";

(async ()=>{
  console.log(getState("counter").num);// log 1
  await ccReducer.counter.inc();
  console.log(getState("counter").num);// log 2
  await ccReducer.counter.asyncInc();
  console.log(getState("counter").num);// log 3
})()

Define computed

If you want to compute another state with module state, we recommend put them to computed

run({
  counter: { 
    state: { /** ... */},
    reducer: { /** ... */},
    computed: {
      numx2: ({num})=> num * 2,
      numBigx2: ({numBig})=> numBig * 2,
      numSumBig: ({num, numBig})=> num + numBig,
    }
  },
});


// get computed result in funtion component
const { moduleComputed } = useConcent('counter');

// get computed result in class component
const { moduleComputed } = this.ctx;

Attention that when you deconstruct the state for a computed function, you are also declare the dep keys for the function at the same time.

 // current function will only been execute when num or numBig changed.
 const numSumBig = ({num, numBig})=> num + numBig,

async comoputed is also supported, here see the online demo.

Some advanced Features

Concent allow user write code with its cool features, they are all optional, but once you learned it you will love it.

🎇Enjoy composition api🎊 🎉

with composition api, user can easily separate ui and logic.
view this demo


import { run, useConcent } from "concent";

run();// startup concent

const setup = ctx => {
  const { initState, computed, watch, setState, sync } = ctx;

  initState({ greeting: 'hello concent' });
  computed("reversedGreeting", n => n.greeting.split('').reverse());
  watch("greeting", (n, o) => alert(`from ${o.greeting} to ${n.greeting}`));

  return {
    changeGreeting: (e) => setState({ greeting: e.target.value }),
    changeGreeting2: sync('greeting'),
  };
};

function HelloConcent(){
  const { state, refComputed, settings } = useConcent({ setup });
  return (
    <>
      <h1>{state.greeting}</h1>
      <h1>{refComputed.reversedGreeting}</h1>
      <input value={state.greeting} onChange={settings.changeGreeting}/>
      <input value={state.greeting} onChange={settings.changeGreeting2}/>
    </>
  );
}

simple demo 1
simple demo 2

🎆Unified coding of class components and function components

setup can be used in both class and function component, that means user can easily share logic (even including life cycle logic) between the 2 kinds of component.


// for class
@register({setup})
class ClsComp extends React.Component{...}

// for function
function FnComp(){
  useConcent({setup});
}

view demo

Eco system

With middleware and plugin mechanism, you can easily cutomize your common handler for non logic code, or migrate redux eco lib.

Use with react router

Details see here react-router-concent,expose history,you can call it anywhere in your app to enjoy the imperative navigation jump.

react-router-concent online demo

Use with redux-dev-tool

Details see here concent-plugin-redux-devtool,track your state changing history。
redux-dev-tool

Use with plugin-loading

Details see here concent-plugin-loading,control all your reducer function's loading status easily。

concent-plugin-loading online demo

Top comments (0)