Reprint please indicate the source!

Say the previous words:

1. Why not use ready-made scaffolding? Scaffolding configuration is too much and too heavy, I just want to use a few libraries and plug-ins THAT I can understand, and then slowly add others. And you can learn a lot more by configuring yourself from scratch.

The tutorial only configures the development environment, not the production environment.

This tutorial is for people who have experience with React + Redux and want to use TypeScript in their new projects (or who want to configure their development environment from scratch).

4. Because of the rapid development of the front end, the configuration that works today may not work tomorrow (for example, the React-Router has V4 and is officially completely rewritten), so the packages installed in this article are of the specified version.

5, the tutorial follows the principle of minimum availability, so can not use libraries and plug-ins are useless (mainly can not many, afraid to use the problem, escape ~~).

6. Based on 5, the tutorial won’t start with everything installed, it will take time.

6, The tutorial is completed on macOS, there may be some other problems with the WIN environment.

 

The initial conditions

Node version 6.9.0

 

Initialize the project

Create and enter the project

mkdir demo && cd demoCopy the code

Initialize the project

npm initCopy the code

 

Install initial dependencies

First, install webpack and webpack-dev-server.

npm i -D webpack@3.6.0Copy the code

Then install the React declaration files in React and Types

npm i --S react@15.5.4 react-dom@15.5.4 @types/react@15.6.0 @types/react-dom@15.5.0Copy the code

The packages starting with @types above are typeScript declarations, which you can view by going to node_modules/@types/XX/index.d.ts.

Details about the declaration file can be found in the DefinitelyTyped library on Github.

Next install TypeScript, TS-Loader, and source-map-loader

npm i -D typescript@2.5.3 ts-loader@2.3.7 source-map-loader@0.2.2Copy the code

Ts-loader lets Webpack compile TypeScript code using TypeScript’s standard configuration file, tsconfig.json.

Source-map-loader uses any sourcemap output from Typescript to tell WebPack when to generate its own Sourcemaps. This lets you debug the resulting file as if you were debugging TypeScript source code.

 

Add the TypeScript configuration file

We need a tsconfig.json file to tell TS-Loader how to compile TypeScript code.

Create a tsconfig.json file in the current root directory and add the following:

{
  "compilerOptions": {
    "outDir": "./dist/"."sourceMap": true."noImplicitAny": true."module": "commonjs"."target": "es5"."jsx": "react"
  },
  "include": [
    "./src/**/*"]}Copy the code

OutDir: output directory.

SourceMap: Generates the corresponding sourceMap file when compiling ts files into JS files.

NoImplicitAny: If true, the TypeScript compiler still generates JavaScript files when it cannot infer the type, but it also reports an error. It is better to set it to true in order to find errors.

Module: code specification, also can choose AMD, CMD.

Target: Converts to ES5

JSX: TypeScript has three JSX modes: Preserve, React, and React-Native. These patterns only work during the code generation phase – type checking is not affected. JSX is preserved in the generated code in Preserve mode for subsequent conversion operations (e.g., Babel). In addition, the output file will have a.jsx extension. The react mode generates react. CreateElement, which does not need to be converted before use. The output file has a.js extension. React-native is equivalent to preserve, which also preserves all JSX, but the output file has a.js extension. We don’t know how to use Babel, so we can use React.

Include: directory to compile.

 

Write some code

First create a directory

mkdir src && cd src
mkdir components && cd componentsCopy the code

Add a hello. TSX file to this folder as follows:

import * as React from 'react'; export interface Props { name: string; enthusiasmLevel?: number; } exportdefault class Hello extends React.Component<Props, object>{ render() { const { name, enthusiasmLevel= = 1}this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D'); }return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
      </div>
); }}

function getExclamationMarks(numChars: number) {
  returnArray(numChars + 1).join('! ');
}Copy the code

Next, create the index.tsx file under SRC as follows:

import * as React from "react";
import * as ReactDOM from "react-dom"; import Hello from"./components/Hello"; ReactDOM.render(<Hello name="TypeScript" enthusiasmLevel={10} />,
  document.getElementById('root') as HTMLElement
);Copy the code

 

We also need a page to display the Hello component. Create a file named index.html in the root directory as follows:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>demo</title>
</head>

<body>
  <div id="root"></div>
  <script src="./dist/bundle.js"></script>
</body>

</html>Copy the code

 

Write a WebPack configuration file

Create a file called webpack.common.config.js in the root directory and add the following contents:

module.exports ={ entry:"./src/index.tsx", output: { filename:"bundle.js", path: __dirname+ "/dist"}, devtool:"source-map", resolve: { extensions: [".ts", ".tsx", ".js", ".json"] }, module: { rules: [ { test:/\.tsx? $/, loader: "ts-loader"}, { enforce:"pre", test: /\.js$/, loader: "source-map-loader"} ] }, plugins: [ ], };Copy the code

