The End of 7 Criteria for Reliable React Component Design

  Front end, javascript, react.js

Translation: Liu Xiaoxi

Original link:https://dmitripavlutin.com/7- …

This article focuses on testability and significance. Due to limited level, some translations in this article may not be accurate enough. If you have better ideas, please point them out in the comment area.

AlthoughCombinationmultiplexingAndPure componentThe reading volume of the three guidelines is not high, but based on the principle of beginning and ending, of course, I personally still think this article is very high quality, and I still insist on translating it. This is the endReliable React Component DesignI hope this is the last article of this article, which will be helpful to your component design.

More articles to stamp:https://github.com/YvetteLau/ …

I am a dividing line.

If you haven’t read the first few principles:

Testeable and Tested

After testingThe component of verifies whether the output meets the expectation given the input.

TestableComponents are easy to test.

How do you ensure that components work as expected? You can say disapprovingly: “I verified its correctness manually”.

If you plan to manually verify the modifications of each component, sooner or later, you will skip this tedious process, which will lead to defects in your components sooner or later.

This is why automated component validation is important: unit testing. Unit tests ensure that components work properly each time changes are made.

Unit testing involves not only early error detection. Another important aspect is to be able to verify whether the component architecture is reasonable.

I find the following points particularly important:

A component that cannot be tested or is difficult to test is likely to be badly designed.

Components are difficult to test often because they have manypropsDependencies, prototype requirements, and access to global variables are all signs of poor design.

When a component’s architecture design is very fragile, it becomes difficult to test. When a component is difficult to test, your big concept will skip the process of writing unit tests, and the final result is: the component has not been tested.

clipboard.png

In a word, the reason why applications need not be tested is because of improper design. Even if you want to test such applications, you can’t do it.

Case Study: Testability Means Good Design

Let’s test itPackage sectionTwo versions of<Controls>Components.

import assert from 'assert';
import { shallow } from 'enzyme';

class Controls extends Component {
    render() {
        return (
            <div className="controls">
                <button onClick={() => this.updateNumber(+1)}>
                    Increase
                </button>
                <button onClick={() => this.updateNumber(-1)}>
                    Decrease
                </button>
            </div>
        );
    }
    updateNumber(toAdd) {
        this.props.parent.setState(prevState => ({
            number: prevState.number + toAdd
        }));
    }
}

class Temp extends Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }
    render() {
        return null;
    }
}

describe('<Controls />', function () {
    it('should update parent state', function () {
        const parent = shallow(<Temp />);
        const wrapper = shallow(<Controls parent={parent} />);

        assert(parent.state('number') === 0);

        wrapper.find('button').at(0).simulate('click');
        assert(parent.state('number') === 1);

        wrapper.find('button').at(1).simulate('click');
        assert(parent.state('number') === 0);
    });
});

We can see<Controls>Testing is complicated because it depends on the implementation details of the parent component.

An additional component is required for testing.<Temp>Which simulates the parent component, validates<Controls>Whether the status of the parent component has been modified correctly.

When<Controls>Testing becomes easier independent of the implementation details of the parent component. Now let’s look at how the correctly packaged version is tested:

import assert from 'assert';
import { shallow } from 'enzyme';
import { spy } from 'sinon';

function Controls({ onIncrease, onDecrease }) {
    return (
        <div className="controls">
            <button onClick={onIncrease}>Increase</button>
            <button onClick={onDecrease}>Decrease</button>
        </div>
    );
}

describe('<Controls />', function () {
    it('should execute callback on buttons click', function () {
        const increase = sinon.spy();
        const descrease = sinon.spy();
        const wrapper = shallow(
            <Controls onIncrease={increase} onDecrease={descrease} />
        );

        wrapper.find('button').at(0).simulate('click');
        assert(increase.calledOnce);
        wrapper.find('button').at(1).simulate('click');
        assert(descrease.calledOnce);
    });
});

Well-packaged components are simple and clear to test. On the contrary, components that are not properly packaged are difficult to test.

Testability is a practical standard for determining the structural integrity of components.

Full of meaning

It is easy to see what a meaningful component does.

Readability of code is very important. How many times have you used fuzzy code? You see the string, but you don’t know what it means.

Developers spend most of their time reading and understanding code, rather than actually writing it. We spent 75% of our time understanding the code, 20% of our time revising the existing code, and only 5% of our time writing new code.

The extra time spent on readability can reduce the understanding time of future teammates and themselves. As applications grow, naming practices become important because understanding increases with the amount of code.

It is easy to read meaningful code. However, to write meaningful code requires clear code practice and constant efforts to express yourself clearly.

Component naming

Pascal nomenclature

Component names are concatenated by one or more Pascal words (mainly nouns), such as:<DatePicker><GridItem>Application<Header>.

Specialization

The more specialized a component is, the more words its name may contain.

Named<HeaderMenu>The component of indicates that there is a menu in the header. Name<SidebarMenuItem>Represents a menu item located in the sidebar.

Components are easy to understand when the name meaningfully implies intent. To achieve this, you usually have to use a detailed name. That’s good: more detail is better than less clarity.

Suppose you navigate some project files and identify 2 components:<Authors>And<AuthorsList>. Can you tell the difference between them based on name only? Probably not.

In order to get detailed information, you have to open it<Authors>Source files and browse the code. After the word, you know<Authors>Acquiring an author list from a server and presenting the author list<AuthorsList>Components.

A more professional name than<Authors>This situation will not be created. Better names such as:<FetchAuthors>,<AuthorsContainer>Or<AuthorsPage>.

A word-a concept

A word represents a concept. For example, a collection of presentation item concepts is represented by list words.

Each concept corresponds to a word and then maintains a consistent relationship throughout the application. The result is a predictable mapping of word concepts.

