Help us understand the problem. What is going on with this article?

Redux ExampleのTodo ListをはじめからていねいにをTypescriptで(2)

More than 3 years have passed since last update.

Redux ExampleのTodo ListをはじめからていねいにをTypescriptで(1)の続き。
Redux ExampleのTodo Listをはじめからていねいに(2)をtypescriptで書いたメモ。

ここまでのディレクトリ構成

redux-todo
  - bin # docker-composeの操作をシェル化
    - start.sh # 開発サーバの起動
    - build.sh # dist内にjsファイルをビルド
    - bash.sh  # コンテナ内にログイン
    - container_build.sh # コンテナの再作成
    - remove_all_container.sh # 全てのコンテナの削除
  - public # 開発サーバのベースとなるフォルダ
    - index.html # 開発サーバのホーム。
  - src
    - app.tsx     # エントリーポイント
    - actions     # Action(Actionの発行)
      - index.tsx
    - components  # Presentational Components(Reactのコンポーネント)
      - App.tsx
      - Todo.tsx
      - TodoList.tsx
    - containers  # Container Components(Reduxのコンポーネント)
      - AddTodo.tsx
      - VisibleTodoList.tsx
    - reducers    # Reducer(stateの変更)
      - index.tsx
      - todos.tsx
    - states      # stateの定義( typescriptにするときに自分が追加したディレクトリ )
      - TodoState.tsx
  - webpack # ビルドツール
    - Dockerfile              # コンテナの環境設定ファイル
    - package.json            # コンテナ内にコピーされるnpm設定ファイル
    - webpack.config.babel.js # ビルドツールの設定
    - tsconfig.json           # ビルドツールで利用するTypescript設定
  + dist   # ビルドされたファイルの格納先
  - docker-compose.yml # コンテナ起動時設定ファイル

動作環境の更新

node.jsとwebpackのバージョンが上がっていたので更新した。

webpack/Dockerfile
# docker-hubからnode入りコンテナを取得
# https://hub.docker.com/_/node/
FROM node:7.2.1

# コンテナ上の作業ディレクトリ作成
WORKDIR /my_webpack

# 後で確認出来るようにpackage.jsonを作成
RUN npm init -y

# jsViewライブラリreact
RUN npm i --save react
RUN npm i --save react-dom

# jsフレームワークredux
RUN npm i --save-dev redux
RUN npm i --save react-redux

# typescript
RUN npm i --save-dev typescript@next

# ビルドツール
RUN npm i --save-dev webpack@2.2.0-rc.0

# 開発用サーバ
RUN npm i --save-dev webpack-dev-server@2.1.0-beta.12

# webpack用typescript loader
RUN npm i --save-dev ts-loader

# typescriptの型定義ファイル
RUN npm i --save-dev @types/react
RUN npm i --save-dev @types/react-dom
RUN npm i --save-dev @types/redux
RUN npm i --save-dev @types/react-redux
webpack/package.json
{
  "name": "my_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --display-error-details",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^15.4.1",
    "react-dom": "^15.4.1",
    "react-redux": "^5.0.1"
  },
  "devDependencies": {
    "@types/react": "^0.14.55",
    "@types/react-dom": "^0.14.19",
    "@types/react-redux": "^4.4.35",
    "@types/redux": "^3.6.0",
    "redux": "^3.6.0",
    "ts-loader": "^1.3.2",
    "typescript": "^2.2.0-dev.20161214",
    "webpack": "^2.2.0-rc.0",
    "webpack-dev-server": "^2.1.0-beta.12"
  }
}

webpackが以下の警告を出すようになった。

