ReactApps

2019年3月13日

最もシンプルなToDoリストをつくる。

pbqhe-yha

はじめに

ローカル環境でこのサンプルを動かすには以下の手順に従ってください。

$ git close git@github.com:reiwa/reactapps.git
$ cd reactapps
$ yarn
$ yarn workspace todo start

※ $の部分はペーストしないでください。

内容

ここでは最もシンプルな実装のToDoリストを開発します。
ToDoリストではユーザが文字列を入力し、それをタスクとして追加します。タスクは完了したかどうかを表すチェックを付けることができます。最後にチェックの付いたタスクのみにフィルタリングするようにします。

コード

import React, { Fragment, FunctionComponent, useState } from 'react'

interface Todo {
  text: string
  isClosed: boolean
}

const App: FunctionComponent = () => {
  const [isAll, setIsAll] = useState(true)
  const [items, setItems] = useState<Todo[]>([])
  const [text, setText] = useState('')
  const onAdd = () => {
    setItems([...items, { text, isClosed: false }])
    setText('')
  }
  const onClose = (text: string) => () => {
    const index = items.findIndex(item => item.text === text)
    const _items = [...items]
    _items[index].isClosed = !_items[index].isClosed
    setItems(_items)
  }
  const onDelete = (text: string) => () => {
    const index = items.findIndex(item => item.text === text)
    const _items = [...items]
    _items.splice(index, 1)
    setItems(_items)
  }

  return (
    <Fragment>
      <div>
        <input onChange={event => setText(event.target.value)} value={text} />
        <button
          disabled={!text || -1 < items.findIndex(item => item.text === text)}
          onClick={onAdd}
        >
          add
        </button>
      </div>
      <div>
        <button onClick={() => setIsAll(true)}>ALL</button>
        <button onClick={() => setIsAll(false)}>Not Done</button>
      </div>
      <ul>
        {items
          .filter(item => isAll || !item.isClosed)
          .map(item => (
            <li key={item.text}>
              <span>{item.isClosed ? <del>{item.text}</del> : item.text}</span>
              <button onClick={onClose(item.text)}>{'done'}</button>
              <button onClick={onDelete(item.text)}>{'x'}</button>
            </li>
          ))}
      </ul>
    </Fragment>
  )
}

export default App

いくつかポイント

ユーザの入力を状態として扱います。

const [text, setText] = useState('')

タスクはテキストと完了したかどうかの状態を持ちます。

interface Todo {
  text: string
  isClosed: boolean
}

タスクを状態として扱います。空の配列だと型が補完されないのでInterfaceを用いてます。

const [items, setItems] = useState<Todo[]>([])

最後にタスクをフィルタリングしているかどうかを状態として扱います。

const [isAll, setIsAll] = useState(true)

タスクはisAllを用いてフィルタリングできます。

<ul>
  {items
    .filter(item => isAll || !item.isClosed)
    .map(item => (
      <li key={item.text}>
        <span>{item.isClosed ? <del>{item.text}</del> : item.text}</span>
        <button onClick={onClose(item.text)}>{'done'}</button>
        <button onClick={onDelete(item.text)}>{'x'}</button>
      </li>
    ))}
</ul>

タスクを追加するには、現在の配列と新しい値から新しい状態を作ります。

const onAdd = () => {
  setItems([...items, { text, isClosed: false }])
  setText('')
}

タスクを削除するにはToDoから同じテキストをもつタスクを探して削除します。

const onDelete = (text: string) => () => {
  const index = items.findIndex(item => item.text === text)
  const _items = [...items]
  _items.splice(index, 1)
  setItems(_items)
}

タスクを完了の状態にするには、ToDoのisClosedの値を変えます。

const onClose = (text: string) => () => {
  const index = items.findIndex(item => item.text === text)
  const _items = [...items]
  _items[index].isClosed = !_items[index].isClosed
  setItems(_items)
}