A complex application grows from a simple application, and as more features are added to the project, the code becomes more and more difficult to control. This article focuses on how to organize components to make them maintainable in large projects.
Series directory
- 01 Type Check
- 02 Component organization
- 03 Style management
- 04 Component thinking
- 05 Status Management
directory
- 1. Basic principles of component design
- The basic principle of
- Characteristics of high-quality components
- 2. Basic skills
- 3. Component classification
- 1 ️ ⃣Container componentsandDisplay componentsThe separation of
- 2️ separation logic and view
- 3️ stateful component and stateless component
- 4️ pure and non-pure components
- 5️ one according to UI
Layout of the components
andContent components
- 6️ consistent data entry components
- 4. Divide directories
- 1️ basic directory structure
- 2️ multi-page application directory division
- 3 Catalogue division of ️ multi-page application: Monorepo model
- 4️ cross-platform application
- 5️ another way of cross-platform: taro
- Module 5.
- 1️ establishment of strict module boundary
- 2 ️ ⃣
Named export
vsdefault export
- 3️ avoid cycle dependence
- 4️ relative path should not exceed two levels
- Resolution of the 6.
- 1️ dissolving render method
- 2️ disassembly into components
- 7. Example of component partitioning
- 1️ discount page
- 2️ retail of basic UI components
- 3 the state of ️ design components
- 8. The document
- extension
1. Basic principles of component design
The basic principle of
Single Responsibility Principle. This originally comes from object-oriented programming, where the canonical definition is that “a class should only have one reason to change “, or” a class should only do one thing “in vernacular. The single responsibility principle applies to modular programming regardless of programming paradigm. In React, components are modules.
A single responsibility requires that components be limited to a ‘proper’ granularity. This granularity is a more subjective concept, in other words’ single ‘is a relative concept. In my opinion, single responsibility is not the pursuit of the ‘minimum’ of responsibility granularity, which is an extreme and may lead to a large number of modules, and module decentralization will also make the project difficult to manage. A single responsibility requires a granularity suitable for reuse.
Often we design components that combine multiple responsibilities, but then code duplication or module boundaries are broken (for example, one module depends on the ‘details’ of another) and we lazily pull the reusable code away. With more and more refactoring and iteration, module responsibilities can become more and more ‘monolithic ‘(😂 depending on who you are, it can also become noodles).
Of course, experienced developers can start by thinking about the various application scenarios for components, and can observe the overlapping boundaries of modules. For starters, the Don’t repeat Yourself principle is more useful. Don’t be lazy/think/refactor/eliminate duplicate code, and you’ll get better at it
Benefits of a single responsibility:
- Reduce component complexity. Responsibility single component code amount is small, easy to understand, high readability
- Reduce coupling to other components. When the change comes, it can reduce the impact on other functions so that it doesn’t affect them all at once
- Improve reusability. The more simple the function, the more reusable it is, such as some basic components
Characteristics of high-quality components
A high quality component must be highly cohesive and low coupled. These two principles or characteristics are one of the criteria for component independence.
High cohesion requires a component to have a clear component boundary and to gather closely related content under a component to achieve “specific” functionality. Unlike traditional front-end programming, a component is a self-contained unit that contains logic/style/structure and even dependent static resources. This also makes the component naturally a relatively independent entity. This independence is relative, of course, and to maximize this independence, components need to be broken down into smaller components based on a single responsibility, which can be composed and reused more flexibly.
Although a component is independent, it needs to be combined with other components to implement the application. This is called ‘association ‘. Low coupling requires minimizing this correlation, such as making it clear that module boundaries should not access internal details of other components, minimizing interfaces of components, one-way data flows, and so on
The rest of the paper mainly discusses the main measures to achieve high cohesion/low coupling
2. Basic skills
These tips come from React-bits:
- If the component does not require state, use stateless components
- Performance comparison: stateless functions > stateful functions > Class components
- Minimize props. Do not pass more than the required props
- If there is a lot of conditional control flow within the component, this usually means that the component needs to be extracted
- Don’t optimize too early. Just ask for components that can be reused on current demand and then ‘see what happens’
3. Component classification
1 ️ ⃣Container componentsandDisplay componentsThe separation of
Separating container components from presentation components is an important idea in React development, which affects the organization and architecture of React application projects. Here’s a summary of the differences:
Container components | Display components | |
---|---|---|
concerns | business | UI |
The data source | State manager/back end | props |
Component form | High order component | Common components |
-
A presentation component is a presentation only ‘component’ that should not be coupled ‘business/function’ or over-coupled in order to be reused in multiple places. Component libraries like ANTD provide generic components which are clearly ‘presentation components’
The following is a typical application directory structure. We can see that the presentation components may have different degrees of coupling with the business/function, and the lower the degree of coupling with the business, the higher the versatility/reusability:
Node_modules /antd/ 🔴 common component library, can not be coupled to any project business SRC/components/ 🔴 common component library, Containers/Foo/ Components / 🔴 Container/page component-specific component libraries that can be shared by multiple container/page components, and a deep business/function coupling. So that index.tsx Bar/ components/ index.tsx cannot be shared by other container componentsCopy the code
For presentation components, we consider component design as a ‘third party component library’ standard, reducing the degree of coupling with the business, considering the various application scenarios, and designing the public interface.
-
Container components focus on business processing. Container components typically exist in the form of ‘higher-order components’ that (1) get data from external data sources (state managers such as Redux or request server data directly) and (2) compose presentation components to build the complete view.
Container components combine presentation components to build a complete view, but the two are not necessarily a simple container-and-included relationship.
The separation of container components and presentation components provides benefits primarily in terms of reusability and maintainability:
- Reusability: Presentation components can be used for multiple different data sources (container components). Container components (business logic) can also be reused for presentation components on different ‘platforms’
- Better separation of presentation and container components leads to a better understanding of the application and the UI, both of which can be maintained independently
- Presentation components become lightweight (stateless/or partial state) and easier to test
Learn more about Presentational and Container Components
2️ separation logic and view
The separation of container components and presentation components is essentially a separation of logic and view. With React Hooks, container components can be replaced by Hooks, which are more naturally separated from the view layer and provide a pure source of data for the view layer.
The extracted business logic can be reused for different ‘presentation platforms ‘, such as Web and Native:
Login/ uselogin.ts // Reusable business logic index.web.tsx index.tsxCopy the code
Above, uselogin.tsx was used to maintain the business logic separately. Can be reused by web platform and native platform code.
Not only business logic, but also presentation component logic can be separated. For example, the ‘file upload’ logic for FilePicker and ImagePicker components is shared. This logic can be extracted from higher-order components, hooks, or even Context.
The main ways to separate logic and view are:
- hooks
- High order component
- Render Props
- Context
3️ stateful component and stateless component
Stateless components do not store state internally and are completely mapped by external props. Such components exist in the form of functional components as low-level/high-reuse low-level presentation components. Stateless components are ‘pure components’ by nature. If stateless components cost a little to map, use a React.Memo wrapper to avoid repeated rendering
4️ pure and non-pure components
The ‘pure’ of pure components comes from functional programming. A function that, given the same input, always returns the same output, with no side effects and no additional state dependence. In React, pure components refer to props(and, strictly speaking, state and context, which are also inputs to the component), so the output of the component does not change.
In contrast to the React component output model, Cyclejs abstracts the input/output of components more thoroughly and ‘functionally’ 👇. Its component is a normal function with only ‘one-way’ inputs and outputs:
Functional programming and component programming are in a sense the same in that they are both ‘combinatorial’ arts. A large function can be composed of a single function with multiple responsibilities. The same is true of components, where we split a large component into sub-components, give finer control over components, keep them pure, and make their responsibilities more single and independent. The benefits of this are reusability, testability and predictability.
Pure components are also important for React performance optimization. If a component is a pure component, if the ‘input’ does not change, then the component does not need to be rerendered. The larger the component tree, the higher the performance optimization benefit of pure components.
It is easy to keep an underlying component pure because it is simple. But for a complex tree of components, it takes a bit of effort to build, hence the need for ‘state management’. These state managers typically maintain one or more state libraries outside the component tree, and then inject local state into subtrees through dependency injection. The purity of the component tree is maintained by separating views from logic.
A typical solution is Redux, in which a complex component tree can be thought of as a mapping of a state tree. As long as the state tree (which relies on immutable data to ensure predictability of state) remains unchanged, the component tree remains unchanged. Redux recommends keeping the components pure and leaving the component state to Redux and the accompanying asynchronous processing tools to maintain, thus abstracting the entire application into a “one-way data flow” that is a simple “input/output” relationship
In both Cyclejs and Redux, abstraction comes at a price, as Redux code can be verbose; A complex state tree can be difficult to understand if it is not well organized. In fact, not all scenarios are smoothly/elegantly expressed as’ data-driven ‘(see this article modal. confirm violating the React mode?). , such as text box focus, or modal box. So you don’t have to go to extremes to be side-effect-free or data-driven
A follow-up article will review state management.
Extension:
- Redesigning Redux
- Cyclejs
5️ one according to UILayout of the components
andContent components
- Layout components are used to control the layout of the page and provide space for content components. The component is passed in as props for filling. Such as
Grid
.Layout
.HorizontalSplit
- A content component will contain some content, not just a layout. Content components are usually bound to placeholders by layout components. Such as
Button
.Label
.Input
For example, List/ list. Item is the layout component and Input, Address is the content component
Separating layout from content components makes both easier to maintain. For example, layout changes do not affect content, and content components can be applied in different layouts. A component, on the other hand, is a self-contained, cohesive, isolated unit that should not affect its external state, for example, a button should not modify the external layout, and should not affect the global style
6️ consistent data entry components
Data entry components, or forms, are an essential element of client development. For custom form components, I think there should be a consistent API:
interfaceProps<T> { value? : T; onChange:(value? : T) = > void;
}
Copy the code
The benefits of this:
-
Close to native form element primitives. Custom form components generally do not need to be wrapped in event objects
-
Almost all component library custom forms use this API. This makes our custom components compatible with third-party libraries, such as ANTD’s form validation mechanism
-
Easier to render dynamically. Because the interfaces are consistent, dynamic rendering or centralized processing can be easily done, reducing code duplication
-
Status echo is one of the functions of a form component, and my personal best practice is that values should be self-contained:
For example, for a user selector that supports search, options are loaded asynchronously from the back end. If value only stores the user ID, then the user name cannot be displayed in the output. According to my practice, the structure of value should be: {id: string, name: String}, which solves the echo problem. The data required by the output is passed in by the parent node rather than maintained by the component itself
-
Components are controlled. In actual React development, there are very few scenarios for uncontrolled components, and I think custom components can ignore this requirement and only provide fully controlled form components to avoid components maintaining cache state themselves
4. Divide directories
1️ basic directory structure
There are two popular models for dividing project directory structures:
- Rails-style/by-type: Divides files into different directories according to their types, for example
components
,constants
,typings
,views
- Domain-style/by-feature: creates a separate folder based on a feature or service, containing multiple types of files or directories
A typical React project structure is as follows:
SRC/components/ # 🔴 project common 'display components' Button/ index.tsx # SVG # Static resources style.css # Component Styles... Index.ts # containers everywhere all components/ # 🔴 contains' container components' and 'page components' LoginPage/ # page components, e.g. Login components/ # page level display components, these components cannot be reused with other page components. The button.tsx # component is not necessarily a directory, but can be a single file for a simple component. TSX reducer. Ts # redux reduces useLogin. Ts # (optional) Place 'logic ', according to the principle of 👆 separation of logic and view, Ts # typescript type declaration style.css logo.png message.ts constants. Ts index.tsx HomePage/... Index.tsx # 🔴 Use root component hooks/ # 🔴 use elist.ts usePromise. Stores. Ts # redux Stores contants. Ts # global constantsCopy the code
The LoginPage and HomePage directories are grouped together using domain-style to aggregate all the files related to this service or page. The Rails-style pattern is also used here to separate directories based on file type/responsibility, such as Components, hooks, containers; You’ll find a rails-style structure inside the LoginPage, such as Components, but with a different scope, belonging only to the LoginPage and not shared by other pages
Front-end projects typically break down components by page route. These components, let’s call them “page components,” are coupled to business functions and have some independence from each page.
Page components are placed on containers here. As the name indicates, this directory is originally used to place container components. In actual projects, ‘container components’ and’ page components’ are usually mixed together. This directory can also be named views, Pages… (whatever), named containers is just a convention (derived from Redux).
Extension:
- react-boilerplate
2️ multi-page application directory division
For large applications, there may be multiple entry points. For example, many electron applications have multiple Windows. For example, many applications have a background management interface in addition to the App. I usually organize multi-page applications like this:
The containers are containerized on containers. The containers are containerized on containers. The containers are containerized on containers. TSX stores. Ts # redux stores AnotherApp/ #... Anotherapp.tsx # admin. TSX # adminCopy the code
Webpack supports building multi-page applications. I usually name the application entry file *.page. TSX and then automatically scan the matching file in SRC as the entry.
Webpack’s SplitChunksPlugin can automatically extract shared modules for multi-page applications, which makes sense for multi-page applications with similar functionality and more shared code. This means that resources are optimized together and shared modules are extracted, which helps reduce the size of compiled files and also facilitates shared browser caches.
Html-webpack-plugin 4.0 starts to support injection of shared chunks. SplitChunksPlugin was used to explicitly define the shared chunk, and the HTML-webpack-plugin was also used to explicitly inject the chunk.
3 Catalogue division of ️ multi-page application: Monorepo model
In this way, all pages are clustered under a single project, sharing the same dependencies and NPM modules. This may raise some questions:
- You cannot allow different versions of dependencies for different pages
- For unrelated applications, such as apps and backends, the technology stacks/component libraries/interaction experiences they use can be quite different, and naming conflicts can occur.
- Build performance: You want to build and maintain a single page, not all the pages mixed together
You can use lerna or Yarn Workspace to isolate multi-page applications in different NPM modules. Yarn Workspace is used as an example:
Package. json yarn.lock node_modules/ # All dependencies will be installed here, Share / # 🔴 hooks/ utils/ admin/ # 🔴 components/ containers/ index.tsx package.json App / # 🔴 components/ containers/ index.tsx package.jsonCopy the code
Extension:
- Close reading of The Advantages of Monorepo
4️ cross-platform application
Use ReactNative to extend React into the realm of native application development. Although there are solutions such as React-Native Web, web and Native apis/functions/development methods, and even product requirements may differ greatly, resulting in a large amount of uncontrollable adaptation code over time. In addition, the React-native Web itself may become a risk point. So some teams need to develop for different platforms, and generally organize cross-platform applications in the following style:
SRC/components/ Button/ index.tsx # 🔴 ReactNative component index.web. TSX # 🔴 SVG # static resources style.css # Component styles... index.ts index.web.ts containers/ LoginPage/ components/ .... TSX index.tsx HomePage/... index.tsx hooks/ useList.ts usePromise.ts ... TSX # React NativeCopy the code
The priority of extension completion can be configured through webpack’s resolve.extensions. Early ANTD-Mobile was organized in this way.
5️ another way of cross-platform: taro
For domestic developers, cross-platform is not only so simple as Native, we also have a variety of small programs, small applications. The fragmentation of terminals makes front-end development more and more challenging.
Taro is based on the React standard Syntax (DSL) and combines the idea of compilation principle to convert a set of code into multiple terminal object code, and provides a unified set of built-in component library and SDK to smooth out the differences between multiple terminals
Because Taro uses the React standard syntax and API, it allows us to follow the original React development conventions and conventions to develop multipurpose applications with only one set of code. But don’t forget that abstraction comes at a price
Check out the official Taro documentation to learn more
Flutter is a recent comparison or cross-platform solution, but is irrelevant to the topic of this article
Module 5.
1️ establishment of strict module boundary
Below is a page of module import, quite chaotic, which is acceptable, I have seen thousands of lines of components, of which module import statements accounted for more than 100 lines. This may be partly due to VsCode’s automatic import feature (you can use tslint rules to sort and group import statements), but more to do with the lack of organization of these modules.
I think we should create strict module boundaries where there is only one unified ‘exit’ for each module. For example, a complex component:
ComplexPage/ Components/foo.tsx bar.tsx constants. Ts reducers. Ts style.css typesCopy the code
Think of a ‘directory’ as a module boundary. You should not import modules like this:
import ComplexPage from '.. /ComplexPage';
import Foo from '.. /ComplexPage/components/Foo';
import Foo from '.. /ComplexPage/components/Bar';
import { XXX } from '.. /ComplexPage/components/constants';
import { User, ComplexPageProps } from '.. /ComplexPage/components/type';
Copy the code
A module/directory should have an ‘exit’ file to centrally manage module exports and limit module visibility. The modules above, components/Foo, Components /Bar, and constants. Ts are the ‘implementation details’ of the ComplexPage component. These are implementation details that external modules should not decouple, but there is no binding mechanism for this at the language level, relying only on canonical conventions.
When other modules rely on ‘details’ of a module, it may be a sign of refactoring: a utility function or an object type declaration that depends on a module, for example, should probably be promoted to the parent module and shared by sibling modules.
The index file is best used as an ‘exit’ file in a front-end project. When importing a directory, the module finder looks for an index file in that directory. When designing a module’s API, developers need to consider the various ways in which the module can be used and use index files to control module visibility:
// Import the type to be used by the external module
export * from './type';
export * from './constants';
export * from './reducers';
// Do not expose external implementation details that do not need to be cared for
// export * from './components/Foo'
// export * from './components/Bar'
// The module's default export
export { ComplexPage as default } from './ComplexPage';
Copy the code
Import statements can now be more concise:
import ComplexPage, { ComplexPageProps, User, XXX } from '.. /ComplexPage';
Copy the code
This rule can also be applied to component libraries. Before the tree-shaking feature of Webpack was mature, we all used a variety of tricks to implement on-demand imports. For example, babel-plugin-import or direct subpath import:
import TextField from '~/components/TextField';
import SelectField from '~/components/SelectField';
import RaisedButton from '~/components/RaisedButton';
Copy the code
Now you can use Named import to import directly and let WebPack do the optimization for you:
import { TextField, SelectField, RaisedButton } from '~/components';
Copy the code
But not all directories have exit files, so the directory is not the boundary of the module. Utils is just a module namespace. The files under utils are unrelated or of different types:
utils/
common.ts
dom.ts
sdk.ts
Copy the code
We prefer to refer to these files directly, rather than through an entry file, to make it more clear what type is being imported:
import { hide } from './utils/dom'; // As you can see from the file name, this is probably hiding a DOM element
import { hide } from './utils/sdk'; // Some method provided by the WebView SDK
Copy the code
A final summary:
According to the module boundary rule (as shown above), a module can access siblings (in the same scope), ancestors, and ancestors’ sibling modules. Such as:
- Bar can access Foo, but it can’t access its details further down, that is, not
../Foo/types.ts
, but can access its export files../Foo
- SRC /types.ts cannot access containers/HomePage
- LoginPage and access a HomePage
- LoginPage can access utils/ SDK
2 ️ ⃣Named export
vs default export
Both of these export methods have their own application scenarios, and one should not be ruled out. First, let’s look at the advantages of named export:
-
After confirm
-
Convenient Typescript refactoring
-
It facilitates intelligent notification and auto-import identification
-
Convenient reexport
// named export * from './named-export'; // default export { default as Foo } from './default-export'; Copy the code
-
-
A module supports multiple named exports
What are the advantages of default export? :
-
Default export generally stands for ‘module itself’, and when we import a module using ‘default import’, the developer automatically knows what object the default import is.
For example, react exports a React object; LoginPage exports a LoginPage. Somg.png imports an image. Such modules always have a defined ‘body object ‘. So default import names and module names are generally the same (Typescript auto-import is based on file names).
Of course ‘subject object’ is an implicit concept that you can only constrain by specification
-
The import statement of default export is more concise. Such as lazy (import (‘/MyPage ‘))
Default export also has some disadvantages:
- Interoperability with other module mechanisms (CommonJS) is difficult to understand. For example, we’ll import like this
default export
:require('./xx').default
named import
Advantage isdefault export
The disadvantages of the
So to sum up:
- Modules that are explicit about ‘body objects’ need to have default exports, such as page components, classes
- Default exports should not be used for modules where the ‘body object’ is not clear, such as component libraries, utils(placing various tool methods), contants constants
Following this rule, you can organize the components directory like this:
Components/Foo/ foo.tsx types.ts Constants. Ts index. Ts # Export Foo component Bar/ bar.tsx indexCopy the code
For module Foo, there is a subject object called component Foo, so here we use default export to export component Foo with the code:
// index.tsx
// All three files are exported using named export
export * from './contants';
export * from './types';
export * from './Foo';
// Import the body object
export { Foo as default } from './Foo';
Copy the code
Now assume that the Bar component depends on Foo:
// components/Bar/Bar.tsx import React from 'react'; // Import the Foo component. According to the module boundary rules, no direct reference to.. /Foo/Foo.tsx import Foo from '.. /Foo'; export const Bar = () => { return ( <div> <Foo /> </div> ); }; export default Bar;Copy the code
For the Components module, all its submodules are equal, so there is no body object and default export does not apply here. The components/index. Ts code:
// components/index.ts
export * from './Foo';
export * from './Bar';
Copy the code
3️ avoid cycle dependence
Circular dependencies are a symptom of bad module design, and you need to consider splitting and designing module files, for example
// --- Foo.tsx ---
import Bar from './Bar';
export interface SomeType {}
export const Foo = (a)= > {};
Foo.Bar = Bar;
// --- Bar.tsx ----
import { SomeType } from './Foo'; .Copy the code
The Foo and Bar components above form a simple loop dependency, although it doesn’t cause any runtime problems. The solution is to extract SomeType into a separate file:
// --- types.ts ---
export interface SomeType {}
// --- Foo.tsx ---
import Bar from './Bar';
import {SomeType} from './types'
export const Foo = (a)= >{}; . Foo.Bar = Bar;// --- Bar.tsx ----
import {SomeType} from './types'.Copy the code
4️ relative path should not exceed two levels
As the project gets more complex and the directory may get deeper, the import path will look like this:
import { hide } from '.. /.. /.. /utils/dom';
Copy the code
First, the import statements are very inelegant and unreadable. When you don’t know the directory context of the current file, you don’t know where the specific module is; Even if you know the location of the current file, you need to follow the import path up the directory tree to locate the specific module. So this relative path is kind of anti-human.
In addition, such import paths do not facilitate module migration (although Vscode supports refactoring import paths when moving files), and file migration requires overwriting these relative import paths.
Therefore, it is generally recommended that the relative path import should not exceed two levels, that is, only.. / and./. You can try to convert relative paths to absolute paths. For example, resolve. Alias can be configured in webpack to do this:
. resolve: { ... alias: {// You can use ~ directly to access modules relative to the SRC directory
/ / such as ~ / components/Button
'~': context,
},
}
Copy the code
Now we can import modules relative to SRC like this:
import { hide } from '~/utils/dom';
Copy the code
extension
- You can configure the Paths option for Typescript;
- For Babel, you can use the babel-plugin-module-resolver plug-in to convert to a relative path
Resolution of the 6.
1️ dissolving render method
When the RENDER method’s JSX structure is very complex, the first thing you should try to do is split the JSX into multiple sub-Render methods:
Of course, this approach makes the Render method look less complicated for the time being, it doesn’t split the component itself, and all the input and state is still clustered under one component. So usually splitting the Render method is just the first step in refactoring: as components get more complex and files get longer, I generally use 300 lines as a threshold, with more than 300 lines indicating that the component needs to be split further
2️ disassembly into components
If you have split the render of a component into subrender methods as described above at 👆, you can easily split those subrender methods into components when a component becomes bloated. In general, components are removed in the following ways:
- Pure render split: Subrender methods are generally pure render, and they can be easily separated intoStateless component
public render() { const { visible } = this.state return ( <Modal visible={visible} title={this.getLocale('title')} width={this.width} maskClosable={false} onOk={this.handleOk} onCancel={this.handleCancel} footer={<Footer {... }></Footer>} > <Body {... }></Body> </Modal> ) }Copy the code
- Pure logical split: according to
Logic and view separation
To isolate logical control parts into hooks or higher-order components - Logic and rendering split: The separation of related views and logic into a separate component is a more complete split, following the principle of a single responsibility.
7. Example of component partitioning
We usually analyze and divide components from UI prototypes. Thinking in React also describes how to divide components by UI: “This is because the UI and the data model tend to follow the same information architecture, which means that it is often easy to divide the UI into components. Just partition it into components that accurately represent part of your data model. “Component partitioning relies on your development experience, in addition to following some of the principles mentioned above at 👆.
This section describes the process of partitioning components through a simple application. This is the service declaration system of a government department, consisting of four pages:
1️ discount page
Pages are usually the topmost component unit, and are easily divided into four pages based on the prototype diagram: ListPage, CreatePage, PreviewPage, and DetailPage
SRC/containers/ ListPage/ CreatePage/ PreviewPage/ DetailPage/ index.tsx # root component, where routes are generally definedCopy the code
2️ retail of basic UI components
First, take ListPage
The ListPage can be divided into the following components based on the UI:
List container, layout component, Item, layout component, provides header, Props - Header Title # Props - after Time # props - body Status # The state of the render itemCopy the code
Look again at CreatePage
This is a form filling page, divided into several steps to improve the form filling experience; There are multiple groups of forms in each step; The structure of each form is similar, with the label display on the left and the actual form component on the right, so components can be divided according to the UI like this:
On the other hand, on the other hand, have the function of setting up and switching between Steps. On the other hand, on the other hand, have the function of setting up and switching between Steps. Label Input # Address NumberInput Select FileUpload is supportedCopy the code
Component naming suggestions: For collective components, use singular and plural names, such as Steps/Step above. List/Item is also a common Form, such as Form/ form. Item, which is suitable as a child component. Learn how third-party component libraries name components.
Take a look at the PreviewPage, which is the created data PreviewPage, and has a similar data structure and page structure to CreatePage. Map Steps to the Preview component and Step to preview.item. Input to input.preview:
3 the state of ️ design components
Status is relatively simple for ListPage, and the state of CreatePage is mainly discussed here. CreatePage features:
- The form component uses controlled mode and does not store the state of the form itself. In addition, states between forms may be linked
- State needs to be shared between the CreatePage and PreviewPage
- Forms need to be validated uniformly
- Save the draft
Because you need to share data between the CreatePage and PreviewPage, the state of the form should be extracted and promoted to the parent level. In the actual development of this project, I would create a FormStore Context component that the subordinate components use to store data uniformly. I also decided to use configuration to render the forms dynamically. The general structure is as follows:
// CreatePage/index.tsx <FormStore defaultValue={draft} onChange={saveDraft}> <Switch> <Route path="/create/preview" component={Preview} /> <Route path="/create" component={Create} /> </Switch> </FormStore> // CreatePage/Create.tsx <Steps> {steps.map(I => <Step key={i.name}> <FormRenderer Forms ={i.form} /> {/* Forms for the form configuration, according to the configured form type render form components, */} </Step>)} </Steps>Copy the code
8. The document
The recommended way to document components is to use Storybook, a component Playground that has the following features
- Examples of interactive components
- Documents that can be used to present components. Supports props generation and Markdown
- Can be used for component testing. Support component structure testing, interaction testing, visual testing, accessibility testing or manual testing
- Rich plugin ecosystem
The React example. For reasons of space, Storybook does not expand into details, and interested readers can refer to the official documentation.
extension
- Three Rules For Structuring (Redux) Applications
- How To Scale React Applications
- Redux FAQ: Code structure
- export default considered harmful
- Cyclic loading of JavaScript modules
- thinking-in-react
- Building Multitenant UI with React.js