preface

  • Use react 16.8.x and typescript 3.5.3 in projects
  • And then also usekoa2+typescriptSet up asimpleAPI backend services, just to verify the use of Axios encapsulation API, and personal Node.js play needs 🙃, does not involve database operations etc… Code can be stampedhere
  • I upgraded webpack4
  • Then, it’s just an empty template to validate some things, just a few simple demo pages, the rest of the pages have been removed…
  • The source of this project can be seen here
  • [2019-09-05]: electron
  • [查 看 å…¨ æ–‡] [2019-09-09] Third Party resources using CDN
  • Update: [2019-11-08]: Status management byredux+rematchChange formobx, resource preloading prefetch, etc

1. Create a project

Instead of using antD as an official demo, antD is added to normal React +typescript projects and then modified

Why not use antD official demo? Because I tried to use it, but webpack alias Settings can not get, always have a problem, I don’t use that…

create-react-app project --typescript
Copy the code

The SRC structure:

API ├. ├ ─ ─ ─ ─ assets ├ ─ ─ components ├ ─ ─ lang ├ ─ ─ routes ├ ─ ─ store ├ ─ ─ utils ├ ─ ─ views ├ ─ ─ App. The SCSS ├ ─ ─ App. Test. The TSX ├ ─ ─ ├─ app.tsx ├─ index.scSS ├─ index.tsX ├─ router.tsx ├─ setupproxy.jsCopy the code

2, typescript

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "src"."outDir": "build/dist"."module": "esnext"."target": "es5"."lib": ["es6"."dom"]."sourceMap": true."allowJs": true."jsx": "preserve"."moduleResolution": "node"."rootDir": "."."forceConsistentCasingInFileNames": true."noImplicitReturns": true."noImplicitThis": true."noImplicitAny": true."importHelpers": true."strictNullChecks": true."suppressImplicitAnyIndexErrors": true."noUnusedLocals": true."allowSyntheticDefaultImports": true."experimentalDecorators": true."paths": {
      "@ / *": [". / *"]}},"awesomeTypescriptLoaderOptions": {
    "useBabel": true."useCache": false."emitRequireType": false
  },
  "includes": [
    "src"]."exclude": [
    "node_modules"."build"."scripts"."acceptance-tests"."webpack"."jest"."src/setupTests.ts"."public/"]}Copy the code

.babelrc

{
  "presets": [
    "react-app"]."plugins": [
    "transform-decorators-legacy"["import",
      {
        "libraryName": "antd-mobile"."style": "css"}}]]Copy the code

3. Upgrade WebPack4.x

Add mode to webpack.config.dev.js: mode: ‘development’ webpack.config.prod.js: mode: ‘production’

Relevant modules to be upgraded:

Yarn upgrade ** Upgrade or directly yarn add ** -d is also available

  • file-loader
  • fork-ts-checker-webpack-plugin
  • html-webpack-plugin@next
  • react-dev-utils
  • url-loader
  • webpack
  • webpack-cli
  • webpack-dev-server
  • webpack-manifest-plugin

Part of the QA

  1. Webpack is not a function

    Script /start.js: const compiler = createCompiler(webpack, config, appName, urls, useYarn); Instead: const Compiler = createCompiler({webpack, config, appName, urls, useYarn});

  2. Compile error: this. HtmlWebpackPlugin. GetHooks is not a function

    Note that HTML – webpack – plugin @ next this plug-in to add @ next to config/webpack.com FIG. Dev. Js, config/webpack config. Prod. Js: New InterpolateHtmlPlugin(env.raw) 对 : new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw)

  3. An error message is displayed indicating Chunk Loading failed

    config/paths.js:

    function getServedPath(appPackageJson) {
      const publicUrl = getPublicUrl(appPackageJson);
      const servedUrl = envPublicUrl ||
        (publicUrl ? url.parse(publicUrl).pathname : '/');
      return ensureSlash(servedUrl, true);
    }
    Copy the code

    Change the ‘/’ to ‘./’

  4. Description @types/tapable @types/ HTML -minifier @types/webpack does not exist

    yarn add @types/tapable @types/html-minifier @types/webpack
    Copy the code

