素のJavaScript (ES6)でTodoアプリを作る、ついでにReactのrenderっぽい動きを再現する

完成品

f:id:tatsuzaki98:20210307001639g:plain
完成品

コード全文

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <script>
      const state = {
        todoList: [],
        formText: '',
      };

      const render = () => {
        document.getElementById('main').innerHTML = `
          <div>
            <div>
              <input
                type="text"
                onchange="handleEvents({type: 'INPUT_TEXT', event})"
                value="${state.formText}"
              />
              <input type="submit" onclick="handleEvents({type: 'SUBMIT'})"/>️
            </div>
            <hr/>
            <table id="table">
              ${state.todoList.length > 0 ? `
                <thead>
                  <tr>
                    <th/>
                    <th>memo</th>
                    <th>deadline</th>
                    <th/>
                    <th/>
                  </tr>
                </thead>
              ` : ``}
              <tbody>
                ${state.todoList.map((todo, key) => (`
                    <tr>
                      <td>
                        <button onclick="handleEvents({key: ${key}, type: 'CHECK'})">
                          check
                        </button>
                        <span>${todo.isChecked ? '✅' : ''}</span>
                      </td>
                      <td><span>${todo.text}</span></td>
                      <td>
                        <input
                          type="date"
                          onblur="handleEvents({key: ${key}, type: 'CHANGE_DATE', event})"
                          value="${todo.deadlineDate}"
                        />
                      </td>
                      <td>
                        <input
                          type="time"
                          onchange="handleEvents({key: ${key}, type: 'CHANGE_TIME', event})"
                          value="${todo.deadlineTime}"
                        />
                      </td>
                      <td>
                        <button onclick="handleEvents({key: ${key}, type: 'DELETE'})">
                          delete
                        </button>
                      </td>
                    </tr>
                `)).join('')}
              </tbody>
            </table>
            <hr/>
            <div>
              <button onclick="handleEvents({type: 'SORT'})">sort by deadline</button>
            </div>
          </div>
        `;
      };

      const handleEvents = (action) => {
        switch (action.type) {
          case 'INPUT_TEXT':
            state.formText = action.event.target.value;
            break;
          case 'SUBMIT':
            document.activeElement.blur();
            if (state.formText === '') {
              break;
            };
            const date = new Date();
            state.todoList.push({
              text: state.formText,
              isChecked: false,
              createdAt: date.getTime(),
              deadlineDate: '',
              deadlineTime: '',
            });
            state.formText = '';
            break;
          case 'CHECK':
            state.todoList[action.key] = {
              ...state.todoList[action.key],
              isChecked: state.todoList[action.key].isChecked ? false : true,
            };
            break;
          case 'DELETE':
            state.todoList.splice(action.key, 1);
            break;
          case 'CHANGE_DATE':
            state.todoList[action.key].deadlineDate = action.event.target.value;
            break;
          case 'CHANGE_TIME':
            state.todoList[action.key].deadlineTime = action.event.target.value;
            break;
          case 'SORT':
            const parseDateTime = (todo) => (
              todo.deadlineDate ?
                new Date(`${todo.deadlineDate} ${todo.deadlineTime}`) :
                new Date(todo.createdAt)
            );
            state.todoList.sort((before, after) => parseDateTime(before) - parseDateTime(after));
          default:
            break;
        }
        render();
      };

      window.onload = render;
    </script>
  </head>
  <body>
    <div id='main'></div>
  </body>
</html>

説明

表題の通りCDNフレームワーク等は使っていない。

最近はhtmlを書くだけでブラウザが勝手にカレンダーとか時計とかを表示してくれるので素晴らしい。

表題に「Reactのrenderっぽい動き」と書いたが、要点は三つで

  • state: Todoリストなどの状態を保持しておく
  • render: stateを画面に反映させる
  • handleEvents: ユーザーの操作を受け取ってstateを更新し、再度renderする

ということ。

Reactのrenderはもちろんもっと複雑にできているが、状態管理の設計の練習くらいにはなっただろうと思う。