3
头图

Preface

This article around the theme concent of setup and react the five hooks to expand, now referred to setup can not be separated composition api this keyword, to be exact setup by composition api brought out an overview, and composition api (combination api) And optional api (optional api) Two ways of organizing code, I believe you vue3 , they can exist at the same time, it is not mandatory that you can only use which one, but combined api The two major advantages really make developers more inclined to use it instead of optional api.

  • Package the reusable logic with function as the basic unit and inject it into any component to make the decoupling of view and business more elegant
  • Let the businesses of the same function be placed more closely together without being separated, and improve the development and maintenance experience

The above two points are hook in React. Then, compared to hook , what other advantages does the combined api have? I believe that some friends already know when Youda introduced the combination api, the combination api is statically defined, which solves the hook must regenerate the temporary closure function every rendering, and there is no trap of the old value of closure hook , Manual detection dependence and other coding experience problems.

However, react is all in js , so as long as we dare to think and do, all excellent programming models can be absorbed. Next, we will use the native hook and the setup and use examples and explanations to thoroughly hook mentioned by Youda^\_^

image.png

react hook

We are here to design a traditional counter, the requirements are as follows

  • There is a decimal, 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 large number reaches 10000, the number of the large 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 finishing 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);
}

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 function

  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

 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 . Here we are used to deal with the two requirements

  • When the large number reaches 10000, the number of the large number is reported
  • When the calculator is uninstalled, report the current number
  useEffect(() => {
    if (bigNum > 10000) api.report('reach 10000')
  }, [bigNum]);
  useEffect(() => {
    return ()=>{
      api.reportStat(num, bigNum)
    }
  }, []);

Hey, at this point, react newbies have been brought into the trap, that is, closure old value trap , the original value is submitted at the moment of uninstallation, and the useEffect writing method of the cleanup function here will also be warned if it is written in the IDE. , Because the num, bigNum variable is used internally, we are required to declare the dependency.

useRef

But if we change to the following way to avoid IDE warnings, it is obviously 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.

  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

  const ref = useRef(); // ref是一个固定的变量,每一轮渲染都指向同一个值
  ref.current = {num, bigNum}; // 帮我们记住最新的值
  useEffect(() => {
    return () => {
      const {num, bigNum} = ref.current;
      reportStat(num, bigNum);
    };
  }, [ref]);

image.png

Complete counter

After using 5 hooks, our complete component 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 ...
}

Of course, we can 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, and at the same time, ui and business can be reused. Isolation is conducive to maintenance.

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

concent setup

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 save them, it can indeed help gc reduce some recycling setup , now let’s take a look at the Count after the transformation is completed with 06125c04de6918

image.png

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

import { run } from 'concent';

run();// 先启动,在render
ReactDOM.render(<App />, rootEl)

Then we will be more than a little transformation logic, all wrapped into setup internal logic of the internal setup function will only be executed once, the need to use a rendering context ctx API provided are initState , computed , effect , setState , in conjunction with setState call It also needs to read the status state , which is also obtained ctx

function setup(ctx) {// 渲染上下文
  const { initState, computed, effect, state, setState } = ctx;
  // setup仅在组件首次渲染之前执行一次,我们可在内部书写相关业务逻辑
}

initState

initState 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.

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

The function initialization state is also supported here

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

computed

computed 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.

// 仅当num发生变化时,才触发此计算函数
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 we only need to call computed once.

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

effect

effect usage and useEffect is exactly the same, the only difference is dependent on an array of key names can only pass, while effect internal components of the life cycle function and class components were unified package, users can migrate business without making any changes to it Class component

effect(() => {
  if (state.bigNum > 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
  // 这里可以书写首次渲染完毕时需要做的事情
  return () => {
      // 卸载时触发的清理函数
    api.reportStat(state.num, state.bigNum)
  }
}, []);

setState

Used to modify the state, we define the method based on setState setup , and then return, then we can use this setup in any component, get these method handles ctx.settings

function setup(ctx) {// 渲染上下文
  const { state, setState } = ctx;
  return {// 导出方法
    addNum: () => setState({ num: state.num + 1 }),
    addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
  }
}

Complete Setup Counter

Based on the above apis, our final counter logic code is as follows

function setup(ctx) {// 渲染上下文
  const { initState, computed, effect, state, setState } = ctx;
  // 初始化数据
  initState({ num: 6, bigNum: 120 });
  // 定义计算函数
  computed({
    // 参数列表解构时就确定了计算的输入依赖
    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 {// 导出方法
    addNum: () => setState({ num: state.num + 1 }),
    addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
  }
}

image.png

After defining the core business logic, we can use useConcent inside any function component to assemble our defined setup to use it. useConcent will return a rendering context (and the setup function parameter list refers to the same object reference , Sometimes we also call the instance context), we can ctx on demand, for this example, we can export

state (data), settings (setup package return method), refComputed (example calculation function result container) these three keys can be used.

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 setup same class may be assembled to the assembly, use register to, class components should be noted that after assembly, from this.ctx directly on concent generated for rendering context, while it this.state and this.ctx.state are equivalent , this.setState and this.ctx.setState are also equivalent, it is convenient for the user to change the code 0 to access concent .

import { register } from 'concent';

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

Concluding remarks

Compared with native hooks, setup the business logic inside 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 hook of the rules of 06125c04de6f9d (think useEffect) With useRef, does it have a significant cognitive cost?), instead of passing these constraints on learning obstacles to users, it is also more friendly to gc. I believe everyone has defaulted that hook is an important invention react In fact, it is not for users, but for frameworks. Users don’t need to understand the details and rules of brain-burning. For concent users, they only need a hook to open a portal, and then they can access another portal. All business logic is implemented inside the space, and these logics can also be reused on class components.

image.png

better to go through the eyes a hundred times than once by hand. The following are links to the two ways of writing. You must have some experience

If you want to appeal two hook Counter state share, we need to transform the code access redux or self Context , but concent under development model, setup without any transformation, only need a statement ahead of a module, and then in the registration component belonging to the Module is enough, this silky migration process allows users to flexibly deal with various complex scenarios.

import { run } from 'concent';

run({
  counter:{
    state: { num:88, bigNum: 120 },
  },
  //reducer: {...}, // 如操作数据流程复杂,可再将业务提升到此处
})

// 对于函数组件
useConcent({setup});
//  ---> 改为
useConcent({setup, module:'counter'})

// 对于函数组件
@register({setup});
//  ---> 改为
@register({setup, module:'counter'});

One more thing

If you are interested in building an admin site by concent, we also provide a sample site tntweb-admin for your reference. Thanks to wp2vite , it realizes the dual-engine drive capability that can be started by both vite and webpack. You only need 3 steps:

git clone git@github.com:tnfe/tntweb-admin.git
npm i
npm run vite

Of course, if you want to start with npm run start connect, but it is recommended to use webpack to build it, that is, npm run build .

In addition to the dual-engine driver, tntweb-admin also has built-in features, such as real-time theme , tabs , 27 dynamic typesetting . At the same time, the front-end architecture itself is also welcome. The site, this section is still under development. After we have released more template pages and the micro front-end deployment mode document is ready, it will be opened to developers for sharing as soon as possible.

team

TNTWeb-Tencent news front-end team, TNTWeb is committed to the industry's cutting-edge technology exploration and the improvement of team members' personal capabilities. The latest high-quality content in the field of small programs and web front-end technology has been compiled for front-end developers, updated weekly✨, welcome to star, github address: https://github.com/tnfe/TNT-Weekly

image.png


TNTWEB
3.8k 声望8.5k 粉丝

腾讯新闻前端团队