4, antd

yarn add antd
Copy the code

According to the need to load

  • ts/tsxuseawesome-typescript-loaderThis loader resolves
  • antdThe CSS of the component is loaded and used on demandbabel-plugin-importThis plugin
yarn add awesome-typescript-loader babel-plugin-import
Copy the code
// webpack.config.dev.js, webpack.config.prod.js
{
    test: /\.(ts|tsx)$/.include: paths.appSrc,
    loader: 'awesome-typescript-loader'.exclude: /node_modules/.options: {
      babelOptions: {
        "presets": ["react"]."plugins": [["import", 
            { 
              "libraryName": "antd"."style": "css"}]]}}},Copy the code

5. Routing/permission control

If @types/ XXX fails, install as instructed. If not, manually add a Declare Module ‘@loadable/ Component ‘to common.d.ts;

yarn add @loadable/component
Copy the code

routing

  • Route under App

This is similar to the way routing is nested inside the App in Vue, where props. Children in App is equivalent to router-view in Vue, and Header and other global components are mounted only once

// src/router.tsx. <AuthRoute path='/' 
  render={() => (
    <App>
      <Switch>
        {routes.map(route => route)}
      </Switch>
    </App>
  )}
/>
...
Copy the code
  • Routes that are independent of the App

aloneComp

// src/router.tsx
<Switch>
  {
    aloneComp.map(route= > route)
  }
  <AuthRoute 
    path='/' 
    render={() => (
      <App>
        <Switch>
          {routes.map(route => route)}
        </Switch>
      </App>)} / ></Switch>
Copy the code
// src/App.tsx. public render() {return (
      <div className={style.app}>
        <Header />
        { this.props.children }
      </div>
    );
  }
Copy the code

Routing management

  • Unified Route Management
// src/routes/index.tsx
import login from './login-register';
import home from './home';

/** * use this component '@/routes/auth-route' instead of the official route */
export default [
  ...login,
  ...home
]
Copy the code
  • Routing module
// src/routes/home.tsx
import AuthRoute from '@/routes/auth-route';
import * as React from 'react';
import Loadable from '@loadable/component';

// home
export default [
  <AuthRoute 
    key="home" 
    exact={true} 
    path="/" 
    component={Loadable(() => import('@/views/home'))} 
  />,
  <AuthRoute 
    key="home" 
    exact={true} 
    path="/home" 
    component={Loadable(() => import('@/views/home'))} 
  />
]
Copy the code
  • Router.tsx

It is divided into routes under App and routes outside App independently. Depending on the situation, if all pages have the same App shell, there is no need for this separation

// src/router.tsx
import * as React from 'react';
import { HashRouter, Switch } from 'react-router-dom';
import AuthRoute from '@/routes/auth-route';
import Loadable from '@loadable/component';
import PageRoutes from './routes';
import login from '@/routes/login-register';

// use import {lazy} from '@loadable/component';
// Lazy () has the same warning as react.lazy ()
const App = Loadable((a)= > import('./App'));
const ErrComp = Loadable((a)= > import(/* webpackPrefetch: true */ './views/err-comp'));

const AppComp = (a)= > {
  // Route independent of app
  const aloneComp = [
    ...login
  ];
  constErrRoute = <AuthRoute key='err404' exact={true} path='/err404' component={ErrComp} />; const NoMatchRoute = <AuthRoute key='no-match' component={ErrComp} />; const routes = [...PageRoutes, ErrRoute, NoMatchRoute]; return ( <Switch> { aloneComp.map(route => route) } <AuthRoute path='/' render={() => ( <App> <Switch> {routes.map(route  => route)} </Switch> </App> )} /> </Switch> ); } export default function Router() { return ( <HashRouter> <AppComp /> </HashRouter> ); }Copy the code
  • Project entry SRC /index.tsx
