- This project address: React-coat-helloworld
- React-coat is also supported
Browser Rendering (SPA)
andServer Rendering (SSR)
, this Demo is for demonstration onlyBrowser rendering
, please first understand:The react – coat v4.0
React-coat is developed with Typescript and integrated with Redux.
Start with Helloworld (This demo)
Advanced: SPA(single page application)
Upgrade: SPA(single page application)+SSR(Server Rendering)
First stop: Helloworld
The installation
git clone https://github.com/wooline/react-coat-helloworld.git
npm install
Copy the code
run
npm start
Run in development modenpm run build
Build files in production modenpm run prod-express-demo
Compile the build file in production mode and enable express for demonpm run gen-icon
Automatically generateiconfontFile and TS type
Viewing online Demo
- Click to view the online Demo
About Scaffolding
- Webpack 4.0 as the core, no secondary packaging, clean and transparent
- Using typescript as the development language, using Postcss and LESS to build CSS
- Do not use CSS Modules. Use modular namespaces to prevent CSS conflicts
- Editorconfig > Prettier for uniform style configuration. It is recommended to use VScode as the IDE and install the prettier plug-in for automatic formatting
- Use tsLint, ESLint, or stylelint for code checking
Modularity of CSS and images
This Demo does not use CSS modules for CSS modularization, because the readability is not good after compilation, and it increases complexity and compilation time. Using a common CSS namespace convention, we can also easily prevent CSS naming conflicts.
We divide CSS into three categories: global CSS, Module CSS, and Component CSS
Global (global) CSS
: Common CSS for use across modules and components.g-
“At the beginning, deposit to/SRC/asset/CSS/global. The CSSThe Module (Module) CSS
: a module’s private use of CSS, we agreed to start with”Module name -
“, follow the module folder to storeCSS View (View)
In modular CSS, if some CSS is used exclusively by a view, we agree to start with”Module name - View name -
“, follow the view folder to store
CSS components (Component)
: component private use of CSS, we agreed to start with”Comp - Component name -
“, followed by the components folder
Similarly, for the images used in the project, if they are used across modules and components, we put them in/SRC /asset/imgs/, and for other module private, view private, component private, we put them in their respective folders
Definition of TS type
Using Typescript means using strong typing, and we’ve divided TS type definitions in business entities into two broad categories: API types and Entity types.
- API types: Refer to the types from the background API input, which may be generated directly by Swagger or by a machine.
- Entity type: This type is defined by the system for modeling business entities. Each business Entity (Resource) has its own definition.
Ideally, the API type and Entity type would be the same because the business logic is the same, but in real development, there might be two tables because of parallel development at the front and back ends, or different perspectives at the front and back ends.
For full decoupling, we allow this inconsistency by converting the API type to the Entity type at the source, while in the system’s code logic, instead of using the API type directly, we should use the Entity type defined by ourselves to reduce the influence of other systems on the system.
Hypothetical project: Journey Web App
Main Page:
- Tour route Display
- Travel video presentation
- In-site letter display (login required)
- Comment display (visitors can view comments, and login is required for publication)
Project requirement
- Web SPA single page application
- It is mainly used in mobile browsers, but can also be adapted to desktop browsers
- No SEO requirement, but need to be able to share the current page to others
- When you enter this site for the first time, welcome displays and counts down
Routing planning
SPA single page is not a page? Why do you need to plan routes?
- First, keep the current display as much as possible for the user to refresh
- Second, so that the user can share the current display with others via the URL
- Third, in order to follow-up SEO
The path planning
According to the project requirements and UI diagram, we preliminarily planned the main route path as follows:
Itinerary list photosList
: / photosItinerary details photosItem
: / photos / : photoIdShare small video list videosList
/ videos:Share small video details with videosItem
: / videos / : videoIdmessagesList
: / messages
Parameter programming
Because list pages are paginated and searched, list routes take parameters such as:
/photos? Title = Zhangjiajie & Page =3&pageSize=20
We estimate that we will call this part of the query list condition “ListSearch”, but other routing parameters may appear in addition to ListSearch to control other conditions (not covered in this demo), such as:
/photos? Title = zhangjiajie&page =3&pageSize=20&showComment=true
Therefore, if there are many parameters, it becomes difficult to express them in a flat one-dimensional structure. Furthermore, using the URL argument to store the data, the data will all become strings. For example, if id=2, you have no way of knowing whether 2 is a number or a character, which can make subsequent processing cumbersome. So, we use JSON to serialize the second-level parameters, such as:
/photos?search={title:”张家界”,page:3,pageSize:20}&showComment=true
The downside of this is that encodeURI is required, and then the special characters get ugly.
Route Parameter Default value
In order to shorten the URL length, the framework designs a parameter default value. If a parameter is the same as the default value, it can be omitted. We need to do two things:
- When generating Url query conditions, compare the default values and omit the default values if they are the same
{title:” zhangjiajie “,page:1,pageSize:20} default value: {title:””,page:1,pageSize:20}
Original value: {title:””,page:1,pageSize:20} Default value: {title:””,page:1,pageSize:20
- When a Url query condition is received, merge the query condition with the default value
/photos? search={page:2} === photos? search={title:””,page:2,pageSize:20}
/photos === photos? search={title:””,page:1,pageSize:20}
- Handle null and undefined
Because when receiving Url parameters, if a key is undefined, we will fill it with the corresponding silent value, so we cannot define undefined as the route parameter value and use NULL instead. In other words, each parameter in the route parameter is mandatory, for example:
// When defining route parameters, each parameter must be filled in. The following is an error exampleinterface ListSearch{ title? :string, age? :number }// Change the correct definition as follows:
interface ListSearch{
title:string | null.age:number | null
}
Copy the code
- The original route parameter is SearchData. The default route parameter is SearchData and the complete route parameter is WholeSearchData. WholeSearchData = merage(the default route parameter (SearchData), the original route parameter (SearchData))
- Each entry of the original route parameter (SearchData) is optional and is represented by type TS:
Partial<WholeSearchData>
- Each entry of the complete route parameter (WholeSearchData) is mandatory and is represented as a type of TS:
Required<SearchData>
- The default route parameter (SearchData) and the type WholeSearchData are the same
- Each entry of the original route parameter (SearchData) is optional and is represented by type TS:
Do not use route status directly
Routes and their parameters are essentially stores, like Redux stores, reflecting some state of the current program. But it’s one-sided, it’s instantaneous, it’s unstable, and we see it as a kind of redundancy of the Redux Store. So it’s best not to rely on it and use it directly in your application, but rather to control its entry and exit, to digest it at its source and make it part of the entire Redux Store for the rest of the run. In this way, we decouple the application from the routing design, and the application has greater flexibility and can even be migrated to other runtime environments without the concept of URLS.
The module planning
Modules have nothing to do with Page
Partition module can be very good disassembly function, simplify complexity, and internal hidden details, external exposure of a small number of interfaces. Modules are divided by high cohesion and low coupling, not by Page or View. A module contains some complete business functions, which may involve multiple pages or views.
So looking back at our project requirements and UI diagrams, we can roughly break them down into three modules:
- Photos // Tour itinerary display
- Videos // Share videos
- Messages // Display of intra-site messages
These three modules are obvious, but we note: “Picture detail” and “videoDetail” both contain “comment display”, and “comment display” itself has pagination, sorting, detail display, create reply and other functions, it has its own independent logic, but is nested in the view of photoDetail and videoDetail. Therefore, it is appropriate to separate “comment presentation” into a module.
On the other hand, the whole program should have a startup module, which is called the “God View module”, which can do some utilities and, if necessary, can be used for coordination and scheduling between multiple modules. We call it the Applicatioin module.
So finally, the Demo is divided into 5 modules:
- App // Start the module
- Photos // Tour itinerary display
- Videos // Share videos
- Messages // Display of intra-site messages
- Comments // Comments show
Divide views for modules
Each module may contain a set of views that reflect some specific business logic. A View is the Component in React. Is the Component the View? No, there are some differences:
- A view represents Store data and is more specific to a specific business logic, so its props connect directly to the Store using mapStateToProps.
- Component represents a pure component without a business logic context, and its props are typically derived from parent passes.
- Component is usually public, whereas view is usually non-public
Going back to our project requirements and UI diagram, we roughly split the following views:
- App views: Main, TopNav, BottomNav, LoginPop, Welcome, Loading
- Photos views: Main, List, Details
- Videos Views: Main, List, Details
- Messages views: Main, List
- Comments Views: Main, List, Details, Editor
The directory structure
After the above analysis, we have a large skeleton of the project, because there are fewer modules, so we no longer use the secondary directory classification:
├─ SRC ├─ ├─ CSS │ ├─ imgs │ ├─ entity │ ├─ common │ ├─ / / store the React common components ├ ─ ─ modules │ ├ ─ ─ app │ │ ├ ─ ─ views │ │ │ ├ ─ ─ TopNav │ │ │ ├ ─ ─ BottomNav │ │ │ ├ ─ ─... │ │ │ └ ─ ─ index. The ts / / export to other modules using the view of │ │ ├ ─ ─ model. The ts / / define ModuleState and ModuleActions │ │ ├ ─ ─ the API / / will need this module API to encapsulate the background │ │ │ ├─ ├─ ├─ ├─ exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises ├ ─ ─ model. Ts │ │ ├ ─ ─ API │ │ ├ ─ ─ the facade. The ts │ │ └ ─ ─ but ts │ ├ ─ ─ videos │ ├ ─ ─ messages │ ├ ─ ─ comments │ ├ ─ ─ names. The ts / / define the module name, use enumerated types to ensure not to repeat │ └ ─ ─ index. The ts / / export module's global Settings, such as RootState types and the way of module loaded └ ─ ─ index. The TSX start the entranceCopy the code
facade.ts
The other directories are easy to understand, notice that in each module directory there is a facade.ts file, which, like index.ts, exports this module, so why not merge it into one?
- Index. ts exports the physical code of the entire module. Since modules are relatively independent, we generally want to package the entire module code into a separate chunk file.
- Facade. Ts exports only the type and logical interface of this module. We know that the TS type is completely erased after compilation, and the interface is just an empty handle. If we need to dispatch ModuleB’s action in ModuleA, we only need to import ModuleB’s facade.ts, which is just an empty handle and does not cause physical dependencies between the two module codes.
Configuration module
Q: How do I configure a module in react-Coat? Including packaging, loading, registering, managing its life cycle, etc.?
A: The index.ts file in the./ SRC /modules root directory is the total configuration file of the module. You only need to configure a module here
// ./src/modules/index.ts
// A validator that uses type TS to ensure that when a module is added, the related configuration is added at the same time
type ModulesDefined<T extends {[key in ModuleNames]: any}> = T;
// Define the module loading scheme, synchronous or asynchronous
export const moduleGetter = {
[ModuleNames.app]: (a)= > {
return import(/* webpackChunkName: "app" */ "modules/app");
},
[ModuleNames.photos]: (a)= > {
return import(/* webpackChunkName: "photos" */ "modules/photos");
},
[ModuleNames.videos]: (a)= > {
return import(/* webpackChunkName: "videos" */ "modules/videos");
},
[ModuleNames.messages]: (a)= > {
return import(/* webpackChunkName: "messages" */ "modules/messages");
},
[ModuleNames.comments]: (a)= > {
return import(/* webpackChunkName: "comments" */ "modules/comments"); }};export type ModuleGetter = ModulesDefined<typeof moduleGetter>; // Verify if any modules have forgotten to be configured
// Define Module States
interface States {
[ModuleNames.app]: AppState;
[ModuleNames.photos]: PhotosState;
[ModuleNames.videos]: VideosState;
[ModuleNames.messages]: MessagesState;
[ModuleNames.comments]: CommentsState;
}
// Define the Root State of the entire site
export type RootState = BaseState & ModulesDefined<States>; // Verify if any modules have forgotten to be configured
Copy the code
Routing and loading
/app/views/ main.tsx: /app/views/ main.tsx:
const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main");
const VideosView = loadView(moduleGetter, ModuleNames.videos, "Main");
const MessagesView = loadView(moduleGetter, ModuleNames.messages, "Main");
<Switch>
<Redirect exact={true} path="/" to="/photos" />
<Route exact={false} path="/photos" component={PhotosView} />
<Route exact={false} path="/videos" component={VideosView} />
<Route exact={false} path="/messages" component={MessagesView} />
<Route component={NotFound} />
</Switch>
Copy the code
Use loadView() to asynchronously load a View on demand. If you don’t want to load a View on demand, you can simply import it:
import {Main as PhotosView} from "modules/photos/views"
Copy the code
When a View is loaded, its associated modules are automatically loaded and the Model is initialized. A view has no “soul” without a Model, so when the view is loaded, the framework automatically loads and initializes its Model. This process consists of three steps:
- 1. Load the JS Chunk package corresponding to the module
- 2. Initialize module Model and issue module/INIT Action
- 3. The module can listen to its own module/INIT Action, initialization behavior, such as access to remote data, etc
Redux Store structure
Module division is not only reflected in the project catalog, but also in the Redux Store:
router: { // It is generated by connected-react-router
location: {
pathname: '/photos'.search: ' '.hash: '#refresh=true'.key: 'gb9ick'
},
action: 'PUSH'
},
app: {... },// app ModuleState
photos: { // photos ModuleState
isModule: true.// Automatically generated by the framework, indicating that the node is a ModuleState
listSearch: { // List search criteria
title: ' '.page: 1.pageSize: 10
},
listItems: [ // List data
{
id: '1'.title: 'Singapore + Kuala Lumpur + Malacca 6 or 7 days package tour'.departure: 'wuxi'.type: 'Package tour'.price: 2499.hot: 265.coverUrl: '/imgs/1.jpg'},... ] .listSummary: {
page: 1.pageSize: 5.totalItems: 10.totalPages: 2}},messages: {... },// messages ModuleStatecomments: {... },// comments ModuleState
}
Copy the code
The specific implementation
See Demo source code, with notes
a fly in the ointment
Deficiencies in route planning
So far, this Demo has fulfilled the requirements of the project. Next, the business looked at it and asked a few questions:
- Unable to share specified “comments”, comments are important and eye-catching content, we want to be able to specify comments when sharing links.
Currently, only five routes can be shared:
- /photos
- /photos/1
- /videos
- /videos/1
- /messages
Copy the code
Looks like we need to add:
/photos/1/comments/3 // Show a comment with id 3Copy the code
- Comment content is important for future SEO, and we want routing to control the page-turning and sorting of the comment list:
/photos/1? comments-search={page:2,sort:"createDate"}
Copy the code
- At present, our project is mainly used for mobile browser access. Many Android users are accustomed to using the back button below the phone to undo operations, such as closing popovers, etc. Can you simulate the original APP?
Conclusion: URL routing is not only used to record which Page and View to display, but also to identify some interactive operations, which completely subvert the traditional concept of routing.
Inadequate routing effectiveness
Routing looks like it’s going to get more complicated, and so far we haven’t managed routing parameters well in TS, and we haven’t done TS validation when concatenating urls. For pathnames, we write them in strings, such as:
if(pathname === "/photos") {... }const arr = pathname.match(/^\/photos\/(\d+)$/);
Copy the code
This direct hardcode seems not very good, if the product want to change the name how to do.
Write the same code repeatedly in Model
Notice that 90% of the code in photos/model.ts and videos/model.ts is the same. Why? Because both modules basically do the same thing: list display, search, get details…
In fact, it is not just photos and videos. Using the RestFul concept, the interaction process of web pages is to maintain “Resource resources”, and there is no more than the basic operations of “add, delete, change and check”. In most cases, their logic is similar. By its is in the background system, basically even the UI interface can be standardized, if this part of the “add, delete, change and check” logic extraction, the module can save a lot of repeated code.
The next Demo
Since there are so many shortcomings, we’ll look forward to addressing them step by step in the next Demo
Advanced: SPA(single page application)