What is business infrastructure

Business infrastructure is the upper level common infrastructure maintained by the business team, such as common methods, state management schemes, COMMIT specifications, and so on.

Business infrastructure is different from engineering infrastructure. Engineering infrastructure mainly refers to the tool chain system for construction, deployment and testing, while business infrastructure is mainly for business development.

There are two main meanings of business infrastructure: one is reuse to improve development efficiency; the other is standardization of technical architecture.

By technical architecture standardization, we mean binding best practices. There is no constraint, there is no standard landing. Standardization constraints should be established at the beginning of the project, and if they are relaxed, or “wait until necessary,” code can decay faster than expected. When I realized the need to optimize, it was already hard to turn back. The cost of what was easy at the beginning has been doubled and magnified. Any changes may require deliberation, careful testing, changing dozens of pages, spending others’ pr time and returning to the line.


Eternal vigilance is the price of liberty


But what are best practices? Is there a clear consensus across the industry? Is there a way for every front-end engineer in the field to know this? In addition to all kinds of scaffolding to provide industry general engineering solutions, what else is missing, need to be handled by the business team?


Static analysis

ESLint does nothing if you don’t configure rules for it. The effectiveness of ESLint depends on the completeness of the rule configuration. There are many mature rule sets out there, such as Standard and Airbnb. Each framework also has its own custom rule set.

Lint does more than just keep the format consistent. For example, it controls complexity (prefer-const, prefer-rest-params, Object-Type…), and consolidates ES6 syntax (prefer-const, prefer-rest-Params, Object-Type…). Syntax error (no-undef…) , constraint function definition (max-params…) And so on.

CSS also needs Lint, especially since preprocessing languages bring so many new tricks and constraints. For example, many people like nesting, but random nesting will bring many problems, such as selector weight is too high, coupled with the template structure… This problem can be circumvented using max-nesting depth or selector-max-compound-selectors. Even a common technique, like :not(:first-child), isn’t very good because it’s coupled to the DOM structure, and if someone changes the DOM structure, it can invalidate the rule, and they probably won’t know about it. This can be restricted with selector-pseudo-class-blacklist. There are also rules like selector-no-vendor-prefix to prevent people from knowing that autoprefixer is still diligently writing prefixes like -webkit- in business code.

In fact, by studying the rule set, you know the best practices for language usage.

In addition to the generic ESLint, there are other static analysis tools that can help you find specific problems, For example, JS-Copyppaste-detect, PurifyCSS (clean up useless CSS), circular-dependency-plugin (check module cyclic reference), ES-Check (check whether live code contains ES6), etc.

Lint needs to do this every compile, every commit, and every release to guard code quality.

ESLint has an autofix, where prettier is integrated further. Prettier is a necessary tool for collaborative maintenance projects.


Basic style

Wouldn’t a Web project without a basic style reset be a pain in the ass? P, H1, ul and so on have default style, all have to own manual overwrite, hate can not hand project builder.

Common override libraries include reset.css and normalize.css, while some well-known component libraries have their own reset libraries, such as bootstrap-reboot.css.

In my personal experience, if a new project CSS comes up with the following code, it must be an old driver.

*, 
*:before, 
*:after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
Copy the code

The project common base CSS library should contain the following three styles:

  • Basic Reset: Resets the browser style
  • OOCSS: Abstract class, look at the design immediately understand
  • Basic Tools: Utility classes, such as the most common clearfix

OOCSS and Basic Tools are called for development, both in the form of class names. The best known is tailwind.css.

The basic style library has four guidelines for design:

  • The minimum subset principle: Do not provide any specific UI styles that are not generic to minimize code volume and learning costs
  • The principle of minimum interference: Keep the selector weight as small as possible
  • The biggest general rule: only code that has a high probability of being used will appear here
  • Rule of minimum memorization: The name to be invoked must be easiest to remember and use

A common base style library can obviously improve development efficiency, but the difficulty is how to popularize it and get everyone to accept it.


Layout of the adapter

