TODOアプリに react-beautiful-dnd を追加する

f:id:bamch0h:20190224125024g:plain
Drag and Drop している様子

TODO アプリに ドラッグ&ドロップでタスクを動かせるようにした。

苦労した点1 Typescript と react-beautiful-dnd の相性

react-beautiful-dnd では公式には Typescript をサポートしていません。型定義にはflowtypeを使用しています。 ただ、@types/react-beautiful-dnd はあるようなので今回はそちらを使用しました。バージョンは10.0.3 しかし、この定義ファイルにも問題があるようで、そのままで使うと webpack のビルドでエラーが発生しました。 なので、node_modules/@types/react-beautiful-dnd/index.d.ts の中の React.ReactElement の部分を React.ReactElement<any> に書き換え とりあえずビルドを通して使うことにしました。

苦労した点2 firebase realtime database との連携

上記の画像のように入れ替えを行うためには、移動させたい要素を移動させたい位置に挿入することで行うわけですが、realtime database には要素のインデックスは無く挿入用のAPIはありませんので、set()かupdate()で行う必要があります。set() は単体の値を設定するときの関数で、update() 関数は複数の値の書き込みに適しています。今回は update() 関数のほうが有用そうでしたので、そちらを使いました。一番初めに思いついた作戦は、移動させる要素をremove()で削除して、移動先以降の要素も削除して再度push() と set() で要素を追加する方法だったのですが、コストや処理の複雑さからやめました。二番目に思いついたのは移動元と移動先の間にある要素すべてに対して、値だけをスワップさせる方法です。

f:id:bamch0h:20190224132438p:plain
作戦2 イメージ

  onDragEnd(payload:any) {
    const { dst, src, todos } = payload
    const database:any = this.getDatabase();
    const startAt = dst.index
    const endAt = src.index+1
    var todoAry:any = []

    // 元の位置から移動先の位置までの部分配列を作成する
    // 自身より手前に挿入するか、後に挿入するかで作成方法を
    // 変えている。具体的には、手前に挿入する場合は要素の
    // 手前から部分配列の要素として挿入し、後ろに挿入する
    // 場合は後ろから挿入する。
    // こうすることで、一番初めに移動させたい要素がくるので
    // 後の挿入処理が一元化できる。
    if(dst.index < src.index) {
      for(var i = src.index;i >= dst.index;i--) {
        todoAry.push({...todos[i]})
      }
    } else {
      for(var i = src.index;i <= dst.index;i++) {
        todoAry.push({...todos[i]})
      }
    }

    // 挿入処理
    // 初めの要素を取り出してから、次の要素を順次手前に移動させる
    // 移動させ終わったら、最後の要素に初めの要素を挿入する
    // id 要素だけは移動させてはいけないので、元の要素のidを使用する
    var tmp = {...todoAry[0]}
    for(var i:any = 1;i < todoAry.length; i++) {
      todoAry[i-1] = {...todoAry[i], id: todoAry[i-1].id}
    }
    todoAry[todoAry.length-1] = {
      ...tmp,
      id: todoAry[todoAry.length-1].id,
    }

    const todoRef = database.ref(`todos/${this.uid}`)

    // 更新がかかった要素を update() する
    var updates:any = {}
    todoAry.forEach((val:any) => {
      updates[`/${val.id}`] = val
    })

    todoRef.update(updates)
  }

成果物

成果物は以下のリポジトリにある。

GitHub - bamchoh/react-study at b8d395774638edbf3132ea976410a90193496cba