The preface

This article focuses on the five hooks of Concent setup and React. Since setup is mentioned, it cannot be separated from the keyword composition API. To be precise, setup is an overview brought by composition API. The composition API and the Optional API are two ways to organize code, which 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

React solves both the above two points gracefully with Hook. What other advantages do composite apis have compared with Hook? Composition API is statically defined, which solves the performance problem that hook must regenerate temporary closure functions every time it renders. 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

Hey hey, write here, novice react to trap had been made, and the closure of the old value trap, unloading to the moment of submission is the initial value, at the same time, the cleaning function useEffect here of writing in the IDE will also be a warning, because internal use num, bigNum variables, so the requirements depend on our statement.

useRef

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(); Current = {num, bigNum}; // ref is a fixed variable, and every render round points to the same 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(){ // .... 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.

// 仅当num发生变化时,才触发此计算函数
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(() => {return () => {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 data initState({num: 6, bigNum: 120}); // Define a computed function ({// The input dependency of the parameter list is determined during numBtnColor: ({num}) => num > 100? 'red' : 'green', bigNumBtnColor: ({ bigNum }) => bigNum > 1000 ? 'purple' : 'green', }); 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). Target data and methods are available on demand from CTX, which we can export for this example

Three keys are used: state(data), Settings (a method returned by a setup package), and refComputed(a container for calculating function results for instances).

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.

It is better to pass it by hand than to pass it by eye a hundred times. Here are the links of two kinds of writing methods

  • The original hook Counter
  • setup Counter

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: {... }, // For the function component useConcent({setup}); // --> useConcent({setup, module:'counter'}) // For function component @register({setup}); @register({setup, module:'counter'});Copy the code
  • shared Counter

One more thing

If Concent is interested in building an admin site, we also provide a sample site tntWeb-admin for your reference. Thanks to wP2Vite support, tntWeb-admin can be used for both vite and Webpack startup in 3 steps:

git clone [email protected]:tnfe/tntweb-admin.git
npm i
npm run vite
Copy the code

Of course, if you want to start with Webpack, you can use NPM run start, but it is recommended to start with webpack build, i.e. NPM run build.

In addition to double with drive, tntweb – admin has built-in features, according to the facts in the theme of the skin, the TAB, 27 kinds of dynamic layout, and other functions, welcome the attention, at the same time he is also built into the micro site front-end architecture patterns, this one is still in development, we more template page after release, follow-up and micro front-end deployment pattern documents ready, Will be the first time open to the majority of developers to share.

team

TNTWeb – Tencent news front end team, TNTWeb is committed to the exploration of cutting-edge technology in the industry and the improvement of team members’ personal ability. For front-end developers to organize the applets and the latest quality content in the field of web front-end technology, weekly update ✨, welcome star, github address: github.com/tnfe/TNT-We…