Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

What if you want to practice React or TypeScript? How do you create a project? How do you build a React project from scratch? This article teaches you how to build a React + dVA + TypeScript + SCSS application from scratch. The full example project is also posted on Github.

Full project example: Github

Without further ado, let’s begin

Create a React project

Use the create-react-app recommended by the React documentation to create our app.

The Create React App is a comfortable environment to learn React and the best way to Create new single-page applications with React.

It will configure your development environment so that you can use the latest JavaScript features, provide a good development experience, and optimize your application for production. You will need to install Node >= 8.10 and NPM >= 5.6 on your machine.

Install create-react-app globally:

NPM install -g create-react-appCopy the code

Since this project is developed in TypeScript, we create our project using the following command:

npx create-react-app react-demo --typescript
cd react-demo
Copy the code

With a series of information printed, our project is preliminarily set up, let’s have a look at the project catalog:

With create-React-app, we now have a WebPack-based front-end build pipeline that integrates React, TypeScript, front-end performance checks, unit tests, and more.

Let’s add the type declaration file:

react-app-env.d.ts:

/// <reference types="node" /> /// <reference types="react" /> /// <reference types="react-dom" /> declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: "development" | "production" | "test"; readonly PUBLIC_URL: string; } } declare module "*.avif" { const src: string; export default src; } declare module "*.bmp" { const src: string; export default src; } declare module "*.gif" { const src: string; export default src; } declare module "*.jpg" { const src: string; export default src; } declare module "*.jpeg" { const src: string; export default src; } declare module "*.png" { const src: string; export default src; } declare module "*.webp" { const src: string; export default src; } declare module "*.svg" { import * as React from "react"; export const ReactComponent: React.FunctionComponent< React.SVGProps<SVGSVGElement> & { title? : string } >; const src: string; export default src; } declare module "*.module.css" { const classes: { readonly [key: string]: string }; export default classes; } declare module "*.module.scss" { const classes: { readonly [key: string]: string }; export default classes; } declare module "*.module.sass" { const classes: { readonly [key: string]: string }; export default classes; }Copy the code

We can start the project with the script NPM run start:

You can see that it has started successfully. But is it over now? That’s far from it. What more do we need? That’s route management and global state management, we’re using DVA here.

Introducing the dva

We use the command:

npm i dva -save
npm i dva-loading -save
npm i dva-core -save
npm i redux -save
npm i react-redux -save
Copy the code

Once dVA is installed, create a new folder dva under the SRC directory and create an index.tsx file.

Create a DVA instance

SRC/dva/index. TSX:

import React from "react"; import { Provider, connect } from "react-redux"; import createLoading from "dva-loading"; import { create } from "dva-core"; import { Model } from "dva"; export { connect }; export interface Options { models: Model[]; extraReducers? : any; initialState: any; onError: (e: any) => void; onAction? : any[]; } export function dva(options: options) {/** * const APP = create(options); /** * register global model */ options.models. ForEach ((model: model) => app.model(model)); */ app.use(createLoading()) must be registered before start(); */ app.start(); */ app.start(); /** * const store = app._store; app.start = (container: any) => () => <Provider store={store}>{container}</Provider>; */ app.getStore = () => store; return app; }Copy the code

In this file we encapsulate the DVA instance, and since TS cannot detect DVA-core and DVA-loading, we need to define a declaration.d.ts file.

SRC/declaration. Which s:

declare module "dva-loading";
declare module "dva-core";
Copy the code

We first delete some unnecessary files, we delete all related App components, and delete the performance test files.

We’ll create a new page directory to store our page files and a new home.tsx to verify.

SRC/page/home. TSX:

import React, { FC } from "react";

const home: FC<any> = (props: any) => {
  return <div>123</div>;
};

export default home;
Copy the code

SRC /index.tsx: SRC /index.tsx:

import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import Home from "./page/home"; import { dva } from "./dva"; /** * Create dVA app */ const app = dva({initialState: {}, models: [], any) { console.error("onError", e); }}); Const render = (Component: any) => {reactdom. render(<Component />, document.getelementbyid ("root")); }; const App = app.start(<Home />); render(App);Copy the code

Routing management

Create a router folder in the SRC directory to store our routing files.

src/router/index.tsx

import React from "react"; import { Router, Route, Switch, Redirect } from "react-router-dom"; import { createBrowserHistory } from "history"; import dynamic from ".. /dva/dynamic"; import { routes, RouteConf } from "./config"; const RouterConfig = ({ app }: any) => { return ( <Router history={createBrowserHistory()}> <Switch> {routes.map((value: RouteConf, key: number) => ( <Route exact key={key} path={value.path} component={dynamic({ app, ... value, })} /> ))} <Redirect from="/*" to="/404"></Redirect> </Switch> </Router> ); }; export default RouterConfig;Copy the code

So.. What about /dva/dynamic and./config?

The SRC /router/config.ts file is used to store our routing table:

export interface RouteConf { path: string; component: Function; name? : string; models? : Function; loadingComponent? : Function; } export const routes: RouteConf[] = [ { path: "/home", component: () => import("../page/home"), models: () => [import("../models/home")], }, ];Copy the code

