In the process of reconstruction, we will certainly encounter the problem of technology selection for the new code. Considering the vitality of this technology, that is, whether it is a newer technology, as well as its flexibility and extensibility, we hope to achieve the upgrade of technology stack that does not need to be enlarged in the next three years at least. My refactoring experience was to convert jQuery code into React, which, you know, is probably the most difficult and labor intensive refactoring task. After reading the previous code often thousands of lines of Class, chaotic use of global variables, more and more feel that the code must be written simply, do not use too much black technology, especially various design patterns, in order to reuse and iteration out of the massive IF judgment. Code is not for the machine to see, is for people to see, he needs to let the later people quickly understand, but also can let others in your code on the basis of rapid iteration of new requirements. So we need to figure out what technology stack to use and how to organize the code.

Why Function Component

The Class Component versus Function Component debate has a long history. From my own experience, I think these are two different approaches to programming.

Class Component Object-oriented programming inheritance The life cycle
Function Component Functional programming combination Data driven

Why not use Class

First of all, if we use object-oriented programming, we should note that it is not just a simple matter of defining a Class. We know that object-oriented has three main features: inheritance, encapsulation, and polymorphism.

Is the front end really suitable for inheritance in the first place? Is the UI really suitable for inheritance, exactly? In the real world, abstract things better defined as a class, the class had the means of classification and categories, as we put the tiger, cat, lion these creatures known collectively as animals, so we can define the class of an animal, but the real world and no animals the entity, but UI page are all can see the real thing, We can divide a page into different blocks, and then use “composition” between blocks. Therefore, I think UI components should not be inherited, but should be composed. If you write a component that inherits from a class, it will be hard to refactor or even rewrite it.

Encapsulation is about using encapsulated methods to expose properties in a class. However, our component basically exposes internal events and data through props and internal methods through refs, without using encapsulated features in nature.

Even less use of polymorphism, polymorphism is more interface based, or abstract class, but JS is weak, TS might be better.

To sum up, as a front-end UI programmer, I prefer to use a combination of functions.

Why is it data-driven

React and Vue are all about data changes, data binding to the view, data driving, data changes causing UI re-rendering, but the lifecycle is not straightforward when describing this problem. In Class Component, how do we detect data changes? ShouldUpdate is basically a shouldUpdate lifecycle, why should we care about a lifecycle when we are programming, when we are focusing on data and business, this part of the business is more like a side effect, or should not be exposed to developers.

To sum up, this is where I think Function Component + Hooks have a better programming experience, but this is only a relatively one-sided perspective, there is no good or bad. After all, even React officials say that there is no good or bad writing between the two methods, and the performance difference can be almost ignored. React will support both for a long time.

Hooks: True responsive programming

What exactly is responsive programming? Everyone is vague and uncomprehending. A lot of people don’t get it right. From my years of programming experience, reactive programming is “programming with asynchronous data flows.” Let’s take a look at how the front end typically handles asynchronous operations. Common asynchronous operations include asynchronous requests and page mouse-action events. When handling such operations, we usually use an event loop, which is an asynchronous event stream. However, the event loop does not explicitly solve the problem of event dependency. Instead, we need to manage the order of calls when coding. For example:

const x = 1;
const a = (x) = > new Promise((r, j) = >{
  const y = x + 1;
	r(y);
});
const b = (y) = > new Promise((r, j) = >{
  const z = y + 1;
	r(z);
});
const c = (z) = >  new Promise((r, j) = >{
  const w = z + 1;
	r(w);
});
// There are three asynchronous requests, and they have dependencies between them
a(x).then((y) = >{
	b(y).then((z) = >{
  	c(z).then((w) = >{
  		// The final result
      console.log(w); })})})Copy the code

The above event-based callback, which we replace with Hooks, looks like this:

import { useState, useEffect } from 'react';

const useA = (x) = > {
	const [y, setY] = useState();
  useEffect(() = >{
    // Suppose there is an asynchronous request
  	setY(x + 1);
  }, [x]);
  return y;
}