The core of layout adaptation is the unit. Unit involves three problems: absolute unit problem, relative unit problem and minimum unit problem. In the h5 environment, all three problems need to be considered, and rn is included in the mobile terminal because it uses absolute units, so relative units are mainly considered.

Absolute unit problem: PPI adaptation

The problem to be solved by PPI adaptation is how to find a fixed size unit when the minimum basic unit size is not fixed.

For text, we want 16px text to look the same size on any screen. That is, we want px here to be a fixed unit of actual physical size.

Setting the viewPort does this:

Size of 1 logical pixel = 1/ ppi * scaling factor = 1/163 inch

So setting the viewPort essentially turns px into an “absolute unit.”

How about cm or mm? It actually fits the scenario, but it’s a little anti-human.


Relative unit problem: Resolution fit

Resolution adaptation solves the problem of finding a relative unit that makes the same size look the same on different screens.

For example, if we have a banner chart that is 100% wide and 100 units high, we want it to scale equally on any screen size, so we need the unit here to be a relative unit.

Vw and VH are very good relative units. However, considering compatibility, the current mainstream approach is still flexible and REM is used to replace VW.


Minimum unit problem: DPR fit

The devicePixelRatio adaptation resolves the problem of how to draw 1px (device pixel) with viewport, width=device-width set.

DPR =2 means that 1px in CSS will be rendered with two device pixels, and on iphone6s it will be rendered with three device pixels.

The solutions are as follows: use decimals, use images, use gradients, use shadows, and use Transform scaling. The method of mobile Taobao is to use JS to dynamically set the initial-scale of viewport, but this method has been removed in Flexible2.0 due to too many side effects.

Reference: how big 1px is


Common components

Common components fall into five categories: Style, Utility, Widget, Layout, and Icon.

Style refers to non-logical, pure CSS components, such as hairline (1px thin line on the move).

A utility is a component suitable for calling through functions, such as toast, modal pop-ups, and action-Panels.

A widget is a component that is invoked in a template, which is quite common.

A layout is a layout component. A layout can also be implemented with CSS (like bootstrap), but some layouts may be better packaged as widgets (see RN or Native components) :

Why is a layout worth packaging separately? The main purpose is to decouple layout from widgets and reuse layout. Widgets can be replaced and removed at any time without affecting the layout.

Icon packaging forms are more, the early common way is Sprite, then there are font ICONS, there are many CSS ICONS, as well as component ICONS. ICONS are typically built with component libraries and need to be maintained in collaboration with the front-end and THE UED.

Implement arrows in SVG


There is another special class of components: pure logical components. Like the context in React. In some scenarios, it is appropriate to encapsulate/reuse only the logical parts of the component, while the style parts are customized.

For example, multiple and radio components come in a variety of forms. Directly encapsulating RadioGroup or CheckboxGroup components does not meet diverse presentation requirements.

But you can see that the logic of single and multiple choices is stable and deterministic, which means it should be possible to abstract the logic separately, leaving the concrete UI to the concrete implementation. Like this:

<CheckboxGroupProvider
    list={this.state.data} // Provide an array of all options
    value={this.state.value} // Set the default value
    onChange={v= > this.setState({ value: v })} // Synchronize changes to selected values
>
   {(item, isActive) = > ( // Consume single option data & whether it is selected
       // Concrete UI implementation
   )}
</CheckboxGroupProvider>
Copy the code


What to consider when designing a common component library is a topic that can be written in its own right. Google “Component Library Boilerplate” to find a lot of ready-made solutions, you don’t have to build from scratch.


Popup window management

Popover refers to modal, slideModal, Dialog, ActionPanel and other full-screen masks or floating on the page, and generally only one component can appear at a time.

Popovers need to be managed uniformly. With the gradual increase of operation functions on the page, a page may appear all kinds of pop-ups, such as a drainage cover of what activity, a popover of brush presence to remind you what function there is, and some functions may also be realized through the popover. In short, these popovers are independent of each other, with their own pop-up logic and timing. When they exist on the same page at the same time, conflicts may occur.

Imagine that A popup window A pops up when it meets A certain condition (the user does A certain operation, stays on the page for 3s, etc.), but there may already be other pop-ups on the page, and A does not know. So is A playing or not playing? How to control the pop-up zIndex?

