Open source is not easy, thanks for your support ❤ star me if you like concent ^_^

Here is a list of state management items in the collection for those interested

The preface

The composition API and the Optional API are two ways to organize code, as you’ve already seen in the various vuE3 introductions. They can exist together, and you don’t have to use one or the other. But the two advantages of combining apis do make developers more inclined to use them instead of the alternative apis.

  • Packaging reusable logic in function-based units and injecting it into arbitrary components makes view and business decoupling more elegant
  • Services with the same functions are placed closer together without being separated, improving the development and maintenance experience

The above two points are gracefully solved by Hook in React. What are the advantages of composite API compared with Hook? The combination API is statically defined, which solves the performance problem of regenerating temporary closure functions every time a hook is rendered. There is no trap of old closure value in hook, manual detection dependency and other coding experience problems.

However, React is an all in JS coding method, so as long as we dare to think and do, all excellent programming models can be incorporated. Next, we will use the native hook and Concent setup, and through examples and explanation, thoroughly solve the hook point mentioned by University

react hook

Let’s start by designing a traditional counter with the following requirements

  • We have a decimal and a large number
  • There are two groups of add and subtract buttons that operate on large decimal numbers. The decimal button adds and subtracts 1, and the large button adds and subtracts 100
  • When the counter is first mounted, pull a welcome greeting
  • The button turns red when the decimal reaches 100, green otherwise
  • When the large number reaches 1000, the button turns purple, otherwise green
  • When the major number reaches 10000, the major number is reported
  • The current number is reported when the calculator is uninstalled

To accomplish this, we need the following five hooks

useState

After completing the requirements, we need to use the first hook, useState, to initialize the state of the component’s first rendering

function Counter() {
  const [num, setNum] = useState(6);
  const [bigNum, setBigNum] = useState(120);
}
Copy the code

useCallback

To use the cache function, we use the second hook, useCallback, which we use here to define the addition and subtraction functions

  const addNum = useCallback(() = > setNum(num + 1), [num]);
  const addNumBig = useCallback(() = > setBigNum(bigNum + 100), [bigNum]);
Copy the code

useMemo

If we need cached results, we use a third hook, useMemo, which we use here to calculate button colors

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

useEffect

To handle the side effects of a function, use the fourth hook, useEffect, which we use to handle two requirements

  • When the major number reaches 10000, the major number is reported
  • The current number is reported when the calculator is uninstalled
  useEffect(() = > {
    if (bigNum > 10000) api.report('reach 10000')
  }, [bigNum])
  useEffect(() = > {
    return () = >{
      api.reportStat(num, bigNum)
    }
  }, [])
Copy the code

useRef

The useEffect notation above, which uses the cleanup function, is warned by the IDE because the num, bigNum variables are used internally (not writing dependencies will trap closure old values), so we are required to declare dependencies

However, if we want to avoid IDE warnings, it is clearly not our intention to say that we just want to report the number when the component is unloaded, not to trigger the cleanup function for every turn of rendering

  useEffect(() = > {
    return () = >{
      api.reportStat(num, bigNum)
    }
  }, [num, bigNum])
Copy the code

At this point we need the fifth hook, useRef, to help us fix the dependency, so the correct way to write it is

  const ref = useRef();// ref is a fixed variable that points to the same value in each render
  ref.current = {num, bigNum};// Help us remember the latest value
  useEffect(() = > {
    return () = > {
      const {num, bigNum} = ref.current;
      reportStat(num, bigNum);
    };
  }, [ref]);
Copy the code

Complete counter

After making 5 hooks, our complete assembly is as follows

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 ...
}
Copy the code

Of course, based on the customizable feature of hook, we can separate this code into a hook. In this way, we only need to export the data and methods, so that various Counter components expressed by UI can be reused, and UI can be isolated from business, which is convenient for maintenance.

function useMyCounter(){
  / /... slightly
  return { num, bigNum. addNum, addNumBig, numBtnColor, bigNumBtnColor}
}
Copy the code

concent setup

Hook function during each round of rendering all must be need to perform again, so inevitable during each round of rendering produces a large number of temporary closure, if we can save them, can really help the gc, reduce the pressure for some recovery now let’s look at the use of what it’s like to be setup after the transformation of the Counter.

Using Concent is very simple, just start with RunAPI before the root component, so we don’t have a module definition, just call it.

import { run } from 'concent';

run();// start with render
ReactDOM.render(<App />, rootEl)

Copy the code

The logic inside the setup function is executed only once. The apis provided by the rendering context CTX include initState, computed, Effect, setState, The state state that needs to be read in conjunction with the setState call is also obtained by CTX.