// src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import store from '@/store';
import AxiosConfig from './api';
import Router from './router';
import './index.scss';
// import registerServiceWorker from './registerServiceWorker'; 

const Loading = (a)= > (<div>loading...</div>);

AxiosConfig(); // Initialize axios

ReactDOM.render(
  <React.Suspense fallback={<Loading />} ><Provider {. store} >
      <Router />
    </Provider>
  </React.Suspense>,
  document.getElementById('root') as HTMLElement
);

// registerServiceWorker();
Copy the code

Login Permission Control

Using the JS-cookie package, the token(sessionId?) returned by the back-end interface will be logged in. Exists in the ‘auth’ field in cookie

// src/routes/auth-route.tsx:
import * as React from 'react';
import { ComponentProps } from 'react';
import { Route, Redirect, RouteProps } from 'react-router';
import * as Cookies from 'js-cookie';

exportinterface AuthRouteProps extends RouteProps { key? : string|number, path? : string, auth? : boolean,// Whether permissions are requiredredirectPath? : string,// Redirected routerender? : any, component? : ComponentProps<any> }const initialProps = {
  key: 1.path: '/login'.auth: true.component: (a)= > <div />
};

/** * Permission control handles routing */
const AuthRoute = (props: AuthRouteProps = initialProps) = > {
  const { auth, path, component, render, key, redirectPath } = props;
  if(auth && ! Cookies.get('auth')) {
    // console.log('path: ', path);
    return( <Route key={key} path={path} render={() => <Redirect to={{ pathname: redirectPath || '/login', search: '? fromUrl='+path }} /> } /> ) } return ( <Route key={key} path={path} component={component} render={render} /> ) } export default AuthRoute;Copy the code

6. API management

axios

yarn add axios
Copy the code

Axios configuration, request/response interception

