※注意: 覚えたてのデビューメモなので、間違った解釈があるかもしれません。参考までに。
またお気づきの点あればコメントいただけたら幸いです✨
Topics
- なぜこの組み合わせで作ろうと思ったか
- Typescriptのメリット
- Ruby感覚の自分が苦しんだRedux理解まとめ
- ReactRedux周りのディレクトリ構造の現時点で思いつく限りのベスト考えてみた
なぜこの組み合わせで作ろうと思ったか
-
作りたいツールサービスの実現手段を手に入れるため。
-
自由度の高いJSを特定のルールの中で開発することで、Railsのようなチーム開発のしやすさ、運用保守の可能性を実感したい
Typescriptのメリット(感想)
- 必要な引数渡し忘れがすぐ気づける
- 型ルールがしっかりしているので、Validation 的な考慮が渡された先で心配にならない。
- 自由度が高すぎるJS内での統一
- ReactComponentの可読性が高まる
具体例:
Before:jsx
- パット見、propsにどんなものが渡ってくるか分かりづらい
After:tsx
- どんな propsが渡ってくるか明確
-
React.Component
じゃなくてReact.SFC
でかけることに気づけた
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周りの話では、主にコンポーネントで受け取る引数 |
コード☓図から
コードから
※サンプルコードは、TodoList簡易版@Typescript+React+Redux - Qiita を参考に一部、変えたものを使わせて頂いています🙇♀️
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に描画する
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とりまとめ用
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リスト用
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のロジックをつなぐ場所。
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の中で実行タイミングを指定されている。
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側で判別する。
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定義に似てる)
export interface Todo {
id: number;
completed: boolean;
text: string;
}
export type Todos = Todo[];
ReactRedux周りのディレクトリ構造の現時点で思いつく限りのベスト考えてみた
※まだ手がつけられていないですが、Atomic Designでやりたい妄想中。
Atomic Design について調べて見た - Qiita
https://qiita.com/yoshimo123/items/302fb3f1698a8db3cf23