SRC /dva/dynamic.tsx is used to register our component and register the Model.

import React, { Component } from "react"; import { Model } from "dva"; // Module interface Cached {[key: string]: number; } const cached: Cached = {}; function registerModel(app: any, model: Model) { if (! cached[model.namespace]) { app.model(model); cached[model.namespace] = 1; } } let defaultLoadingComponent = () => null; function asyncComponent(config: any) { const { resolve } = config; return class DynamicComponent extends Component { state: any = { AsyncComponent: null, }; loadingComponent: Function; mounted: boolean; constructor(props: any) { super(props); this.load(); this.loadingComponent = config.loadingComponent || defaultLoadingComponent; this.mounted = false; } componentDidMount() { this.mounted = true; } componentWillUnmount() { this.mounted = false; } load() { resolve().then((m: any) => { const AsyncComponent = m.default || m; if (this.mounted) { this.setState({ AsyncComponent }); } else { this.state.AsyncComponent = AsyncComponent; // eslint-disable-line } }); } render() { const { AsyncComponent }: any = this.state; const LoadingComponent: Function = this.loadingComponent; if (AsyncComponent) return <AsyncComponent {... this.props} />; return <LoadingComponent {... this.props} />; }}; } export default function dynamic(config: any) { const { app, models: resolveModels, component: resolveComponent } = config; return asyncComponent({ resolve: config.resolve || function () { const models = typeof resolveModels === "function" ? resolveModels() : []; const component = resolveComponent(); return new Promise((resolve) => { Promise.all([...models, component]).then((ret) => { if (! models || ! models.length) { return resolve(ret[0]); } else { const len = models.length; ret.slice(0, len).forEach((m) => { m = m.default || m; if (! Array.isArray(m)) { m = [m]; } m.map((model: Model) => registerModel(app, model)); }); resolve(ret[len]); }}); }); },... config, }); }Copy the code

State management

Let’s create a new models folder in the SRC directory to hold our state management files and test it by creating a new home.ts folder.

SRC/models/home. Ts:

import { Effect } from "dva"; import { Reducer } from "redux"; interface stateType { name: String; } interface options { namespace: String; state: stateType; reducers: { setState: Reducer<any, any>; clearState: Reducer<any, any>; }; effects: { initState: Effect; }; } const home: options = { namespace: "home", state: { name: "", }, reducers: { setState(state, action) { const { payload } = action; return Object.assign({}, state, payload); }, clearState(state) { return { ... state, }; }, }, effects: { *initState({ payload }: any, { put }: any) { yield put({ type: "setState", payload: { name: payload.name }, }); ,}}}; export default home;Copy the code

There is a name variable in home.ts, which is the global variable we will use later. We have bound these files in the routing configuration file, and the next step is to connect them in the page file.

SRC/page/home. TSX:

import React, { FC } from "react"; import { connect } from ".. /dva"; const Home: FC<any> = (props: any) => { const { dispatch, home } = props; return ( <div> <div>{home.name}</div> <div onClick={() => { dispatch({ type: "home/initState", payload: { name: "kryst4l" } }); }} > I </div> </div>); }; const mapStateToProps = (state: any) => ({ home: state.home }); export default connect(mapStateToProps)(Home);Copy the code

SRC /index.tsx; SRC /index.tsx; SRC /index.tsx;

src/index.tsx

import React from "react"; import ReactDOM from "react-dom"; import { dva } from "./dva"; import Router from "./router/index"; /** * Create dVA app */ const app = dva({initialState: {}, models: [], any) { console.error("onError", e); }}); Const render = (Component: any) => {reactdom. render(<Component />, document.getelementbyid ("root")); }; const App = app.start(<Router app={app} />); render(App);Copy the code

verify

At this point we are done introducing DVA, route management and state management, respectively. We have a model of home and a page home. So let’s launch the app and see if it works. Enter the command NPM run start and switch the route to the home path. (Because the root directory was originally redirected to the 404 page)

CSS preprocessor

I’m using sass here, so let’s install sass first. Use the command NPM I sass to install dependencies. After it is installed, add a styles folder in the SRC /page directory to hold our style files.

src/page/styles/home.module.scss

.title {
  color: red;
}
Copy the code

SRC /page/home.tsx: SRC /page/home.tsx

import styles from "./styles/home.module.scss"; //... <div className={styles.title}>{home.name}</div> // example: use the writing methodCopy the code

Then the project is basically set up, and the file directory is:

Modify the configuration

Since this project is create-react-app based, if you want to modify the webpack configuration of the project, you can use the following command:

npm run eject
Copy the code

It’s a one-way operation, meaning that if you use it, you can’t switch back.

When this script is used, it exposes the WebPack configuration information file in the project, which we can use to modify. You can see the official documents for details.

Of course, in addition to the official solution, we can also use other community solutions, here we can use the Craco library, see antD documentation for details.

conclusion

We can build a React + DVA + TypeScript + SCSS application from scratch, which can be easily used in daily development. If there is any confusion in the process, you can refer to the full example of this project at the link below:

Full project example: Github

If after reading this article, if it is helpful to you, please click on it and pay attention to it. I wish you a happy life.