learning composition api to improve your react development experience
Tags: react, concent, javascript
Published: 2020-08-21 11:52:53

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/img/learncapi2.png)

Open source is not easy, thank you for your support, [❤ star me if you like concent ^_^](https://github.com/concentjs/concent)

Here is a list of status management in the collection, welcome friends who are interested to know ^_^
[awesome-state](https://github.com/fantasticsoul/awesome-state/blob/master/README.md)

In this article, we will see the big difference between composition style and hook style in react code.

## Preface
`composition api` (combination api) and `optional api` (optional api) are two ways to organize code. I believe you have learned a lot in the various related introduction articles of `vue3`, they can exist at the same time, It is not mandatory that you can only use which one, but the two major advantages of combined api do make developers more inclined to use it instead of optional api.
- Package the reusable logic based on the function and inject it into any component, making the decoupling of the view and business more elegant
- Let the businesses of the same function be placed more closely together without being separated, improving the development and maintenance experience

The above two points are elegantly solved by `hook` in React, so what are the advantages of combined api compared to `hook`? I’m not going to sell it here. I believe that some friends have already known when Youda introduced the combined api. The combined api is statically defined, which solves the performance problem that the `hook` must regenerate the temporary closure function every time it is rendered. In the `hook`, the old value trap, manual detection dependence and other coding experience problems are solved.

However, react is the coding method of **all in js**, so as long as we dare to think and do, all excellent programming models can be absorbed. Next, we use native `hook` and concentrated `setup` and pass Examples and explanations, to completely solve the pain point of `hook` mentioned by You Da^_^

## react hook

We first design a traditional counter, the requirements are as follows
- There is a decimal and a large number
- There are two groups of plus and minus buttons, which operate on decimal and large numbers respectively, the decimal button adds and subtracts 1, and the large number button adds and subtracts 100
- Pull the welcome greeting when the counter is first mounted
- When the decimal reaches 100, the button turns red, otherwise it turns green
- When the large number reaches 1000, the button turns purple, otherwise it turns green
- When the big number reaches 10,000, the number of the big number is reported
- When the calculator is uninstalled, report the current number

In order to complete this requirement, we need to use the following 5 hooks

### useState
After the requirement, we need to use the first hook `useState` to initialize the state of the component's first rendering

```js
function Counter() {
  const [num, setNum] = useState(6);
  const [bigNum, setBigNum] = useState(120);
}
```

### useCallback
If you need to use the cache function, you need to use the second hook `useCallback`, here we use this hook to define the addition and subtraction functions

```js
  const addNum = useCallback(() => setNum(num + 1), [num]);
  const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);
```

### useMemo
If you need to use the cached calculation results, you need to use the third hook `useMemo`, here we use this hook to calculate the button color

```js
 const numBtnColor = useMemo(() => {
    return num> 100?'red':'green';
  }, [num]);
  const bigNumBtnColor = useMemo(() => {
    return bigNum> 1000?'purple':'green';
  }, [bigNum]);
```

### useEffect
To handle the side effects of the function, the fourth hook `useEffect` is needed. Here we are used to deal with the two requirements
- When the big number reaches 10,000, the number of the big number is reported
- When the calculator is uninstalled, report the current number

```js
  useEffect(() => {
    if (bigNum> 10000) api.report('reach 10000')
  }, [bigNum])
  useEffect(() => {
    return ()=>{
      api.reportStat(num, bigNum)
    }
  }, [])
```

### useRef
The writing of `useEffect` using the cleanup function above will be warned in the IDE, because of the internal use of `num, bigNum` variables (not writing dependencies will fall into the trap of the old value of the closure), so we are required to declare dependencies

However, if we change to the following method to avoid IDE warnings, obviously it is not our intention. We just want to report the number when the component is uninstalled, instead of triggering the cleanup function every round of rendering
```js
  useEffect(() => {
    return ()=>{
      api.reportStat(num, bigNum)
    }
  }, [num, bigNum])
```

At this time we need the fifth hook `useRef` to help us fix our dependencies, so the correct way of writing is
```js
  const ref = useRef();// ref is a fixed variable, and each round of rendering points to the same value
  ref.current = {num, bigNum};// Help us remember the latest value
  useEffect(() => {
    return () => {
      const {num, bigNum} = ref.current;
      reportStat(num, bigNum);
    };
  }, [ref]);
```


### Complete counter
After using 5 hooks, our complete component is as follows

```js
function Counter() {
  const [num, setNum] = useState(88);
  const [bigNum, setBigNum] = useState(120);
  const addNum = useCallback(() => setNum(num + 1), [num]);
  const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);
  const numBtnColor = useMemo(() => {
    return num> 100? "red": "green";
  }, [num]);
  const bigNumBtnColor = useMemo(() => {
    return bigNum> 1000? "purple": "green";
  }, [bigNum]);
  useEffect(() => {
    if (bigNum> 10000) report("reach 10000");
  }, [bigNum]);

  const ref = useRef();
  ref.current = {num, bigNum};
  useEffect(() => {
    return () => {
      const {num, bigNum} = ref.current;
      reportStat(num, bigNum);
    };
  }, [ref]);

  // render ui ...
}
```

Of course, we can abstract this code separately as a hook based on the customizable characteristics of `hook`. In this case, we only need to export the data and methods, so that the Counter components expressed by multiple uis can be reused, while also achieving ui It is isolated from the business and facilitates maintenance.

```js
function useMyCounter(){
  // .... slightly
  return {num, bigNum. addNum, addNumBig, numBtnColor, bigNumBtnColor}
}
```

## concent setup
The `hook` function must be re-executed during each round of rendering, so it is inevitable that a large number of temporary closure functions will be generated during each round of rendering. If we can omit them, it can indeed help reduce gc Some recovery pressure, now let's take a look at what the Counter will look like after using `setup`.

Using `concent` is very simple, just use the `run`api to start it before the root component, so we don't have a module definition, just call it directly.

```js
import {run} from'concent';

run();// Start first, in render
ReactDOM.render(<App />, rootEl)

```

Then we slightly modify the above logic, and wrap it all inside `setup`. The logic inside the setup function will only be executed once. The APIs provided by the rendering context `ctx` that need to be used include `initState`, `computed`, `effect`, `setState`, and the state `state` that needs to be read when calling `setState` are also obtained by `ctx`.

```js
function setup(ctx) {// rendering context
  const {initState, computed, effect, state, setState} = ctx;
  // setup is executed only once before the component is rendered for the first time, we can write related business logic internally
}
```

### initState
`initState` is used to initialize the state, instead of `useState`, when our component state is large, we still don't need to consider how to divide the state granularity.

```js
initState({ num: 6, bigNum: 120 });
```

The function initialization state is also supported here

```js
initState(()=>({ num: 6, bigNum: 120 }));
```

### computed
`computed` is used to define the calculation function. When deconstructing from the parameter list, the input dependency of the calculation is determined. Compared with `useMemo`, it is more direct and elegant.

```js
// This calculation function is triggered only when num changes
computed('numBtnColor', ({ num }) => (num> 100?'red':'green'));
```

Here we need to define two calculation functions. The calculation function can be configured with the description body of the calculation object, so that you only need to call `computed` once.

```js
computed({
  numBtnColor: ({ num }) => num> 100?'red':'green',
  bigNumBtnColor: ({ bigNum }) => bigNum> 1000?'purple':'green',
});
```

### effect
The usage of `effect` is exactly the same as `useEffect`, the difference is that only the key name is passed in the dependent array. At the same time, `effect` internally encapsulates the life cycle of function components and class components. Make any changes and migrate to class components

```js
effect(() => {
  if (state.bigNum> 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
  // Here you can write what needs to be done when the first rendering is completed
  return () => {
  // Cleanup function triggered when uninstalling
    api.reportStat(state.num, state.bigNum)
  }
}, []);
```

### setState
It is used to modify the state. After we define the method in `setup` based on `setState`, then return. Then we can get these method handles through `ctx.settings` in any component that uses this `setup` Can call

```js
function setup(ctx) {// rendering context
  const {state, setState} = ctx;
  return {// export method
    addNum: () => setState({ num: state.num + 1 }),
    addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
  }
}
```

### Complete Setup Counter
Based on the above apis, the logic code of our final Counter is as follows

```js
function setup(ctx) {// rendering context
  const {initState, computed, effect, state, setState} = ctx;
  // Initialization data
  initState({ num: 6, bigNum: 120 });
  // Define calculation function
  computed({
    // When the parameter list is deconstructed, the input dependency of the calculation is determined
    numBtnColor: ({ num }) => num> 100?'red':'green',
    bigNumBtnColor: ({ bigNum }) => bigNum> 1000?'purple':'green',
  });
  // define side effects
  effect(() => {
    if (state.bigNum> 10000) api.report('reach 10000')
  }, ['bigNum'])
  effect(() => {
    return () => {
      api.reportStat(state.num, state.bigNum)
    }
  }, []);

  return {// export method
    addNum: () => setState({ num: state.num + 1 }),
    addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
  }
}
```

After defining the core business logic, we can use `useConcent` to assemble our defined `setup` inside any function component to use it. `useConcent` will return a rendering context (and the parameter list of the setup function refers to Is the same object reference, sometimes we also call the instance context), we can get the target data and methods from `ctx` on demand, for this example, we can export
You can use the three keys of `state` (data), `settings` (the method returned by the setup package), and `refComputed` (container of the calculation function result of the instance).

```js
import {useConcent} from'concent';

function NewCounter() {
  const {state, settings, refComputed} = useConcent(setup);
  // const {num, bigNum} = state;
  // const {addNum, addNumBig} = settings;
  // const {numBtnColor, bigNumBtnColor} = refComputed;
}
```

We mentioned above that `setup` can also be assembled to class components, just use `register`. Note that the assembled class components can be directly obtained from `this.ctx` to the rendering context generated by `concent` At the same time, `this.state` and `this.ctx.state` are equivalent, and `this.setState` and `this.ctx.setState` are also equivalent, which is convenient for users to change the code 0 to access `concent `Use.

```js
import {register} from'concent';

@register(setup)
class NewClsCounter extends Component{
  render(){
   const {state, settings, refComputed} = this.ctx;
  }
}
```

## Conclusion
Compared with native hooks, `setup` fixes the business logic in a function that will only be executed once, provides a more friendly api, and is perfectly compatible with class components and function components, allowing users to escape the troubles of the use rules of `hook` ( Think about useEffect and useRef, is there a significant cognitive cost?), instead of passing these constraints on learning barriers to users, it is also more friendly to gc. I believe everyone has already defaulted to `hook` yes` An important invention of react`, but in fact, it is not for users, but for frameworks. Users do not need to understand the details and rules of brain-burning. For concent users, they only need one hook to open one. The portal can implement all business logic in another space, and these logics can also be reused on class components.

My dear guest official has seen so much, don’t hurry up and try it out. The following links are provided in two ways for you to play with😀
- [Original hook Counter](https://codesandbox.io/s/hook-counter-cu1i9)
- [setup Counter](https://codesandbox.io/s/setup-counter-y7g0u)

### one more thing
If you want to share the state of the two hook counters, we need to modify the code to connect to `redux` or build a self-built `Context`, but in the development mode of `concent`, `setup` does not require any modification, just declare one in advance Module, and then register the module that belongs to the component. This silky migration process allows users to flexibly deal with various complex scenarios.

```js
import {run} from'concent';

run({
  counter:{
    state: {num:88, bigNum: 120 },
  },
  //reducer: {...}, // If the operation data process is complicated, the business can be promoted here
})

// For function components
useConcent({setup});
// ---> Change to
useConcent({setup, module:'counter'})

// For function components
@register({setup});
// ---> Change to
@register({setup, module:'counter'});
```
-[shared Counter](https://codesandbox.io/s/shared-counter-hetl9)

## Finish
Previous articles
- [Use concent in react, share state easily and enjoy exact update](https://dev.to/fantasticsoul/use-concent-in-react-share-state-easily-and-enjoy-exact-update-hmn)
- [Redux, mobx, and concentrated feature competition, see how juniors play against seniors](https://dev.to/fantasticsoul/compare-redux-mobx-and-concent-in-depth-to-let-you-have-a-funny-way-in-developing-react-app-1o1i)

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

[Edit on CodeSandbox](https://codesandbox.io/s/concent-guide-xvcej)
![https://codesandbox.io/s/concent-guide-xvcej](https://user-gold-cdn.xitu.io/2019/11/17/16e79b2dee1e3c4d?w=302&h=62&f=png&s=9408 )

[Edit on StackBlitz](https://stackblitz.com/edit/cc-multi-ways-to-wirte-code)
![https://stackblitz.com/edit/cc-multi-ways-to-wirte-code](https://user-gold-cdn.xitu.io/2019/11/17/16e79b30771f4fd4?w=302&h =61&f=png&s=10028)

If you have any questions about concent, you can scan the QR code and join the group consultation or send email to me( zhongzhengkai@gmail.com ). We will try our best to answer questions and help you learn more 😀.

![](https://user-gold-cdn.xitu.io/2019/11/10/16e539222e4bfcdc?w=540&h=740&f=jpeg&s=52155)