When many words represent the same concept, readability will be affected. For example, you define a component that presents an order list as:<OrdersList>, define another component that renders the expense list as:<ExpensesTable>.

The same concept of a set of rendered items is represented by 2 different words:listAndtable. There is no reason to use different words for the same concept. It adds confusion and breaks the consistency of naming.

Name the component<OrdersList>And<ExpensesList>(Uselist) or<OrdersTable>And<ExpensesTable>(Usetable)。 Use words that you think are better, as long as they are consistent.

Notes

The meaningful names of components, methods, and variables are sufficient to make code readable. Therefore, comments are mostly redundant.

Case Study: Writing Self-explanatory Codes

Common misuse of annotations is an explanation for unrecognized and ambiguous naming. Let’s look at examples like this:

// <Games> 渲染 games 列表
// "data" prop contains a list of game data
function Games({ data }) {
    // display up to 10 first games
    const data1 = data.slice(0, 10);
    // Map data1 to <Game> component
    // "list" has an array of <Game> components
    const list = data1.map(function (v) {
        // "v" has game data
        return <Game key={v.id} name={v.name} />;
    });
    return <ul>{list}</ul>;
}

<Games
    data=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }]
/>

The comments in the above example clarify the ambiguous code.<Games>,data,data1,vFigures10They are meaningless and difficult to understand.

If you refactor components, use meaningfulpropsName and variable name, then comments can be omitted:

const GAMES_LIMIT = 10;

function GamesList({ items }) {
    const itemsSlice = items.slice(0, GAMES_LIMIT);
    const games = itemsSlice.map(function (gameItem) {
        return <Game key={gameItem.id} name={gameItem.name} />;
    });
    return <ul>{games}</ul>;
}

<GamesList
    items=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }]
/>

Don’t use comments to explain your code, but the code is comments. (xiaoxi note: many people may not be able to make comments based on code. in addition, due to the different level of team members, people should still write appropriate comments.)

Expressive ladder

I divide the expressive force of a component into 4 steps. The lower the component’s position on the stairs, the more effort it takes to understand.

clipboard.png

You can understand the function of components in the following ways:

  • Read variable names andprops
  • Read documents/comments
  • Browse code
  • Consulting author

If the variable names andpropsIt provides enough information to make you understand the function and usage of this component, which is a kind of super expression ability. Try to maintain this high quality level.

Some components have complex logic, and even good naming cannot provide necessary details. Then you need to read the document.

If the document is missing or there are no documents that do not answer all the questions, you must browse the code. This is not the best choice because it takes extra time, but it is acceptable.

The next step is to ask the author of the component for details when browsing the code is not helpful for understanding the component. This is definitely a wrong name and should be avoided. It is best to let the author refactor the code or refactor the code himself.

Continuous improvement

Rewriting is the essence of writing. Professional writers rewrite their sentences over and over again.

To generate high-quality text, you must rewrite the sentence many times. Read written text, simplify confusing areas, use more synonyms, and delete cluttered words-then repeat until you have a pleasant passage.

Interestingly, the same rewrite concept applies to design components. Sometimes it is almost impossible to create the correct component structure on the first attempt because:

  • Urgent project scheduling does not allow enough time to be spent on system design.
  • The method originally chosen was wrong.
  • Just found an open source library that can better solve the problem.
  • Or for any other reason.

The more complex the component, the more verification and refactoring is required.

clipboard.png

Does the component fulfill a single responsibility, is it well packaged, and is it fully tested? If you cannot answer a certain yes, please identify the vulnerable part (by comparing with the above 7 principles) and refactor the component.

In fact, development is a never-ending process that can review previous decisions and make improvements.

Reliability is very important.

Quality assurance of components requires effort and periodic review. This investment is worthwhile because the right components are the foundation of a well-designed system. This system is easy to maintain and grow, and its complexity increases linearly.

Therefore, development is relatively convenient at any project stage.

On the other hand, as the system size increases, you may forget to plan and periodically correct the structure to reduce coupling. It’s just a function.

However, at the inevitable moment when the system becomes sufficiently tightly coupled, meeting the new requirements becomes exponentially complicated. You can’t control the code, but the fragility of the system controls you. Error repair will generate new errors, and code update requires a series of related modifications.

How does the sad story end? You may abandon the current system and rewrite the code from scratch, or you may continue to eat cactus. I ate a lot of cacti, and you probably did too. This is not the best feeling.

The solution is simple but demanding: write reliable components.

Conclusion

The seven principles mentioned above illustrate the same idea from a different point of view:

Reliable components implement a responsibility to hide their internal structure and provide effectivepropsTo control their behavior.

The single responsibility and encapsulation issolidThe foundation of design. (maybe, you need to know what the solid principle is. )

Single responsibility suggests creating a component that implements only one responsibility and has a reason to change.

A well-packaged component hides its internal structure and implementation details and definespropsTo control behavior and output.

Components with large combined structure. Only need to divide them into smaller blocks, and then use combinations to integrate, making complex components simple.

Reusable components are the result of carefully designed systems. Reuse code as much as possible to avoid duplication.

Side effects such as network requests or global variables make components environment-dependent. By being the samepropValues return the same output to purify them.

Meaningful component naming and expressive code are the key to readability. Your code must be easy to understand and read.

Testing is not only a way to automatically detect errors. If you find a component difficult to test, it is likely that the design is incorrect.

Successful applications stand on the shoulders of reliable components, so writing reliable, extensible and maintainable components is very important.

Which principles do you think are useful when writing React components?

Finally, I would like to thank all my friends who are willing to spend precious time reading this article. If this article gives you some help or inspiration, please don’t be stingy with your praise and Star. Your affirmation is my greatest motivation to move forward.https://github.com/YvetteLau/ …

I recommend paying attention to my public number.

clipboard.png