Hello Concent, a funny way to develop react app.
Tags: react, javascript, concent
Published: 2020-06-19 08:05:22

**[star me if you like concent ^_^](https://github.com/concentjs/concent)**

## [concent](https://concentjs.github.io/concent-doc)
❤️ Build-in **dependency collection**, a predictable、zero-cost-use、progressive、high performance's react develop framework 

<p align="center">
  <a href="#">
    <img width="500" src="https://raw.githubusercontent.com/concentjs/concent-site/master/img/banner.png">
  </a>
</p>
</div>
    
![hello-concent](https://github.com/fantasticsoul/assets/blob/master/img/cc-intro-1.gif?raw=true)

[review this gif source code](https://stackblitz.com/edit/react-wpzgqd?file=index.js) or [see a full demo](https://xvcej.csb.app/#/)

## 📦Quick start
Make sure you have installed [nodejs](http://nodejs.cn/download/)。

### Install concent
Install `concent` with npm command.

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

or yarn command

```sh
$ yarn add concent
```

### Define module
Use `run` to define a module.

```js
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 `useConcent`for function component.
> it will let concent konw which module current component belong to.

```js
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](https://codesandbox.io/s/dep-collection-uiqzn?file=/src/App.js) become true

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/recoil-vs-concent/r5.gif)

### 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](https://codesandbox.io/s/rvc-demo2-vg3uh?file=/src/index.js) you can view the demo.

```jsx
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.

```js
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`

```js
//  --------- 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 `setState`、`dispatch` and `reducer`.
 
- with `setState`    

```js
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`

```js
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!
```js
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`

```js
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.

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

**async comoputed** is also supported, [here](https://codesandbox.io/s/async-computed-35byz?file=/src/App.js:1378-2042) 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](https://codesandbox.io/s/hello-concent-djxxh)

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/img/cc-re-render-process.png)

```js
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](https://codesandbox.io/s/hello-concent-egb4d)
[simple demo 2](https://codesandbox.io/s/dep-collection-uiqzn)


### 🎆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. 

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/cc-unified-lifecycle-en.png)

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

// for function
function FnComp(){
  useConcent({setup});
}
```
[view demo](https://codesandbox.io/s/nifty-cdn-6g3hh)

## Eco system

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

![](https://raw.githubusercontent.com/concentjs/concent-site/master/img/cc-core.png)

### Use with react router
Details see here [react-router-concent](https://github.com/concentjs/react-router-concent)，expose `history`，you can call it anywhere in your app to enjoy the imperative navigation jump.

[react-router-concent online demo](https://stackblitz.com/edit/cc-multi-ways-to-wirte-code)

### Use with redux-dev-tool
Details see here [concent-plugin-redux-devtool](https://github.com/concentjs/concent-plugin-redux-devtool)，track your state changing history。
![redux-dev-tool](https://raw.githubusercontent.com/fantasticsoul/assets/master/img/cc-eco/cc-pic1.png)

###  Use with plugin-loading
Details see here [concent-plugin-loading](https://github.com/concentjs/concent-plugin-loading)，control all your reducer function's loading status easily。

[concent-plugin-loading online demo](https://stackblitz.com/edit/cc-plugin-loading?file=models%2Fstudent%2Freducer.js)