5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React & Redux & Typescript デビューメモ _φ(・・

Last updated at Posted at 2018-12-02

※注意: 覚えたてのデビューメモなので、間違った解釈があるかもしれません。参考までに。
またお気づきの点あればコメントいただけたら幸いです✨

Topics

  • なぜこの組み合わせで作ろうと思ったか
  • Typescriptのメリット
  • Ruby感覚の自分が苦しんだRedux理解まとめ
  • ReactRedux周りのディレクトリ構造の現時点で思いつく限りのベスト考えてみた

なぜこの組み合わせで作ろうと思ったか

  • 作りたいツールサービスの実現手段を手に入れるため。

  • 自由度の高いJSを特定のルールの中で開発することで、Railsのようなチーム開発のしやすさ運用保守の可能性を実感したい


Typescriptのメリット(感想)

  • 必要な引数渡し忘れがすぐ気づける
  • 型ルールがしっかりしているので、Validation 的な考慮が渡された先で心配にならない。
  • 自由度が高すぎるJS内での統一
  • ReactComponentの可読性が高まる

具体例:

Before:jsx

  • パット見、propsにどんなものが渡ってくるか分かりづらい
スクリーンショット 2018-11-30 18.24.06.png

After:tsx

  • どんな propsが渡ってくるか明確
  • React.Component じゃなくて React.SFC でかけることに気づけた
スクリーンショット 2018-11-30 18.26.03.png

Redux理解まとめ

キーワードから

React&Redux キーワード 自分なり解釈メモ
render ReactDom.render(描画したいコンポーネント, 描画する場所)
component ここでいうコンポーネントはビューのパーツをreactで作っている部分。 React.Compornent<Props, State> と stateが要らない React.SFC<Props> がある
conteiner ReactのコンポーネントとReduxのロジックをつなぐ場所。
dispatch actionをreducerに送り実行する
reducer statusとaction情報を受け取って新しいstatusを返す。actionを送るdispach実行時に呼ばれる。
store Reduxで管理するデータ。 createStore(reducer, initState) でreducerを渡してつなげる。 ※createStoreはこの記事が分かりやすい
action stateを更新するケース名の定義みたいなもの。ここで定義される type名によって、どのstateを返すかをreducer側で判別する。
state データオブジェクトの定義。(感覚としてはDBのfield定義に似てる気がする)
props react周りの話では、主にコンポーネントで受け取る引数

コード☓図から

React Redux 図解

コードから

※サンプルコードは、TodoList簡易版@Typescript+React+Redux - Qiita を参考に一部、変えたものを使わせて頂いています🙇‍♀️

package.json

自分が試した環境のpackage.jsonです。

package.json
{
  "name": "reatas",
  "private": true,
  "dependencies": {
    "@babel/preset-react": "^7.0.0",
    "@rails/webpacker": "https://github.com/rails/webpacker",
    "@types/node": "^10.12.9",
    "@types/react": "^16.7.7",
    "@types/react-dom": "^16.0.10",
    "@types/react-redux": "^6.0.10",
    "@types/webpack-env": "^1.13.6",
    "file-loader": "^2.0.0",
    "import-glob-loader": "^1.1.0",
    "prop-types": "^15.6.2",
    "rails-ujs": "^5.2.1",
    "react": "^16.6.1",
    "react-dom": "^16.6.1",
    "react-redux": "^5.1.1",
    "react_ujs": "^2.4.4",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "ts-loader": "^5.3.0",
    "typescript": "^3.1.6"
  },
  "devDependencies": {
    "eslint": "^5.9.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^3.3.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.1.2",
    "eslint-plugin-prettier": "^3.0.0",
    "eslint-plugin-react": "^7.11.1",
    "postcss": "^7.0.5",
    "prettier": "^1.15.2",
    "prettier-stylelint": "^0.4.2",
    "stylelint": "^9.8.0",
    "stylelint-config-standard": "^18.2.0",
    "stylelint-order": "^1.0.0",
    "stylelint-scss": "^3.4.0",
    "tslint": "^5.11.0",
    "tslint-config-airbnb": "^5.11.1",
    "tslint-config-prettier": "^1.16.0",
    "tslint-loader": "^3.5.4",
    "tslint-plugin-prettier": "^2.0.1",
    "tslint-react": "^3.6.0",
    "webpack-dev-server": "^3.1.10"
  }
}

render

コンポーネントをdomに描画する

app/frontend/packs/js/todos.tsx

import * as React from 'react'
import * as ReactDom from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, Store } from 'redux'
import todos from '../../screens/todos/reducers/todos'
import { Todos } from '../../screens/todos/stores/TodoState'
import App from '../../screens/todos/App'

let store: Store<Todos> = createStore(todos, []);

ReactDom.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);


component