Suppose, at the same time, there are 5 pop-ups that meet the conditions of pop-ups, do they have to pop up at the same time?

Obviously, if you have more than one popover, the experience suffers. So all popovers should be called in a unified wrapper. This method will design some policies, such as when to discard the shells with lower priority, and when to play through the queue and then the next one.

Non-click-triggered pop-ups (e.g. via a timed task, or a box that pops at a specific time) also present the problem of ensuring that pop-ups will pop up on the right page in a single page application. Because popovers can be global, the page can be switched at will before they pop up, so the design of popovers should also consider the binding relationship with the page.


loading/toast

Similar to the popover problem, global components such as Loading and Toast have concurrency management issues. This component seems extremely simple, but not many teams will get it right.

For example, loading components: Assume that loading is displayed when each interface initiates a request, and loading is hidden when the request ends. There may be many interface requests at a time, but only one loading can be performed on the interface at any time. For example, if A request is sent to show loading, then B request is sent to show loading. If B has not finished loading when A request is finished, it continues to show loading. Otherwise, it hides loading.

// Singleton loading components using a semaphore - like strategy
const loading = {
    count: 0.el: document.createTextNode('loading'),
    show () {
        if (this.count === 0) {
            document.body.appendChild(this.el)
        }
        this.count += 1
    },
    hide () {
        this.count -= 1
        if (this.count === 0) {
            document.body.removeChild(this.el)
        }
    }
}
Copy the code


Unlike singleton loading, each of the concurrent TOAST components needs to be displayed, preferably as a list rather than one on top of the other in the same location. Tabular rendering requires consideration of extreme scenes beyond screen boundaries, such as limiting the number of scenes to three at a time.


Click on the heavy

Repeated clicks over a short period of time can cause problems, both at the interface level (especially for create type requests) and at the code level (such as when the context is changed the second time around).

In the scenario where loading state is not added for a short time, or because the page is stuck, the popup window is not closed in time, causing the user to click ok for many times.

Considering the general functions of click events, such as highlighting feedback, disabled state, loading state, it can encapsulate a logical component, all the components involved in click events, are wrapped in a layer.

Simply speaking, the logic component for throttling click events can be encapsulated as follows:

function throttle(fn, wait) {
  let lock = false
  return function() {
    if (lock) return
    lock = true
    fn.call(null.arguments)
    setTimeout(() = > (lock = false), wait)
  }
}

function ThrottledTouchable({
  onPress,
  children,
  wait = 0
}) {
  const memorizedThrottle = useCallback(throttle(onPress, wait), [
    onPress,
    wait
  ])
  return <div onCLick={memorizedThrottle}>{children}</div>
}

// Example usage
<ThrottledTouchable onPress={this.fn}>
    <Button />
</ThrottledTouchable>
Copy the code


Public methods

Encapsulation of network request methods

The network request approach has a number of issues to consider: Semantics, protocols (agreements between the front and back ends on data formats), public parameters, data formats, encoding and decoding, asynchrony, cross-domain, cookie, network exception handling, service exception handling, request logging, Progess, Loading, abort, Timeout, security (risk control, encryption, etc.), logon state verification, isomorphism, etc.

In the Development environment, the default timeout should be considered unlimited to facilitate operations such as Charles breakpoint debugging.

Reference: useRequest- Ant intermediate standard request Hooks


Encapsulation of jump methods

Public jump methods should generally consider: general parameters, multi-terminal compatibility (jump in the small program, jump in the APP, jump in the browser environment). It is better to include the FROM field in the general parameter to identify the source of the jump for troubleshooting. For example, the h5 page can use location.origin + location.pathname. In the exception reporting method of each page, from is reported as the public parameter.

Each page should encapsulate a separate method to jump to that page. If each page is viewed as a service, the jump method is the interface that the page provides to external calls. This way, when external calls are made, you don’t need to care what the page link is, and what parameters to pass, and it’s very clear.


other

Bridge, log, buried point, life cycle, mainly consider multi-terminal compatibility.