お酒を飲んで書いてるので文章がひどいかもしれないけどご了承。
最近、色々あり自分が初めて書いたReactのコードを読んでみたら面白かったのでそれについて書いていく
この頃の僕
この頃の僕がどういう人間だったのかを知ってたほうが多分いいので書く
という訳で見ていく
初めて書いた React コード
リポジトリにも書いている通り某社のインターン選考の課題。
初めてでよくある TodoList アプリを作ってる。
機能としては以下の通り
タスクの一覧
タスクの追加
タスクの完了
タスクのフィルタリング
タスクの削除
ローカルストレージに保存
という感じ。
src/App.js
TypeScript じゃないんか!みたいな話になるが、普段は Elm を書いていてビルドには parcel を使っているような人間である。どうすれば TypeScript を使えるかなどわかるわけもない。
import React, { useReducer, useEffect } from "react";
import "./style/App.css";
import Form from "./components/Form.jsx";
import List from "./components/List.jsx";
import Filter from "./components/Filter";
import Reducer, {
init,
INPUT,
POST,
SELECT,
CHECK,
DELETE
} from "./reducer/App";
const getInitTask = () => {
const tasks = localStorage.getItem("tasks");
return tasks ? JSON.parse(tasks) : [];
};
export default () => {
const [state, dispatch] = useReducer(Reducer, init(getInitTask()));
const inputEvent = e => dispatch({ type: INPUT, str: e.target.value });
const enterEvent = e => (e.keyCode === 13 ? dispatch({ type: POST }) : null);
const filterEvent = type => () => dispatch({ type: SELECT, filter: type });
const checkEvent = i => () => dispatch({ type: CHECK, id: i });
const deleteEvent = i => () => dispatch({ type: DELETE, id: i });
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(state.tasks));
}, [state.tasks]);
return (
<div className="App">
<Form onChange={inputEvent} onEnter={enterEvent} value={state.input} />
<Filter onClick={filterEvent} filterType={state.filter} />
<List
checkEvent={checkEvent}
deleteEvent={deleteEvent}
tasks={state.tasks}
filterType={state.filter}
/>
</div>
);
};
はい。TodoList に useReducer を使ってますね。
それよりも見るべきところは filterEvent のあたり。高階関数である。なんで?
id を受け取る関数でいいじゃん。
src/reducer/App.js
コード
import { newTask } from "../model/Task";
export const POST = "POST";
export const INPUT = "INPUT";
export const CHECK = "CHECK";
export const DELETE = "DELETE";
export const SELECT = "SELECT";
export const NONE = "NONE";
export const PROGRESS = "PROGRESS";
export const DONE = "DONE";
const Counter = init => {
let i = init;
return () => i++;
};
export const init = (tasks = [], input = "", filter = NONE) => {
const counter = Math.max(0, ...tasks.map(x => x.id + 1));
return {
tasks,
input,
filter,
idGenerator: new Counter(counter)
};
};
export default (state, action) => {
const { type } = action;
const { tasks, input, idGenerator } = state;
switch (type) {
case POST:
return {
...state,
tasks:
input.length > 0
? tasks.concat(newTask(idGenerator(), input))
: tasks,
input: ""
};
case INPUT:
return {
...state,
input: action.str
};
case CHECK:
return {
...state,
tasks: tasks.map(x =>
x.id === action.id ? { ...x, isDone: !x.isDone } : x
)
};
case DELETE:
return {
...state,
tasks: tasks.filter(x => x.id !== action.id)
};
case SELECT:
return {
...state,
filter: action.filter
};
default:
return state;
}
};
いわゆる ADT のパターンで action を作ってます。 TypeScript じゃないけど型を感じる書き方です。
パッと見は普通なのですが、ここが変だよポイント
idGenerator: new Counter(counter)
なんだこれは。
const Counter = init => {
let i = init;
return () => i++;
};
そう、クロージャでカウントされるという状態を隠蔽して idGenerator を実行するだけで何らかの id が返ってくるようなことをしている。はっきり言ってやり過ぎ。なんならちょっと引く。
数日前に見たときに「は?」ってなってキショってなった。
個人的には idGenerator は、一意な値を返す関数なら何でも良くてそれに今回は Counter を使っているんだぞという命名からにじみ出る思想がとても好きです。
src/components/*
コンポーネント類のコード。ここはちょっと多めなのでコードは載せないのですが、共通している部分があります。
ローカルステートが存在しない
強い意思を感じる。Elm ユーザーという感じがある。コンポーネントはViewを吐き出す純粋関数であるべきという思想がひしひしと感じられるコード。
何がやばいかって、フォームの値すら Reducer にいるところ。クセが強すぎる。
src/components/List.jsx とかなかなかひどいコードがある。
const isDisable = type => isDone => {
switch (type) {
case NONE:
return true;
case DONE:
return isDone;
case PROGRESS:
return !isDone;
default:
return true;
}
};
普通の関数でいいじゃん!!なんで高階関数にするの!!
おわり
2年前に初めて React を書いた僕ですが、今は仕事でバリバリ React を書いてます。
今ではこの意志の強さは無くなってしまいましたが、奥底にあるのかたまに無意味に高階関数を書いたりします。
みなさんも初めて書いた〇〇のコードを見てみると面白いかもしれません。
ではでは