preface
- Use react 16.8.x and typescript 3.5.3 in projects
- And then also use
koa2+typescript
Set 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 by
redux+rematch
Change 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
-
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});
-
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)
-
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 ‘./’
-
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/tsx
useawesome-typescript-loader
This loader resolvesantd
The CSS of the component is loaded and used on demandbabel-plugin-import
This 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
- ordinary
connect + mapState + mapDispatch
writing
// 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 }  
<Button onClick={props.addCount}>count++</Button>
</p>
<p className={styles.count}>
countAsync: { props.countAsync }  
<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-redux
The new Hooks:useSelector, useDispatch
writing
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 }  
<Button onClick={()= > dispatch({ type: 'common/addCount' })}>count++</Button>
</p>
<p className={styles.count}>
countAsync: { countAsync }  
<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@action
Decorated function interior modificationstate
, such as the followingsetTimeout
Internal modification data - Flow: Returns a generator function with
function */yield
Instead ofasync/await
(These two are actually their grammar candy), do not need to use@action/runInAction
- @ inject (‘ homeStore) : will be
homeStore
Injection 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 output
chunkhash
->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.