I don’t want to explain too much here. Basically webpack experience can understand. As for why webpack.common.config.js instead of webpack.config.js. Since we are configuring the development environment now and the production environment later, we need multiple configuration files and put the common parts of both into webpack.common.config.js

Run the following command in the root directory:

webpack --config webpack.common.config.jsCopy the code

Then open up index.html to see the page we wrote.

 

Write the WebPack development environment configuration file

For formal development, the above code is definitely not enough, we need the most basic and most useful hot update function provided by Webpacl-dev-server.

NPM [email protected] I - DCopy the code

Create webpack.dev.config.js in the root directory and add the following configuration:

const webpack = require('webpack');
const config = require('./webpack.common.config');
config.devServer = {
  hot: true, publicPath:'/dist/'
}
config.plugins.push(new webpack.HotModuleReplacementPlugin());
module.exports = config;Copy the code

Common configurations need to be introduced first and then modified on top of that.

DevServer is the configuration item for Webpack-dev-server.

Hot: open hot update, open hot after the update, we need to add webpack in plugins. HotModuleReplacementPlugin to fully enabled. If you start webpack-dev-server with –hot in the command, the plugin will be automatically loaded, and you don’t need to import it in config.js.

The relevant section on HMR can be viewed by clicking on Webpack HMR.

PublicPath: resource directory, since webpack-dev-server starts to put compiled resources in memory, where are these resources? In the directory specified by publicPath, since the output.path configured in webpack.common.config.js is in the /dist directory of the current directory, in order not to change the index.html file in the root directory, So we’ll do dist/ here. See the Webpack2 paths for details in this section

Run the command:

webpack-dev-server --config webpack.dev.config.jsCopy the code

Open the web page and go to Localhots :8080 to see our page. Open the developer tools in your browser and you will see the following two messages in the console section indicating that the hot update has started successfully.

Then add the long commands to NPM scripts. Add “start” to the scripts of package.json: “webpack-dev-server –config webpack.dev.config.js”

Enter NPM start to start our service.

 

Add a simple Redux (non-novice direction)

Install redux dependencies

To reflect Redux, let’s add two buttons to our web page to add/remove exclamation marks after text.

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

export interface StoreState { languageName: string; enthusiasmLevel? : number; }Copy the code

Define some constants for action and Reducer use, put SRC/Constants /index.tsx

export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeofINCREMENT_ENTHUSIASM; export const DECREMENT_ENTHUSIASM= 'DECREMENT_ENTHUSIASM';
export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;Copy the code

To add action, place SRC /actions/index.tsx

import * as constants from '.. /constants'export interface IncrementEnthusiasm { type: constants.INCREMENT_ENTHUSIASM; } export interface DecrementEnthusiasm { type: constants.DECREMENT_ENTHUSIASM; } export type EnthusiasmAction= IncrementEnthusiasm |DecrementEnthusiasm; exportfunction incrementEnthusiasm(): IncrementEnthusiasm {
  return{ type: constants.INCREMENT_ENTHUSIASM } } exportfunction decrementEnthusiasm(): DecrementEnthusiasm {
  return{ type: constants.DECREMENT_ENTHUSIASM } }Copy the code

Add reducer and add SRC /reducers/index.tsx

import { EnthusiasmAction } from '.. /actions';
import { StoreState } from '.. /types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '.. /constants/index'; exportfunction enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
  switch (action.type) {
    case INCREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
    case DECREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1)}; }return state;
}Copy the code

To modify the Hello component, here is the code:

import * as React from 'react'; export interface Props { name: string; enthusiasmLevel?: number; onIncrement? : () = >void; onDecrement? : () = >void; } exportdefault function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D'); }return (
    <div className="hello">
      <div className="greeting">
        Hello {name + getExclamationMarks(enthusiasmLevel)}
      </div>
      <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
      </div>
    </div>
  );
}

function getExclamationMarks(numChars: number) {
  returnArray(numChars + 1).join('! ');
}Copy the code

At this point our page has been modified successfully, but nothing happens when we click on it because we haven’t connected to Redux’s store yet.

Add a container to link the Hello component into SRC /containers/ hello.tsx

import Hello from '.. /components/Hello';
import * as actions from '.. /actions/';
import { StoreState } from '.. /types/index';
import { connect, Dispatch } from 'react-redux'; exportfunction mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
  return{ enthusiasmLevel, name: languageName, } } exportfunction mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
  return{ onIncrement: ()= >dispatch(actions.incrementEnthusiasm()), onDecrement: ()= >dispatch(actions.decrementEnthusiasm()), } } exportdefault connect(mapStateToProps, mapDispatchToProps)(Hello);Copy the code