function setup(ctx) {// Render context
  const { initState, computed, effect, state, setState } = ctx;
  // Setup is executed only once before the component is first rendered, and we can write the relevant business logic internally
}
Copy the code

initState

InitState is used to initialize the state, instead of useState, so that we don’t have to worry about partitioning the state granularity when our component state is large.

initState({ num: 6.bigNum: 120 });
Copy the code

Functional initialization state is also supported here

initState(() = >({ num: 6.bigNum: 120 }));
Copy the code

computed

Computed is used to define computational functions, which determine the input dependence of a computation when deconstructed from a parameter list, and is more straightforward and elegant than useMemo.

// This function is triggered only when num changes
computed('numBtnColor'.({ num }) = > (num > 100 ? 'red' : 'green'));
Copy the code

Here we need to define two computed functions that can be configured with the computed object descriptor so that we only need to invoke computed once

computed({
  numBtnColor: ({ num }) = > num > 100 ? 'red' : 'green'.bigNumBtnColor: ({ bigNum }) = > bigNum > 1000 ? 'purple' : 'green'});Copy the code

effect

The usage of effect is the same as that of useEffect, except that only the key name is passed in to the dependent array. Meanwhile, the life cycle of function component and class component is uniformly encapsulated in Effect, so that users can transfer their business to class component without any modification

effect(() = > {
  if (state.bigNum > 10000) api.report('reach 10000')},'bigNum'])
effect(() = > {
  // Here you can write what you need to do when you finish rendering the first time
  return () = > {
  	// The cleanup function triggered during uninstallation
    api.reportStat(state.num, state.bigNum)
  }
}, []);
Copy the code

setState

To change state, we define methods inside Setup based on setState and then return them. We can then call them in any component that uses setup using ctx. Settings

function setup(ctx) {// Render context
  const { state, setState } = ctx;
  return {// Export method
    addNum: () = > setState({ num: state.num + 1 }),
    addNumBig: () = > setState({ bigNum: state.bigNum + 100}}}),Copy the code

Complete Setup Counter

Based on the above apis, the logical code of our final Counter is as follows

function setup(ctx) {// Render context
  const { initState, computed, effect, state, setState } = ctx;
  // Initialize the data
  initState({ num: 6.bigNum: 120 });
  // Define the calculation function
  computed({
    // The input dependency of the calculation is determined when the argument list is deconstructed
    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}}}),Copy the code

After defining the core business logic, we can use useConcent to assemble our setup inside any function component. UseConcent returns a render context (the same object reference as the setup function argument list, sometimes called the instance context). We can extract target data and methods from CTX on demand. For this example, we can export three keys: State (data), Settings (method returned by the setup package), and refComputed(container for calculating function results of the instance) to use.

import { useConcent } from 'concent';

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

As we mentioned above, setup can also be applied to class components by using register. Note that the assembled class components can directly obtain the rendering context generated by Concent from this. CTX, and this.state and this.ctx.state are equivalent. SetState and this.ctx.setState are also equivalent, so user code 0 can access Concent.

import { register } from 'concent';

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

conclusion

Compared to native Hooks, Setup locks business logic inside functions that are executed only once, provides a friendlier API, and is perfectly compatible with class components and function components, allowing users to escape the hassle of using hook rules (see useEffect and useRef for example). , rather than learning disabilities these constraints can be passed on to the user, at the same time also more friendly to gc, believe that everyone has a default hook is the react of an important invention, but in fact it is not for user, but in view of the framework, the user is actually does not need to know about the brain and the details of the rules of burning, for the concent users, It only takes a hook to open a portal to implement all the business logic inside another space, and that logic can be reused to class components as well.

Dear guest officer read so much, still don’t hurry to try, the following provides two ways of writing links, for you to play 😀

  • The original hook Counter
  • setup Counter

one more thing

If we want to share the state of the two hook Counter, we need to modify the code to access redux or build our own Context. However, under the development mode of Concent, there is no need to modify the setup. We just need to declare a module in advance and register the module that belongs to the component. This silky migration process gives users the flexibility to deal with complex scenarios.

import { run } from 'concent';

run({
  counter: {state: { num:88.bigNum: 120}},//reducer: {... }, // If the operation data flow is complex, you can upgrade the business to this point
})

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

// For function components
@register({setup});
/ / -- > instead
@register({setup, module:'counter'});
Copy the code
  • shared Counter

After the

The articles

  • Discuss Concent & Recoil and explore new development models for react data flow
  • Redux, Mobx, Concent: How will the younger generation compete with the older generation

❤ star me if you like concent ^_^

Edit on CodeSandbox

Edit on StackBlitz

If you have any questions about Concent, you can scan the code and consult the group. We will try our best to answer your questions and help you learn more 😀.