preface

How to write maintainable and readable code has been a problem for many people. About how to name variables, how to optimize if else and so on small skills, here do not do the introduction, recommend to see the “Corpus code 2”, thousands of books, are not as good as a “Corpus Code 2”.

Since the work, I have been writing some repetitive and complicated interactive pages without organizing my thoughts. This article is some experience I have summed up in the project after working for a year and a half.

layered

For business code, most front-end applications are still to show data, nothing more than get data from the interface, a series of data format, display in the page.

First of all, you should layer as much as possible. Traditional MVC layer is good for front-end development, but for complex pages, as business logic increases, it tends to cause controller bloat. So, on top of that, you can divide the controller into Formatter, service, and so on.

Here are some simple hierarchical directory structures.

    + pages
        + hotelList
            + components
                + Header.jsx
            + formatter
                + index.js
            + share
                + constants.js
                + utils.js
            + view.js
            + controller.js
            + model.js
Copy the code

Service

Manage all request paths uniformly and encapsulate the network requests involved in the page as classes.

// api.js
export default {
    HOTELLIST: '/hotelList'.HOTELDETAIL: '/hotelDetail'
}

// Service.js
class Service {
    fetchHotelList = (params) = > {
        return fetch(HOTELLIST, params);
    }
    fetchHotelDetail = (params) = > {
        returnfetch(HOTELLIST, params); }}export default new Service
Copy the code

This has the benefit of knowing exactly what requests are involved in the page, and if you use TypeScript, it’s handy to have an error in all of the calls when a request method name changes.

formatter

The Formatter layer stores methods that format data, receive data, return new data, and should not involve any additional logic for the benefit of unit testing. A single format function should not format too much data, and functions should be properly split and reused according to their functionality.

mvc

As the name suggests, the controller is the C in MVC, and the controller should be the place where the various side effects (network requests, caching, event responses, and so on) are handled.

When processing a request, the controller calls the corresponding method in the service. When it gets the data, it calls the formatter method to store the formatted data and display it on the page.

class Controller {
    fetchHotelList = () = > async (dispatch) => {
        const params = {}
        this.showLoading();
        try {
            const res = await Service.fetchHotelList(params)
            const hotelList = formatHotelList(res.Data && res.Data.HotelList)
            dispatch({
                type: 'UPDATE_HOTELLIST',
                hotelList
            })
        } catch (err) {
            this.showError(err);
        } finally {
            this.hideLoading(); }}}Copy the code

It is recommended that you use only pure function components. After hooks, react will become more pure. (In fact, the react component can also be treated as an MVC structure, state is model, render is view, various handler methods are controller).

With React, the outermost layer is usually called the container component, and we do network requests and other side effects inside the container component.

In this case, some of the logic from the container components can be stripped off and put into the controller (as react-IMVC does), so that the controller can be given a life cycle and the container components are used for pure presentation only.

We can write a more pure view by putting the life cycle of the container component in the wrapper and calling the life cycle wrapped in the Controller inside the wrapper. For example:

wrapper.js

// Wrapper.js (pseudo code)
const Wrapper = (view) = > {
    return class extends Component {
        constructor(props) {
            super(props)
        }
        componentWillMount() {
            this.props.pageWillMount && this.props.pageWillMount()
        }
        componentDidMount() {
                this.props.pageDidMount && this.props.pageDidMount()
            }
        }
        componentWillUnmount() {
            this.props.pageWillLeave && this.props.pageWillLeave()
        }
        render() {
            const {
                store: state,
                actions
            } = this.props
            return view({state, actions})
        }
    }
}
Copy the code

view.js

// view.js
function view({ state, actions }) {
    
    return (
        <>
            <Header 
                title={state.title} 
                handleBack={actions.goBackPage}
            />
            <Body />
            <Footer />
        </>)}export default Wrapper(view)
Copy the code

controller.js

// controller.js
class Controller {
    pageDidMount() {
        this.bindScrollEvent('on')
        console.log('page did mount')}pageWillLeave() {
        this.bindScrollEvent('off')
        console.log('page will leave')}bindScrollEvent(status) {
        if (status === 'on) { this.bindScrollEvent('off'); window.addEventListener('scroll', this.handleScroll); } else if (status === 'off') { window.removeEventListener('scroll', this.handleScroll); }} // Scroll event handleScroll() {}}Copy the code

other

For the buried point, it should be in the controller, but it can also be a separate tracelog layer, as for how to implement and invoke the Tracelog layer, it is up to my personal preferences, I prefer to use publish subscribe form.

If there is also a cache involved, we can also separate a storage layer, where the cache to add, delete, check and change the various operations.

For some commonly used fixed and invariable values, it is also possible to put the constants in. Js and obtain the value by introducing constants, so as to facilitate subsequent maintenance.

// constants.js
export const cityMapping = {
    '1': 'Beijing'.'2': 'Shanghai'
}
export const traceKey = {
    'loading': 'PAGE_LOADING'
}
// tracelog.js
class TraceLog {
    traceLoading = (params) = >{ tracelog(traceKey.loading, params); }}export default new TraceLog

// storage.js
export default class Storage {
    static get instance() {
        // 
    }
    setName(name) {
        //
    }
    getName() {
        //}}Copy the code

Data and Interaction

However, it does not mean that this is enough. Layering can only ensure that the code structure is clear. If you really want to write good business code, the most important thing is that you are clear about the business logic and how the data flow on the page is. How to design the data structure more rationally? What are the interactions on the page? What are the effects of these interactions?

Take the following hotel list page, which looks simple but actually contains a lot of complex interactions.

The top is the menu of four filter items, open it contains a lot of subclass filter items, such as screening including twin beds, big beds, three beds, price/star included high-grade/luxury, ¥150-¥300 and so on.

Below is the shortcut filter, which corresponds to the subclass filter in the partial filter menu.

When we select the twin in the filter, the lower twin will also be selected by default, and when we select the lower twin, the twin in the filter category will also be selected, and the name will be displayed on the original filter.

In addition, when we click on the search box and type in ‘twin’, the word ‘twin’ will appear and indicate that this is a filter. If the user selects the twin, we still need the filter and shortcut filter to be selected by default.

All three involve filters, and if you change one, the other two will change, and the data for all three comes from three different interfaces, which is a pain.

I use this example to illustrate that before you start writing pages, you need to be familiar with the hidden interactions and data flows in your pages, and you need to design a better data structure.

For deeper list structures, key-value pairs are faster than array queries, and linkage with other data is easier by key, but there is no guarantee of order, and sometimes you need to sacrifice space for time.

// Assume that the bed type of the filter is 1, the id of the king bed is 1, and the ID of the twin bed is 2.
const bed = {
    1-1 ' ': {
        name: 'big bed'.id: 1.type: 1
    },
    '2': {
        name: 'double bed'.id: 2.type: 1}}const bedSort = [1-1 ' '.'2'] // Ensure the display order
Copy the code

When we select the king-size bed, just save the key ‘1-1’ and map it with the key in the store’s shortcut filter list (the shortcut filter should also be formatted as {‘type-id’: FilterItem}), which is more time efficient than simply iterating through the two arrays.

conclusion

Before you start writing business, you should first think about the requirements and business logic, design a reasonable data structure, and layer the code well, so that you can write more maintainable code to a certain extent.