初心者のプログラミング日記

プログラミング初心者の日記

プログラミングに関することを書いていきます。

React+Recoil+TypeScriptでTODOアプリの作成

Recoilが新しくでたので、使ってみました。

まず、フォルダ構成は以下の通り
f:id:nasubiFX:20200610030802p:plain

Recoilのフォルダ構成は上記のようにatomのフォルダを作ってその中に書いていた記事が多かったので、それと同じようにしました。

create-react-appを使ってTypeScriptを導入する場合は以下のコマンドを打ちます。

npx create-react-app フォルダ名 --typescript

Recoilのインストールは以下のコマンドを打ちます。

npm install recoil

とりあえず、環境構築はこれで終わりです。

index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ここは何も変えていません。

App.tsx

import React from 'react';
import './App.css';
import { RecoilRoot } from 'recoil';
import TextForm from './components/TextForm';
import TaskList from './components/TaskList';

const App: React.FC = () => {
  return (
    <RecoilRoot>
      <div>
        <TextForm />
        <TaskList />
      </div>
    </RecoilRoot>
  )
}

export default App;

TextFormでTodoを追加する所を作っていきます。
TaskListでTodoの一覧を作ります。

atom.ts

import { atom } from 'recoil';

export type Item = {
    id: number
    title: string,
    completed: boolean;
}

const initState: Item[] = [];

export const taskState = atom({
    key: "task",
    default: initState
});

今回はInterfaceではなくすべてType aliasで書いています。
一応違いはあるようですが、TypeScriptを勉強し始めて2日なので、今は気にしないことにします。

TextForm.tsx

import React, { useState } from "react";
import { useSetRecoilState } from "recoil";
import { taskState } from '../atoms/atom';

const TextForm: React.FC = () => {
    const [title, setTitle] = useState<string>("");
    const [number, setNumber] = useState<number>(0);
    const setTasks = useSetRecoilState(taskState);

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setTitle(e.target.value)
    }

    const onAdd = () => {
        setNumber(number+1);
        setTasks(t => [...t, { id: number, title, completed: false }]) //titleはtitle:tilteと同じになるので省略できる
        setTitle("")
    }

    return (

        <div>
            <input type="text" value={title} onChange={onChange}></input>
            <button onClick={onAdd}>登録</button>
        </div>
    )
}

export default TextForm;

onChangeのe: React.ChangeEventはおそらくテンプレの書き方なので覚えなくて大丈夫だと思います。
ここではTodoを追加するだけなので書き込み専用である、useSetRecoilStateを使っています。
ここも特に難しいことはやっていないです。

TaskList.tsx

import React from 'react';
import { useRecoilState } from "recoil";
import { taskState, Item } from '../atoms/atom';

const TaskList: React.FC = () => {

    const [tasks, setTasks] = useRecoilState(taskState);

    const isCompleted = (task: Item) => {
        const copytasks = [...tasks];
        const copytask = { ...task };
        copytask.completed = !copytask.completed;
        const index=copytasks.indexOf(task);
        copytasks.splice(index, 1, copytask);
        setTasks(copytasks);
    }

    const onRemove = (id: number) => {
        const copytasks = [...tasks];
        const index=copytasks.findIndex(int=>int.id===id);
        copytasks.splice(index, 1);
        setTasks(copytasks);
    }

    return (
        <div>
            <ul>
                {tasks.map(task => (
                    <li key={task.id}>
                        <input type="checkbox" checked={task.completed} onChange={() => isCompleted(task)}></input>
                        <span style={{ textDecoration: task.completed ? "line-through" : "" }}>
                            {task.title}
                        </span>
                        <button onClick={() => onRemove(task.id)}>削除</button>
                    </li>
                ))}
            </ul>
        </div>
    )
}

export default TaskList;

まず、isCompletedの処理から説明していきます。
copytasksとcopytaskはtasksが読み取り専用なので、一旦コピーしているだけです。
const indexでは.indexOfを使ってtaskが何番目にあるのかを探します。存在しない場合は-1を返します。
copytasks.spliceで変更があったtaskを書き換えます。
あとは、 setTasksでTodoの上書きをして終わりです。

onRemoveでは、引数にとったidを元にfindIndexを使ってそのtaskが何番目にあるのかを特定し、spliceを使ってその要素を取り除くだけです。

感想

RecoilとReduxどちらも使ってみた感想は、Recoilは後発というだけあって、hooksのように使えReduxよりも圧倒的に記述量が少く使いやすいです。
私的には、Recoil推しです。