buildtool_react_ts_1  | WARNING in asset size limit: The following asset(s) exceed the recommended size limit (250 kB).
buildtool_react_ts_1  | This can impact web performance.
buildtool_react_ts_1  | Assets:
buildtool_react_ts_1  |   vendor.bundle.js (1.07 MB)
buildtool_react_ts_1  |
buildtool_react_ts_1  | WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (250 kB). This can impac
t web performance.
buildtool_react_ts_1  | Entrypoints:
buildtool_react_ts_1  |   typescript (1.08 MB)
buildtool_react_ts_1  |       vendor.bundle.js
buildtool_react_ts_1  |       bundle.js
buildtool_react_ts_1  |
buildtool_react_ts_1  |   vendor (1.07 MB)
buildtool_react_ts_1  |       vendor.bundle.js
buildtool_react_ts_1  |
buildtool_react_ts_1  |
buildtool_react_ts_1  | WARNING in webpack performance recommendations:
buildtool_react_ts_1  | You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
buildtool_react_ts_1  | For more info visit https://webpack.js.org/guides/code-splitting/
buildtool_react_ts_1  | webpack: bundle is now VALID.

code-splittingの設定をしたのだけれど、警告はでたまま。

とりあえず即座に影響はないのでこのまま進める。

消す方法がわかったら消したい。

webpack/webpack.config.js
var webpack = require('webpack');

module.exports = {
  // src以下のソースをビルド対象とする
  context: __dirname + '/src',
  // エントリーポイントとしてapp.jsを起点にビルドする
  entry: {
    typescript: './app.tsx',
    // code-splitting用の設定
    vendor: ['react', 'react-dom', 'redux', 'react-redux']
  },
  // distにビルドしたファイルをbundle.jsの名前で保存
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  // importするときに、以下の配列に登録した拡張子は省略できる
  resolve: {
    extensions: [".js", ".ts", ".tsx"]
  },
  module: {
    rules: [
      // .ts, .tsxに一致する拡張子のファイルはts-loaderを通してトランスパイル
      { test: /\.tsx?$/, exclude: /node_modules/, loader: "ts-loader" }
    ]
  },
  plugins: [
    // hot loadを有効にするためのプラグイン
    new webpack.HotModuleReplacementPlugin(),
    // code-splittingを有効にするプラグイン
    new webpack.optimize.CommonsChunkPlugin({/* chunkName= */name: "vendor", /* filename= */ filename: "vendor.bundle.js"})
  ],
  // source-mapを出力して、ブラウザの開発者ツールからデバッグできるようにする。
  // webpack-dev-serverなら不要?
  // devtool: '#cheap-module-source-map',
  // 開発サーバの設定
  devServer: {
    // public/index.htmlをデフォルトのホームとする
    contentBase: './public',
    // インラインモード
    inline: true,
    // 8080番ポートで起動
    port: 8080,
    // dockerのコンテナ上でサーバを動かすときは以下の設定で全ての接続を受け入れる
    host:"0.0.0.0",
    // hot loadを有効にする
    hot: true
  },
  // vagrantの仕様でポーリングしないとファイルの変更を感知できない
  watchOptions: {
    aggregateTimeout: 300,
    // 5秒毎にポーリング
    poll: 5000
  }
};
public/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Document</title>

</head>
<body>
  <div id="root"></div>
  <script src="vendor.bundle.js"></script>
  <script src="bundle.js"></script>
</body>
</html>

1. 完了・未完了を表すcompletedによってスタイルを変える

todoにcompleted要素を追加して、とりあえず取り消し線を表示する

todos.tsxはnew Todostate()をしているだけなので今回は変更しない。

代わりにTodoState.tsxを更新

TodoState.tsx
export default class TodoState {
  constructor(
    public id: number,
    public text: string,
    public completed: boolean = false
  ){}
}
src/components/Todo.tsx
import * as React from 'react';
import {PropTypes} from 'react';

interface IProps {
    completed: boolean;
    text: string;
}

const Todo = ({ completed, text }:IProps) => (
  <li style={{textDecoration: completed ? 'line-through' : 'none'}}>
    {text}
  </li>
);