ここでいうコンポーネントはビューのパーツをreactで作っている部分。
reactが取り扱うコンポーネントの種類としては主に2つ。

  • React.Compornent<Props, State> stateを扱うもの
  • React.SFC<Props> ・・・「Stateless Function Component」の略でStatelessComponentという型の Type Alias

stateを扱わないならなるべくSFCを積極的に使うようにする。(実際にまだ比較したわけじゃないですが、後者の方が役割が限定的でシンプルで、パフォーマンス的にもいいそう)


▽複数Componentとりまとめ用

app/frontend/screens/todos/components/App.ts

import * as React from 'react'
import TodoList from './containers/TodoList'
import AddTodoButton from './containers/AddTodoButton'

const App: React.SFC = () => {
  return (
    <div>
      <AddTodoButton />
      <TodoList />        
    </div>
  )
}

export default App;



▽Todoリスト用

reactapp/frontend/screens/todos/components/TodoList/index.tsx

import * as React from 'react'
import Todo from '../Todo'
import * as State from '../../stores/TodoState'

export interface TodoListProps {
  todos: State.Todos;
  onTodoClick: (id: number) => void;
}

const TodoList: React.SFC<TodoListProps> = ({ todos, onTodoClick }) => {
  return (
    <ul>
      {todos.map(todo =>
        <Todo
          key={todo.id}
          {...todo}
          onClick={ () => onTodoClick(todo.id)}
        />
      )}
    </ul>
  )
}

export default TodoList

※ 他に必要な Todo、AddTodoButton は省略しています。参考元 にあります


container

ReactのコンポーネントとReduxのロジックをつなぐ場所。

app/frontend/screens/todos/containers/TodoList.ts

import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { toggleTodo, TodoAction } from '../actions'
import TodoList from '../components/TodoList'
import { Todos } from '../stores/TodoState'

interface StateFromProps {
  todos: Todos,
}

const mapStateToProps = (state: Todos) => {
  return {
    todos: state
  };
};

interface DispatchFromProps {
  onTodoClick: (id: number) => void;
}

const mapDispatchToProps = (dispatch: Dispatch<TodoAction>): DispatchFromProps => {
  return {
    onTodoClick: (id: number) => {
      dispatch(toggleTodo(id))
    }
  }
}

export default connect<StateFromProps, DispatchFromProps, {}>(
  mapStateToProps,
  mapDispatchToProps,
)(TodoList);


reducer

statusとaction情報を受け取って新しいstatusを返す。
actionを送るdispach実行時に呼ばれる。
この記事で扱っている例では、dipachはcontainerの中で実行タイミングを指定されている。

app/frontend/screens/todos/reducers/todos.ts
import { Todos } from '../stores/TodoState'
import { TodoActionType, AddTodoAction, TodoAction } from '../actions'

const todos = (state: Todos, action: TodoAction): Todos => {
  switch(action.type) {
    case TodoActionType.ADD_TODO:
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false,
        }
      ];
    case TodoActionType.TOGGLE_TODO:
      return state.map(todo =>
        (todo.id == action.id) ? { ...todo, completed: !todo.completed } : todo
      );

    default:
      return state;
  }
}

export default todos;


action

stateを更新するケース名の定義みたいなもの。
ここで定義される type名によって、どのstateを返すかを
reducer側で判別する。

app/frontend/screens/todos/actions/index.ts
let nextTodoId = 0;

export enum TodoActionType{
  ADD_TODO = 'ADD_TODO',
  TOGGLE_TODO = 'TOGGLE_TODO',
}

export interface AddTodoAction {
  type: TodoActionType.ADD_TODO;
  id: number;
  text: string;
}

export interface ToggleTodoAction {
  type: TodoActionType.TOGGLE_TODO;
  id: number;
}

export type TodoAction = AddTodoAction | ToggleTodoAction;

// ActionCreator
export const addTodo =  (text: string): AddTodoAction => {
  return {
    type: TodoActionType.ADD_TODO,
    id: nextTodoId++,
    text: text,
  }
}

export const toggleTodo =  (id: number): ToggleTodoAction => {
  return {
    type: TodoActionType.TOGGLE_TODO,
    id: id,
  }
}

state

データオブジェクトの定義。
(感覚としてはDBのfield定義に似てる)

app/frontend/screens/todos/stores/TodoState.ts

export interface Todo {
  id: number;
  completed: boolean;
  text: string;
}
export type Todos = Todo[];

ReactRedux周りのディレクトリ構造の現時点で思いつく限りのベスト考えてみた

React&Redux ディレクトリ

※まだ手がつけられていないですが、Atomic Designでやりたい妄想中。
Atomic Design について調べて見た - Qiita
https://qiita.com/yoshimo123/items/302fb3f1698a8db3cf23

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?