// src/api/index.ts
import axios, { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios';
import { message } from 'antd';
import * as Cookies from 'js-cookie';
import * as NProgress from 'nprogress';

axios.defaults.timeout = 10000;
axios.defaults.baseURL = process.env.NODE_ENV === 'production'
  ? 'http://192.168.0.5:2333' // Set the production environment address of the actual project
  : ' ';

let startFlag = false; // The loadingStart flag

/ / the interceptor
export default function AxiosConfig() {
  // Request interception
  axios.interceptors.request.use((config: AxiosRequestConfig) = > {
    if (config.data && config.data.showLoading) {
      // Display the loading request
      startFlag = true;
      NProgress.start();
    }
    // Request access_token, with each request after login
    if (Cookies.get('auth')) {
      config.headers.Authorization = Cookies.get('auth');
    }
    if (config.params) config.params._t = Date.now();

    return config;

  }, (err: AxiosError) => {
    if (startFlag) {
      startFlag = false;
      NProgress.done();
    }
    return Promise.reject(err);
  });

  // Response interception
  axios.interceptors.response.use((res: AxiosResponse) = > {
    if (startFlag) {
      startFlag = false;
      NProgress.done();
    }
    return res.data;
    
  }, (err: AxiosError) => {
    // Server error
    if (err.response && (err.response.status+' ').startsWith('5')) {
      message.error('Request error! ')}if (startFlag) {
      startFlag = false;
      NProgress.done();
    }
    return Promise.reject(err); })}Copy the code

The API module

// src/api/test-api.ts
import axios from 'axios';

// Get the file
const api = {
  / / sample:
  // get only params will be used as a request parameter
  // Other request methods such as POST,PUT,PATCH, and data are used as request parameters
  testApi: (params: any = {}) = > {
    // post
    // return axios.post('/api/file/uploadFile', params);

    // get
    return axios.get('/api/file/getFile', { 
      params, 
      data: { showLoading: true}}); }};export default api;
Copy the code

Use the API

import Api from '@/api/test-api'; . Api.testApi(params).then((res: any) = >{... });Copy the code

-7. Use rematch for state management (deprecated rematch, changed to Mobx)

Redux v7.1.0 includes react-redux, which is supported by Hooks that use useSelector and useDispatch. React-redux is supported by Hooks that use useSelector and useDispatch

yarn add @rematch/core react-redux
Copy the code

The store management

// src/store-rematch/index.ts
import { init, RematchRootState } from '@rematch/core';
import * as models from './models/index';

// Cache list
const cacheList = ['common'];
const stateCache = sessionStorage.getItem('store-rematch');
// Initialize state
const initialState = (stateCache && JSON.parse(stateCache)) || {};

const store = init({
  models,
  redux: {
    initialState
  }
});

// Listen for each state change
store.subscribe((a)= > {
  const state = store.getState();
  let stateData = {};
  
  Object.keys(state).forEach(item= > {
    if(cacheList.includes(item)) { stateData[item] = state[item]; }}); sessionStorage.setItem('store-rematch'.JSON.stringify(stateData));
});

export type Store = typeof store;
export type Dispatch = typeof store.dispatch;
export type iRootState = RematchRootState<typeof models>;
export default store;
Copy the code

models

// src/store-rematch/models/indes.ts
import { createModel } from '@rematch/core';
// import detail from './detial';

export interface ICommonState {
  appName: string,
  isMobile: boolean,
  count: number,
  countAsync: number
}
const initialState: ICommonState = {
  appName: 'react-ts-mdnote'.isMobile: false.count: 0.countAsync: 0
};
const common = createModel({
  state: initialState,
  reducers: {
    setIsMobile(state: ICommonState, payload: boolean) {
      return {
        ...state,
        isMobile: payload
      }
    },
    addCount(state: ICommonState) {
      return {
        ...state,
        count: state.count + 1
      }
    },
    setCount(state: ICommonState, payload: number) {
      return {
        ...state,
        countAsync: payload
      }
    }
  },
  effects: (dispatch) = > ({
    async setCountAsync(payload, rootState) {
      await new Promise(resolve= > setTimeout(resolve, 1000))
      dispatch.common.setCount(payload)
    }
  })
});

export {
  common,
  // detail
}
Copy the code

Use in components

  • ordinaryconnect + mapState + mapDispatchwriting
// src/views/home/index.tsx
import * as React from 'react';
import { connect } from 'react-redux';
import { iRootState, Dispatch } from '@/store-rematch';
import { Button } from 'antd';
import styles from './home.scss';

interface IProps {
  [prop: string]: any
}

function Home(props: IProps) {
  return (
    <div className={styles.home}>
      <div className={styles.content}>
        <p>react-ts-antd-template</p>
        <p className={styles.count}>
          count: { props.count } &emsp;
          <Button onClick={props.addCount}>count++</Button>
        </p>
        <p className={styles.count}>
          countAsync: { props.countAsync } &emsp;
          <Button onClick={props.setCountAsync}>countAsync</Button>
        </p>
      </div>
    </div>)}const mapState = (state: iRootState) = > {
  return {
    count: state.common.count,
    countAsync: state.common.countAsync
  }
}
const mapDispatch = (dispatch: Dispatch) = > {
  return {
    addCount: (a)= > dispatch({ type: 'common/addCount' }),
    setCountAsync: (a)= > dispatch({ type: 'common/setCountAsync'.payload: new Date().getSeconds() }),
  }
}

export default connect(mapState, mapDispatch)(Home);
Copy the code
  • react-reduxThe new Hooks:useSelector, useDispatchwriting
import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { iRootState, Dispatch } from '@/store-rematch';
import { Button } from 'antd';
import styles from './home.scss';

interface IProps {
  [prop: string]: any
}

function Home(props: IProps) {
  const dispatch: Dispatch = useDispatch();
  const { count, countAsync } = useSelector((state: iRootState) = > state.common);
  
  return (
    <div className={styles.home}>
      <div className={styles.content}>
        <p>react-ts-antd-template</p>
        <p className={styles.count}>
          count: { count } &emsp;
          <Button onClick={()= > dispatch({ type: 'common/addCount' })}>count++</Button>
        </p>
        <p className={styles.count}>
          countAsync: { countAsync } &emsp;
          <Button 
            onClick={()= > dispatch({ type: 'common/setCountAsync', payload: new Date().getSeconds() })}
          >countAsync</Button>
        </p>
      </div>
    </div>)}export default Home;
Copy the code

Mobx for state management

yarn add mobx mobx-react
Copy the code

Compared to Redux, Mobx has fewer concepts, is simple to write and use; Class components use decorators and function components use functions of the same name

  • Observable: Declares data state
  • @ Computed: Calculates attributes, and you can pull data from objects or arrays
  • @action: action function, you can write asynchronous function directly
  • 16, runInAction: Note no@, not decorator; in@actionDecorated function interior modificationstate, such as the followingsetTimeoutInternal modification data
  • Flow: Returns a generator function withfunction */yieldInstead ofasync/await(These two are actually their grammar candy), do not need to use@action/runInAction
  • @ inject (‘ homeStore) : will behomeStoreInjection into components
  • Observer: Functions/decorators can be used to convert React components into responsive components. It wraps the component’s render function in mobx.autorun to ensure that the component can be forced to refresh whenever the data used in any component rendering changes. The Observer is provided by a separate Mox-React package.

Other configurations:

  • Download the plugin
    yarn add babel-plugin-transform-decorators-legacy -D
    Copy the code
  • Then use the decorator in.babelrc:
    "plugins": ["transform-decorators-legacy"]
    Copy the code
  • Tsconfig. json: Use the decorator
    "compilerOptions": {
      "experimentalDecorators": true,}Copy the code

7.1 Project Entrance

Include projects using providers

import { Provider } from 'mobx-react';
Copy the code
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { Provider } from 'mobx-react';
import store from './store';
import AxiosConfig from './api';
import Router from './router';
import './index.scss';
import registerServiceWorker from './registerServiceWorker'; 

const Loading = (a)= > (<div>loading...</div>);

AxiosConfig(); // Initialize axios

ReactDOM.render(
  <React.Suspense fallback={<Loading />} ><Provider {. store} >
      <Router />
    </Provider>
  </React.Suspense>,
  document.getElementById('root') as HTMLElement
);

registerServiceWorker();
Copy the code

7.2 module

// src/store/home.ts
import * as mobx from 'mobx';

// Do not modify state directly outside of action
mobx.configure({ enforceActions: "observed"});
const { observable, action, computed, runInAction, autorun } = mobx;

let cache = sessionStorage.getItem('homeStore');

// Initialize the data
let initialState = {
  count: 0.data: {
    time: '2019-11-08'}};// Cache data
if(cache) { initialState = { ... initialState, ... JSON.parse(cache) } }class Home {
  @observable
  public count = initialState.count;

  @observable
  public data = initialState.data;

  @computed
  public get getTime() {
    return this.data.time;
  }

  @action
  public setCount = (_count: number) = > {
    this.count = _count;
  }

  @action
  public setCountAsync = (_count: number) = > {
    setTimeout((a)= > {
      runInAction((a)= > {
        this.count = _count; })},1000);
  }

  // public setCountFlow = flow(function *(_count: number) {
  // yield setTimeout(() => {}, 1000);
  // this.count = _count;
  // })
}

const homeStore = new Home();

autorun((a)= > {
  // Data is cached after data changes
  const obj = mobx.toJS(homeStore);
  sessionStorage.setItem('homeStore'.JSON.stringify(obj));
})

export type homeStoreType = typeof homeStore;
export default homeStore;
Copy the code

7.3 the cache

Use sessionStorage here, change it to something else

When caching data, you can match certain keys as required instead of all data.

  • Initialize data

    During data initialization, if there is data in the cache, the cached data is used to override the default data

    let cache = sessionStorage.getItem('homeStore');
    
    // Initialize the data
    let initialState = {
      count: 0.data: {
        time: '2019-11-08'}};// Cache data
    if(cache) { initialState = { ... initialState, ... JSON.parse(cache) } }Copy the code
  • Monitoring data changes

    Autorun executes on each data change and then converts homeStore to a JS object (containing only state) and stores it in the cache

    const homeStore = new Home();
    
    autorun((a)= > {
      // Data is cached after data changes
      const obj = mobx.toJS(homeStore);
      sessionStorage.setItem('homeStore'.JSON.stringify(obj));
    })
    Copy the code

7.4 Module Management Output

// src/store/index.ts
import homeStore from './home';

/** * Use mobx state management */
export default {
  homeStore
}
Copy the code

7.5 Using Components

Use the decorator on the class, inject the corresponding module, can be multiple inject;

Note the order of @inject(‘homeStore’) @observer otherwise there will be a warning

// src/views/home/index.tsx
import { observer, inject } from 'mobx-react';
import { homeStoreType } from '@/store/home'; . interface IProps extends RouteComponentProps {history: History,
  homeStore: homeStoreType
}

@inject('homeStore')
@observer
class Home extends React.Component<IProps> {... public componentDidMount() {this.props.homeStore.setCount(2);
    console.log(this.props.homeStore.count); / / 2}... }Copy the code

8. Cross-domain proxy

Use the HTTP-proxy-Middleware plug-in

yarn add http-proxy-middleware
Copy the code

The new SCR/setupProxy. Js

const proxy = require("http-proxy-middleware");

module.exports = function(app) {
  app.use(
    proxy('/', {
      target: 'http://192.168.0.5:2333'.changeOrigin: true})); };Copy the code

To use in script/start.js:

in

const devServer = new WebpackDevServer(compiler, serverConfig);
Copy the code

After that, add the following code (not if you can broker it)

require('.. /src/setupProxy')(devServer);
Copy the code

Css-module, global SCSS variable

Class output configuration: [local]__[hash:base64:6], output like content__1f1Aqs, see here

Sass: SRC /utils/variable. SCSS: SRC /utils/variable. SCSS: SRC /utils/variable. SCSS: SRC /utils/variable

yarn add sass-resources-loader
Copy the code
// webpack.config.dev.js, webpack.config.prod.js
  {
    test: /\.(scss|less)$/.exclude: [/node_modules/].use: [{loader: require.resolve('style-loader'),}, {loader: require.resolve('css-loader'),
        options: {
          importLoaders: 1.modules: true.localIdentName: '[local]__[hash:base64:6]'}}, {loader: require.resolve('postcss-loader'),
        options: {
          // Necessary for external CSS imports to work
          // https://github.com/facebookincubator/create-react-app/issues/2677
          ident: 'postcss'.plugins: (a)= > [
            require('postcss-flexbugs-fixes'),
            autoprefixer({
              browsers: [
                '> 1%'.'last 4 versions'.'Firefox ESR'.'not ie < 9'.// React doesn't support IE8 anyway].flexbox: 'no-2009',},],},}, {loader: require.resolve('sass-loader'),}, {loader: 'sass-resources-loader'.options: {
          resources: [
            path.resolve(__dirname, '. /.. /src/utils/variable.scss'),],}}]},Copy the code

List keep-alive

You can see here

11. Advanced components with withRouter

The props type needs to be passed when multiple higher-order components are used

Context.Provider

// src/App.tsx
import * as React from 'react';
import Header from '@/components/header';
import Sidebar from '@/components/sidebar';
import Footer from '@/components/footer';
import styles from './App.scss';
import { RouteComponentProps, withRouter } from 'react-router';

interface IProps extends RouteComponentProps {
  [prop: string]: any
}
exportinterface IState { timer? : any }export type State = Readonly<IState>;

export interface IAppContext {
  appname: string
}
const defaultContext: IAppContext = { appname: 'react-antd-ts' };
export const AppContext = React.createContext(defaultContext);

class App extends React.Component<IProps.State> {
  public readonly state: State = {};
  
  constructor(props: IProps) {
    super(props);
  }

  public render() {
    return (
      <div className={styles.app}>
        <AppContext.Provider value={defaultContext}>
          <Header text="tteexxtt" />
          <Sidebar />
          { this.props.children }
          <Footer />
        </AppContext.Provider>
      </div>
    );
  }
}

export default withRouter(App);
Copy the code

The Context. Consumer packaging

You can also use useContext instead of the following Consumer wrapper

// src/components/withAppContext/index.tsx
import * as React from 'react';
import { AppContext, IAppContext } from '@/App';

// High-level component: AppContext Consumer wrapper
WithAppContext 
      
       (withRouter(Header));
      
function withAppContext<T> (Component: React.ElementType) {
  // T: generic, passing the props type of Component. The wrapped Component is intelligently signaled when used by the parent Component
  // But needs to be separated from the withRouter type,
  // Because withRouter won't pass props other than history/location/match
  return (props: T) = > {
    return( <AppContext.Consumer> { (appcontext: IAppContext) => <Component {... props} {... appcontext} /> } </AppContext.Consumer> ); } } export default withAppContext;Copy the code

Components use

Note:

WithRouter doesn’t pass props other than history/location/match, so this is separate from the props type of the component itself.

2. Generics passed with withAppContext are props of the component itself: IProps

// src/components/header/index.tsx
import * as React from 'react';
import withAppContext from '@/components/withAppContext';
import { withRouter, RouteComponentProps } from 'react-router';
import styles from './header.scss';

const { useEffect } = React;

interface IProps {
  text: string,
  [prop: string]: any
}
// withRouter will not pass props other than history/location/match,
// So this is separate from the props type of the component itself
type IPropsWithRoute = IProps & RouteComponentProps;

function Header(props: IPropsWithRoute) {
  useEffect((a)= > {
    console.log(props); } []);return (
    <section className={styles.header}>
      <div className="center-content">
        <div>LOGO</div>
        <div>HEADER, { props.appname }, {props.text}</div>
      </div>
    </section>
  );
}

// withRouter will not pass props other than history/location/match,
// So here we use props of the component itself: IProps
export default withAppContext<IProps>(withRouter(Header));
Copy the code

12. Internationalization

Use the react – intl

yarn add react-intl @types/react-intl
Copy the code

Use IntlProvider in your App

// src/App.tsx
import { IntlProvider } from 'react-intl';
import messages from '@/lang'; . class App extends React.Component<Props, State> { public readonly state: State = {lang: Cookies.get('lang') | |'zh'
  };
  
  constructor(props: Props) {
    super(props);
  }
  
  public onLangChange(locale: string) {
    Cookies.set('lang', locale);
    this.setState({ lang: locale });
  }

  public render() {
    // console.log(this.props);
    const { lang } = this.state;

    return( <div className={styles.app}> <IntlProvider key="intl" locale={lang} messages={messages[lang]}> <AppContext.Provider value={defaultContext}> <Header text="tteexxtt" onLangChange={(locale: string) => this.onLangChange(locale)} /> <Sidebar /> { this.props.children } <Footer /> </AppContext.Provider> </IntlProvider> </div> ); }}...Copy the code

Language file

Lang entrance

// src/lang/index.ts
import en from './en_US';
import zh from './zh_CN';

export default {
  en,
  zh
};
Copy the code

Messages Specific language

It was originally intended to have an extra layer of language modules like i18N used in Vue, but the plugin structure doesn’t seem to allow for this, so you have to flatten the module and then handle the key names in the messages module below

// src/lang/zh_CN/index.ts
import home from './home';
// import detail from './detail';

export default {
  ...home,
  / /... detail
};
Copy the code

Messages module

Note the key name, temporarily use this way to achieve multi-language modules

// src/lang/zh_CN/home.ts
const home = {
  'home.home': 'home'.'home.list': 'list'.'home.login': 'login'
};

export default home;
Copy the code

Components use

React-intl is a multilanguage package that provides the FormattedMessage with other components to display the amount, currency, and date differences

// src/components/sidebar/index.tsx
import { FormattedMessage } from 'react-intl'; . <FormattedMessage id="home.home" />
Copy the code

To switch between languages

// src/components/header/index.tsx. import Cookiesfrom 'js-cookie';

const { useEffect, useMemo } = React;

interface IProps {
  text: string,
  onLangChange: (locale: string) = > void,
  [prop: string]: any
}
// withRouter will not pass props other than history/location/match,
// So this is separate from the props type of the component itself
type IPropsWithRoute = IProps & RouteComponentProps;

function Header(props: IPropsWithRoute) {
  const lang = useMemo((a)= > {
    return Cookies.get('lang') | |'zh';
  }, [Cookies.get('lang')]);

  return (
    <section className={styles.header}>.<div className={styles.langsection}>
            <span 
              className={` ${styles.lang} ${lang= = ='zh' ? styles.active :"'} `}onClick={()= >Props. OnLangChange (' useful ')} > Chinese</span>
            <span 
              className={` ${styles.lang} ${lang= = ='en' ? styles.active :"'} `}onClick={()= > props.onLangChange('en')}
            >English</span>
          </div>.</section>); }...Copy the code

13, build,

The output

With chunkhash, each build generates a hash, resulting in the same content but still a changed filename; Contenthash generates a hash based on the content, and the hash value is content dependent, which is better for caching, but inevitably leads to an increase in build time, but it’s worth it

  • File name: Modifies the file name in outputchunkhash -> contenthash, such as:
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js'.Copy the code
  • The code segment
  optimization: {
    splitChunks: {
      chunks: 'all'}},Copy the code

tree-shaking

The webpack documentation says to set mode: ‘production’, but if I build the webpack Module, I will get an error. If you set mode to ‘development’, you can access the file, but the file is larger than production, so it is not meaningful.

TypeError: Cannot read property 'call' of undefined
Copy the code

In the package. The json

Add “sideEffects” : false,

Webpack. Prod. Js

  optimization: {
    ...
    // tree shaking, used with "sideEffects": false in package.json
    usedExports: true
  }
Copy the code

Third-party resource CDN

Currently only the build uses the resource CDN introduction and there is no difference in the development stage

React-router-dom is unavailable because an error is reported

Temporary manual processing, can also use HtmlWebpackPlugin automatic processing

Format: Package name: name of the exported variable

  • Webpack externals of use:
  externals: {
    'axios': 'axios'.'lodash' : {
      commonjs: 'lodash'.amd: 'lodash'.root: '_' // point to a global variable
    },
    'react': 'React'.'react-dom': 'ReactDOM'.'react-router': 'ReactRouter'.// 'react-router-dom': 'ReactRouterDOM',
    'mobx': 'mobx'.'react-mobx': 'ReactMobx',},Copy the code
  • Add CDN links of third-party resources to public/index. HTML
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-router/5.0.1/react-router.min.js"></script>
<! -- < script SRC = "https://cdn.bootcss.com/react-router-dom/5.0.1/react-router-dom.min.js" > < / script > -- >
<script src="https://cdn.bootcss.com/mobx/4.14.0/mobx.umd.min.js"></script>
<script src="https://cdn.bootcss.com/mobx-react/5.2.0/custom.min.js"></script>
<script src="https://cdn.bootcss.com/lodash.js/4.17.15/lodash.core.min.js"></script>
Copy the code

The last

  • Basically, all the things used in the project are on the top. Please update and add other things later.
  • Some of the previous code is early written, followed by new things, so some functions are not the same as the back, but according to the previous writing method generally will not have a problem; It’s a new feature that requires rewriting part of the original code
  • In addition, webpack development/production configuration can only use one, and then use webpack merge into the can, this article webpack is based on the old file changes, maybe some things are redundant…
  • React Hooks are already good enough to almost eliminate the need to write class components
  • The props passing of multiple higher-order component combinations needs a little attention
  • React has been used for a few months, and that’s all I can think of.