const useB = (y) = > {
	const [z, setZ] = useState();
  useEffect(() = >{
    // Suppose there is an asynchronous request
  	setZ(y + 1);
  }, [y]);
  return z;
}

const useC = (z) = > {
	const [w, setW] = useState();
  useEffect(() = >{
    // Suppose there is an asynchronous request
  	setW(z + 1);
  }, [z]);
  return w;
}

// The above three are custom Hooks, which specify dependencies between each variable data. You don't even need them
// Know the return order of each of their asynchronous requests, just whether the data has changed.
const x = 1;
const y = useA(x);
const z = useB(y);
const w = useC(z);
// The final result
console.log(w);
Copy the code

As we saw in the example above, Hooks are written like simple procedural programming, step by step, logical, and each custom Hooks you can think of as a function that does not need to share state with the outside world, are self-contained and easily tested.

Start streamlining code

Based on the tools provided by React Hooks and the thinking of reactive programming described above, we began our journey of lean code, which can be summarized as: What happens to a thousand lines of code? Splitting works best! How do you break it up? First, file by function module. Function module here refers to the same syntax structure, such as side effects, event handlers, etc. Multiple custom Hooks and functions can be written in a single file, depending on the implementation. The ultimate goal is to keep only the steps of the business logic that the component is implementing in the main file.

Why are there thousands of lines of a single code file?

If you write all the code of a Component into a Component, it is likely that there will be thousands of lines of code in a file, or thousands of lines of code in a Function if you write the Component with Function Component. Of course, thousands of lines of code in a file are unbearable for a sound developer, and a disaster for a subsequent refactoring.

Why put all this code in one file? Doesn’t it smell good to split it down? The next question becomes how to split a component. To split a component, we need to know what a typical component looks like.

A typical component

Hooks are new, they are as flexible as functions, they don’t even include the way I chose to write new code, so what does a typical Component based on Function Component + Hooks contain?

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import {
  Row, Select,
} from 'antd';
import Service from '@/services';

let originList = [];
const Demo = ({ onChange, value, version, }) = > {
  // State management
  const [list, setList] = useState([]);

  // Side effect function
  useEffect(() = > {
    const init = async() = > {const list = awaitService.getList(version); originList = list; setList(list); }; init(); } []);/ / event handler
  const onChangeHandler = useCallback((data) = > {
    constitem = { ... val,value: val.code, label: val.name };
    onChange(item);
  }, [onChange]);
  
  const onSearchHandler = useCallback((val) = > {
    if (val) {
      const listFilter = originList.filter(item= > item.name.indexOf(val) > -1);
      setList(listFilter);
    } else{ setList(originList); }} []);// UI component rendering
  return (
    <Row>
        <Select
        labelInValue
        showSearch
        filterOption={false}
        value={value}
        onSearch={onSearchHandler}
        onChange={onChangeHandler}
        >
         {list.map(option => (<Option value={option.id} key={option.id}>{option.name}</Option>))}
        </Select>
    </Row>
  );
};

export default Demo;

Copy the code

From the above example, we can see what a basic Function Component contains:

  • UseState – based status management
  • UseEffect based side effects management
  • UseCallback – based event handler
  • The UI parts
  • Conversion functions, which are used to request the return of data, or some utility functions that do not have generality

Split functional modules

First, we split the above functional modules into multiple files:

| - container | - hooks. Js / / all kinds of custom hooks. | - handler js / / conversion function, and don't need hooks event handler | - index. The js / / master file, Only keep implementation steps | - index. The CSS / / CSS fileCopy the code

What kind of code is easy to read?