Create an initState to define the initial value of store and place it in/SRC /store/ initstate.tsx

export default{ enthusiasmLevel:1, languageName:'TypeScript',}Copy the code

Create a store, put/SRC/store/configureStore TSX

import { createStore } from 'redux';
import { enthusiasm } from '.. /reducers/index';
import { StoreState } from '.. /types/index';
import initState from './initState';
export default function() { const store= createStore<StoreState>(enthusiasm, initState);
  return store;
}Copy the code

Finally modify the entry file index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import Hello from './containers/Hello';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore'; const store= configureStore();
ReactDOM.render(
  <Provider store={store}>
    <Hello />
  </Provider>,
  document.getElementById('root') as HTMLElement
);Copy the code

At this point, a simple redux is ready. Click the button to add/remove exclamation marks.

However, there are still many imperfections, such as Hello component is a function, and reducer has only one (there will be some bugs to be solved in the process of solving these two problems).

Rest assured, this is all addressed in “Adding a Sufficient Redux” below.

 

Add an adequate Redux

It was clear that a simple Redux would not be enough for our slightly larger development.

So let’s rewrite our code.

The first is our Hello component. Let’s change the Hello component to a class

export default class Hello extends React.Component<Props, {}>{ constructor(props: Props) { super(props); } render() { const { name, enthusiasmLevel= 1, onIncrement, onDecrement } = this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D'); }return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
        <div>
          <button onClick={onDecrement}>-</button>
          <button onClick={onIncrement}>+</button>
        </div>
      </div>
); }}Copy the code

Save, compile, and then error!

————————————————————————————————

The ERROR in the. / SRC/containers/hello.html TSX (20, 21) : ERROR TS2345: Argument of type ‘typeof Hello’ is not assignable to parameter of type ‘ComponentType<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusia… ‘. Type ‘typeof Hello’ is not assignable to type ‘StatelessComponent<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnt… ‘. Type ‘typeof Hello’ provides no match for the signature ‘(props: { enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusiasm; onDecrement: () => DecrementEnthusiasm; } & { children? : ReactNode; }, context? : any): ReactElement

‘.

————————————————————————————————

To copy, then Google it, can find the answer we want TypeScript – React – Starter | Issues# 29, from the point of view of others answer seems to be a bug? So let’s change our Hello container by answering

export function mergeProps(stateProps: Object, dispatchProps: Object, ownProps: Object) {
  returnObject.assign({}, ownProps, stateProps, dispatchProps); } exportdefaultconnect( mapStateToProps, mapDispatchToProps, mergeProps)(Hello);Copy the code

As soon as we finished, the IDE alerted us to an error before we could save it:

Property ‘assign’ does not exist on type ‘ObjectConstructor’.

Object does not provide an assign method. There are two ways to solve this problem. One is to install the object-assign NPM package and use it instead. The other option is to change target from “ES5” to “ES6” in tsconfig.json.

Then compile again and find that the error is still reported. Except this time the error message was changed:

————————————————————————————————

ERROR in./ SRC /index.tsx (10,5): ERROR TS2322: Type ‘{}’ is not assignable to type ‘IntrinsicAttributes & IntrinsicClassAttributes

& Object>’. Property ‘name’ is missing in type ‘{}’.

————————————————————————————————

TSX: the name attribute must be passed in the interface defined by the Hello component, but the name attribute is not passed in the index.

However, if your browser is Chrome and you have the React plugin installed, you can see that the compiled code is passed

Consider it a bug. There are two solutions. One is to add a name to the Hello container in index.tsx

ReactDOM.render(
  <Provider store={store}>
    <Hello name="123"/>
  </Provider>,
  document.getElementById('root') as HTMLElement
);Copy the code

Even if I add the name, it’s still the name in the store. So we’re going to do it this way, and we’re going to add the React-router to it and the code is going to change and we’re not going to have this problem.

The other option is to change the name property to a non-essential property in the Hello component:

export interface Props { name?: string; enthusiasmLevel?: number; onIncrement? : () = >void; onDecrement? : () = >void;
}Copy the code

This approach is not recommended.

Well, the component changes are now complete.

Next we solve a number of reducer problems.

First let’s change the default value of initState:

export default{ demo: { enthusiasmLevel:1, languageName:'TypeScript',}}Copy the code

/ SRC /types/index.tsx also needs to be changed:

export interface demo { languageName: string; enthusiasmLevel?: number; } export interface StoreState { demo: demo; }Copy the code

/ SRC /reducers/index.tsx named demo.tsx and modified the content:

import { EnthusiasmAction } from '.. /actions';
import { demo } from '.. /types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '.. /constants/index';
import initState from '.. /store/initState';
export function enthusiasm(state: demo = initState.demo, action: EnthusiasmAction): demo {
  switch (action.type) {
    case INCREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
    case DECREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1)}; }return state;
}Copy the code

We changed the interface type and added a default value for state.

Then create a new index.tsx file and add the following:

import { combineReducers } from 'redux';
import { enthusiasm } from './demo';
const rootReducer =combineReducers({ demo: enthusiasm }); exportdefault rootReducer;Copy the code

Correspondingly, we need to change the value of the reference in the Hello container:

export function mapStateToProps({ demo: { enthusiasmLevel, languageName } }: StoreState) {
  return{ enthusiasmLevel, name: languageName, } }Copy the code

Finally, modify the reducer reference in configureStore:

import { createStore } from 'redux'; import reducers from'.. /reducers/index';
import { StoreState } from '.. /types/index';
import initState from './initState';
export default function() { const store= createStore<StoreState>(reducers, initState);
  return store;
}Copy the code

Save the changes. An error…

————————————————————————————

The ERROR in the. / SRC/store/configureStore TSX (9) : ERROR TS2345: Argument of type ‘Reducer<{}>’ is not assignable to parameter of type ‘Reducer

‘. Type ‘{}’ is not assignable to type ‘StoreState’. Property ‘demo’ is missing in type ‘{}’.

————————————————————————————

If this sounds familiar, it’s almost the same as the error with the Hello component in index.tsx.

There are also two solutions. One is to find const Store = createStore

(reducers, initState) in configureStore.tsx; Delete the

generic.

/ SRC /types/index.tsx, demo: demo; Plus one? Make it a non-required property demo? : demo; That’s what we do here.

Here is a bug or what other reasons, I hope there is a god can answer.

At this point, we have enough Redux.

 

Add a React-Router

Note that react-Router is now available in version V4 and is officially a complete rewrite. So I’m not familiar with the situation, so I’ll just play it safe by choosing V3 and updating it later.

Install dependencies

NPM I-S [email protected] @types/[email protected]Copy the code

Create the file phones. TSX in the SRC directory and add the following information:

import * as React from 'react';
import { Route, IndexRoute } from 'react-router';
import Hello from './containers/Hello'; exportdefault (
  <Route path="/">
    <IndexRoute component={Hello} />
    <Route path="/demo">
      <IndexRoute component={Hello} />
    </Route>
  </Route>
);Copy the code

A demo path is added to show what the route does.

Then add our route to index. TSX

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
const store = configureStore();
ReactDOM.render(
  <Provider store={store}>
    <Router history={browserHistory} routes={routes} />
  </Provider>,
  document.getElementById('root') as HTMLElement
);Copy the code

Since we added browserHistory as a route, not hashHistory, we need to do some routing configuration for the server. As for why, please search by yourself, here do not explain. If you don’t want to use too many Settings, you can just replace browserHistory with hashHistory.

Our development server is webpack-dev-server, so let’s change webpack.dev.congfig. Js:

const webpack = require('webpack');
const config = require('./webpack.common.config');
config.devServer ={ hot:true, publicPath:'/dist/', historyApiFallback: { index:'./index.html'
  },
}
config.plugins.push(new webpack.HotModuleReplacementPlugin());
module.exports = config;Copy the code

It simply points the page to index.html when the server can’t find the routing directory.

Because of the configuration change, we need to restart the server NPM start

Enter localhost: 8080 / demo

If the Hello component is displayed, the configuration is successful.

 

Add the React – the Router – story

The react-Router version has been updated a lot, so the version should be strictly controlled.

Install dependencies

NPM I-S [email protected] @types/[email protected]Copy the code

Change the index.tsx code as follows:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import { syncHistoryWithStore } from 'react-router-redux'; const store= configureStore();
const history =syncHistoryWithStore(browserHistory, store); ReactDOM.render(<Provider store={store}>
    <Router history={history} routes={routes} />
  </Provider>,
  document.getElementById('root') as HTMLElement
);Copy the code

Then add routerReducer in SRC /reducers/index.tsx

import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { enthusiasm } from './demo';
const rootReducer =combineReducers({ demo: enthusiasm, routing: routerReducer }); exportdefault rootReducer;Copy the code

OK, very simple.

 

conclusion

If there is an error in the react-router and react-router-redux configuration, it is basically the version of the NPM package. Please remove node_modules and reinstall the version I specified.

Summary: I did run into various problems during the installation process, especially with the history version of the Router package, which took a long time. It seems to be a very simple tutorial, but I have stepped on a lot of holes behind it, but fortunately it is finished.

Then there’s the integration of Ant-Design and the configuration of the production environment, which will be updated throughout this tutorial.

 

Resources: typescript-react-starter

The React with webpack | TypeScript faced (in Chinese)

webpack HMR

Explain the paths to Webpack2

TypeScript-React-Starter | Issues#29

Webpack-dev-server uses the react-router browserHistory configuration