TypeScript + TSLint + Prettier + Reactの開発環境をコピペで構築

TypeScriptの開発環境をコピペで簡単に作ります。 
今回はtslintやprettierも使用して、より実践向きな形にしたいと思います。

## シンプルな環境構築

### node_modulesのインストール

#### polyfill

```
yarn add babel-polyfill
```

#### webpack

```
yarn add -D webpack webpack-cli webpack-dev-server
```

#### loader

```
yarn add -D typescript ts-loader
```

#### Lint

```
yarn add -D tslint tslint-config-airbnb tsutils
```

#### formatter

```
yarn add -D prettier tslint-config-prettier tslint-plugin-prettier
```


### ディレクトリ作成

```
mkdir -p src/srcipts　＆＆　touch index.html src/srcipts/app.ts webpack.config.js .prettierrc.yml 
```

```
npx tslint --init　&& npx tsc --init
```

### 記述

#### HTML

```html
<!DOCTYPE html>
<html lang='ja'>
<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <meta http-equiv='X-UA-Compatible' content='ie=edge'>
  <title>TypeScript template</title>
</head>
<body>
  <h1>HELLO TS!!</h1>
  <script src='./dist/js/vendor.js'></script>
  <script src='./dist/js/app.js'></script>
</body>
</html>
```

#### webpack.config.js

```js

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    vendor: ['babel-polyfill'],
    app: './src/srcipts/app.ts'
  },
  output: {
    path: path.join(__dirname, 'dist/js/'),
    chunkFilename: '[name].js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.ts?$/,
        exclude: /node_modules/,
        use: 'awesome-typescript-loader'
      }
    ]
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /node_modules/,
          name: 'vendor',
          enforce: true,
          chunks: 'all'
        }
      }
    }
  },
  resolve: {
    modules: ['node_modules'],
    extensions: ['.ts', '.js']
  }
};
```

#### tsconfig.json

```json
{
  "compilerOptions": {
    "charset": "utf-8",
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "target": "es5",
    "jsx": "react",
    "lib": [
      "es5",
      "es2015",
      "es2017",
      "dom"
    ],
    "typeRoots": [
      "typings",
      "node_modules/@types"
    ]
  },
  "includes": [
    "src/srcipts/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

#### tslint.json

```json
{
    "extends": [
        "tslint-config-airbnb",
        "tslint-config-prettier"
    ],
    "rulesDirectory": [
        "tslint-plugin-prettier"
    ],
    "rules": {
        "prettier": true,
        "max-line-length": [true, 120],
        "object-shorthand-properties-first": false,
        "radix": false,
        "ter-arrow-parens": false,
        "no-boolean-literal-compare": true,
        "import-name": false
    }
}
```

#### .prettierrc.yml

```yml
printWidth: 120
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
trailingComma: all

overrides:
- files: "*.json"
  options:
    tabWidth: 2
    singleQuote: false
```

### 実行

```
# トランスパイル & watch
node_modules/.bin/webpack --w

# lint & format
npx tslint --project tsconfig.json 'front/scripts/**/*.ts{,x}' --fix
```

ここまででトランスパイルできれば、一旦TypeScriptの環境の構築は完了です。
以下はReact Reduxの開発環境になります。


## React Redux環境

### node_modules

#### React + Redux + React Router

```
yarn add react react-dom react-redux react-router-dom redux redux-actions redux-logger
```

#### Types

```
yarn add -D @types/react @types/react-dom @types/react-redux @types/react-router-dom @types/redux @types/redux-actions @types/redux-logger
```

### ディレクトリ&ファイル作成

```
mkdir -p src/scripts/actions src/scripts/components src/scripts/configureStore src/scripts/containers src/scripts/reducers

touch src/scripts/app.tsx src/scripts/router.tsx src/scripts/actions/index.ts src/scripts/components/index.tsx src/scripts/configureStore/index.ts src/scripts/containers/index.ts src/scripts/reducers/index.ts
```

app.tsx

```tsx
import * as React from 'react';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import Router from './router';
import configureStore from './configureStore';

const initialState = {
  counter: {
    count: 0,
  },
};

const store = configureStore(initialState);

render(
  <Provider store={store}>
    <BrowserRouter basename="/">
      <Router />
    </BrowserRouter>
  </Provider>,
  document.getElementById('app'),
);
```
router.tsx

```tsx
import * as React from 'react';
import { Route, Switch } from 'react-router-dom';
import Top from './containers/Top';

const router = () => {
  return (
    <Switch>
      <Route exact path="/" component={Top} />
    </Switch>
  );
};

export default router;
```

actions/index.ts

```ts
import { createAction } from 'redux-actions';

export const INCREMENT = 'increament';
export const increament = createAction(INCREMENT);

export const DECREMENT = 'decreament';
export const decreament = createAction(DECREMENT);
```

components/Top.tsx

```tsx
import * as React from 'react';

interface Props extends React.Props<{}> {
  count: number;
  increament: () => void;
  decreament: () => void;
}

class Top extends React.Component<Props> {
  onClick() {
    this.props.increament();
  }
  render() {
    return (
      <div>
        <button onClick={this.onClick.bind(this)}>+</button>
        <div>{this.props.count}</div>
        <button onClick={this.props.decreament}>-</button>
      </div>
    );
  }
}

export default Top;
```
configureStore/index.ts

```ts
import { createStore, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import reducers, { RootState } from '../reducers';

export default function configureStore(initialState: RootState) {
  const store = createStore(reducers, initialState, applyMiddleware(createLogger()));
  return store;
}
```
containers/Top.ts

```ts

import * as React from 'react';
import { connect } from 'react-redux';
import TopComponent from '../components/Top';
import { increament, decreament } from '../actions';

const mapStateToProps = (state: any) => {
  return { count: state.counter.count };
};

const mapDispatchToProps = (dispatch: any) => {
  return {
    increament: () => {
      dispatch(increament());
    },
    decreament: () => {
      dispatch(decreament());
    },
  };
};

const container = connect(
  mapStateToProps,
  mapDispatchToProps,
)(TopComponent);

export default container;
```

reducers/counter.ts

```ts
import { INCREMENT, DECREMENT } from '../actions';

export type Counter = {
  readonly count: number;
};

type Action = {
  readonly type: string;
};

const counter = (state: Counter = { count: 0 }, action: Action) => {
  switch (action.type) {
    case INCREMENT:
      return Object.assign({}, state, { count: state.count + 1 });
    case DECREMENT:
      return Object.assign({}, state, { count: state.count - 1 });
    default:
      return state;
  }
};

export default counter;
```

reducers/index.ts

```ts
import { combineReducers } from 'redux';
import counter, { Counter } from './counter';

const reducers = combineReducers({
  counter,
});

export type RootState = {
  counter: Counter;
};

export default reducers;
```

## まとめ
初めて学ぶときほど、lintやformatterをしっかり使う事で、作法を覚える事ができるので、できる限り入れるようにしています。TypeScriptについても今回のものをベースに学習を進めていければと思います。