I’ve refactored a lot of other people’s code, and I get frustrated when I see a bunch of logical code stacked together, only to discover later that it all made the same mistake. There is no clear distinction between steps and implementation details. Disaster happens when you write steps and details together, especially if code is iterated over years and years. Hooks are a very efficient tool for code splitting, but they are also very flexible. There is no universal code specification in the industry, but I have a slightly different point of view. I don’t think it needs a generic code specification like Redux, because it is functional programming. The most important thing about functional programming is to break down the steps and implementation details so that code is easy to read, and code that is easy to read is responsible code.

How do you tell the difference between steps and details? An easy way to do this is to use a flow chart to represent your requirements as you sort out the requirements. Each node is basically a step, because it doesn’t involve the implementation. Too much explanation, a little wordy, I’m sure you understand, right? The separation of steps and details also helps refactoring because each step is a function, and there is no global variable like this in class. When you need to delete a step or override a step, you don’t have to affect the other step functions. Also, once functionalized, unit testing is no doubt very simple.

Follow the steps to split the master file

The goal is to keep only business steps in the main file.

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import {
  Row, Select,
} from 'antd';
import { onChangeHandler } from './handler';
import { useList } from './hooks';
import Service from '@/services';

const Demo = ({ onChange, value, version, }) = > {
  // List state operations, where search changes list
  const [originList, list, onSearchHandler] = useList(version);
  
  // UI component rendering
  return (
    <Row>
      <Select
      labelInValue
      showSearch
      filterOption={false}
      value={value}
      onSearch={onSearchHandler}
      onChange={()= > onChangeHandler(originList, data, onChange)}
      >
        {list.map(option => (<Option value={option.id} key={option.id}>{option.name}</Option>))}
      </Select>
    </Row>
  );
};

export default Demo;

Copy the code

See above is based on the separation of steps and details of the idea, the above component was reconstructed, including only two steps:

  • Operations on list data
  • The UI rendering

With this split, the main file contains only a few steps in the code, all replaced with custom hooks that can be written to the extended.js file. The file in this.js reads as follows:

import { useState, useEffect, useCallback } from 'react';

let originList = [];
export const useList = (version) = > {
  // State management
  const [list, setList] = useState([]);
   // Side effect function
  useEffect(() = > {
    const init = async() = > {const list = awaitService.getList(version); originList = list; setList(list); }; init(); } []);// Process the SELECT search
  const onSearchHandler = useCallback((val) = > {
    if (val) {
      const listFilter = originList.filter(item= > item.name.indexOf(val) > -1);
      setList(listFilter);
    } else{ setList(originList); }} []);return [originList, list, onSearchHandler];
}
Copy the code

As you can see, the hooks. Js file contains the data and the methods to change the data, and all the side effects are contained in it. It is also recommended that all asynchronous requests be processed with await. What benefits can be googled.

The handler.js file contains the following contents:

/ / event handler
export const onChangeHandler = (originList, data, onChange) = > {
  const val = originList.find(option= > (option.id === data.value));
  constitem = { ... val,value: val.code, label: val.name };
  onChange(item);
};
Copy the code

The example above is so simple that you might think you don’t need this refactoring at all, because it adds too many files to the code. Very good! I agree with you that some simple components don’t need to be broken up like this, but I view this refactoring approach not as a norm, not as a mandate, but as a value, a value for what is good code. This value boils down to one thing: Make your code easy to change. Easier To Change! Referred to as “ETC.

Code values ETC

The value of ETC is the essence of many good coding principles, such as single responsibility principle, decoupling principle and so on, which all embody ETC. Good design is about adapting to users, and for code, embracing change and adapting to change. So we need to embrace ETC. Values help you make decisions when you’re writing code, and they tell you to do this, right? Or do that? It helps you choose between different ways of coding, and it should even become a subconscious part of your coding. If you accept this value, remind yourself of it as you code.

reference

  • Responsive zhuanlan.zhihu.com/p/27678951 programming
  • How to Become a Programmer Andrew Hunt, David Thomas

The article can be reproduced at will, but please keep this link to the original text.

You are welcome to join ES2049 Studio. Please send your resume to [email protected].