Todo.prototype.propTypes = {
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo;

確認

TodoState.tsx
export default class TodoState {
  constructor(
    public id: number,
    public text: string,
    public completed: boolean = true
  ){}
}

completed = trueにしてブラウザでアクセス。

ここの時点のソース

github

actionCreatorからcompleted要素を操作する

src/actions/index.tsx
import { Action } from 'redux';

export type Actions = AddTodoAction | ToggleTodoAction;

// 省略

export interface ToggleTodoAction extends Action {
    type: 'TOGGLE_TODO';
    id: number;
}

// 省略

export const toggleTodo = (id:number) : ToggleTodoAction => {
  return {
    type: 'TOGGLE_TODO',
    id
  }
}
src/reducers/todos.tsx
import { Actions } from '../actions';
import TodoState from '../states/TodoState';

// 現在のstateとactionを受け取り、新しいstateを返す関数
const todo = (state?:TodoState, action?: Actions) => {
  switch (action.type) {
    case 'ADD_TODO':
      return new TodoState(action.id, action.text);
    case 'TOGGLE_TODO':
      // actionCreatorに渡したidと一致するtodoのみ処理
      if (state.id !== action.id) {
        return state
      }
      // completedだけを反転
      return  new TodoState(state.id, state.text, !state.completed);
    // それ以外のときはstateを変化させない
    default:
      return state
  }
};

const todos = (state: TodoState[] = [], action: Actions) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ];
    case 'TOGGLE_TODO':
      return state.map((t) =>
        todo(t, action)
      );
    default:
      return state
  }
};

export default todos;

確認

src/app.tsx
// 省略
import { addTodo, toggleTodo } from './actions'

let store = createStore(todo);
store.dispatch(addTodo('Hello React!'));
store.dispatch(addTodo('Hello Redux!'));
store.dispatch(toggleTodo(0))

// 省略

ここの時点のソース

github

2. クリックしてcompletedの値を変える

src/containers/VisibleTodoList.tsx
// 省略
import { toggleTodo } from '../actions';

// 省略

interface IDispatchToProps{
  onTodoClick: Function
}

// 省略

const mapDispatchToProps = (dispatch:Function):IDispatchToProps => {
  return {
    onTodoClick: (id:number) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList);

export default VisibleTodoList;
src/components/TodoList.tsx
// 省略

interface IProps extends React.Props<TodoList> {
    todos: TodoState[];
    onTodoClick: Function;
}

class TodoList extends React.Component<IProps, {}> {
  render(): JSX.Element{
    return (
      <ul>
        {this.props.todos.map((todo) =>
          <Todo
            key={todo.id}
            {...todo}
            onClick={() => this.props.onTodoClick(todo.id)}
          />
        )}
      </ul>
    );
  }
 }

 export default TodoList;

onClick: PropTypes.func.isRequired,を追加したらTodo.prototype.propTypesが通らなくなったので、Todo.tsxもクラスを使う形に変更している。
余談だがこのあたりで、VSCodeでimport * as React from 'react';のコード補完が効いていなかったのを効くようにした。*メモ

src/components/Todo.tsx
import * as React from 'react';
import { Props, EventHandler, MouseEvent, Component} from 'react';

interface IProps extends Props<Todo>{
    onClick: EventHandler<MouseEvent<HTMLElement>>,
    completed: boolean;
    text: string;
}

interface IComponentNameState {};

export default class Todo extends Component<IProps, IComponentNameState> {
  render(): JSX.Element{
    return (<li
      onClick={this.props.onClick} 
      style={{textDecoration: this.props.completed ? 'line-through' : 'none'}}
    >
      {this.props.text}
    </li>
    )
  }
}

ここの時点のソース

github

続き→Redux ExampleのTodo ListをはじめからていねいにをTypescriptで(3)

参考

Redux ExampleのTodo Listをはじめからていねいに(2)
Redux ExampleのTodo ListをはじめからていねいにをTypescriptで(1)
Redux typed actions でReducerを型安全に書く (TypeScriptのバージョン別)
VSCodeでTypescriptの型定義ファイルを設定したときのメモ

hibohiboo
Javascriptが好きです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした