ReduxのサンプルのTree View

### 留意点
- 初心者の覚書です。
- 自分の環境で動くように参考にしたコードを適当に修正している。
- Windows10 64bit , PowerShellなどで動かしている。
- 見栄えを若干よくする為にbootstrap4を利用している。
- 解説はアバウトな言い回しで厳密ではない。誤解を含む可能性あり。

###Treeのデータ管理

ノードのオブジェクトは

```json:ノード
{
id : "ノードの識別ID",
counter: "カウンターの値(初期値0)",
childIds: "子ノードの配列"
}
```
のような形式

Treeは idをプロパティ、値を対応するノードオブジェクトにして管理している。id 0 がルートなので、そこから子を辿ると、木構造になる。

![2018_0615_1434_14.jpg](https://qiita-image-store.s3.amazonaws.com/0/76757/c3a92682-efd9-b423-3f88-1fb48176a566.jpeg)

###Treeの初期化

```javascript:generateTree.js
export default function generateTree() {

  const NUM = 10;

  // treeオブジェクトをルートとなる　id 0　のオブジェクトで初期化
  let tree = {
    0: {
      id: 0,
      counter: 0,
      childIds: []
    }
  }

  for (let i = 1; i < NUM ; i++) {
    //i未満の整数をランダムに取り出し
    // 0以上1未満の乱数を二乗することで、上の方に表示されているノードを
    // 優先的に親ノードとしているか
    let parentId = Math.floor(Math.pow(Math.random(), 2) * i)
    // id が　i のノード作成してtreeに追加
    tree[i] = {
      id: i,
      counter: 0,
      childIds: []
    }
    //idがparentIdの 子ノードに追加
    tree[parentId].childIds.push(i)
  }

  return tree
}

```

###リデューサ

「//★★★ はじめの初期化のとき」というコメントのところについて。
ここの処理の意味がわからなくていろいろ調べてみたら、どうもページ読み込みのときに一度、
このリデューサが実行されているらしく、そのアクションにnodeIdが存在しないため、undefinedのidにundefinedのオブジェクトが登録されてしまうので必要になるらしいと判明。



```javascript:reducers\index.js
import { INCREMENT, ADD_CHILD, REMOVE_CHILD, CREATE_NODE, DELETE_NODE } from '../actions'

//ノードのchildIdsの管理
const childIds = (state, action) => {
  switch (action.type) {
    // id追加
    case ADD_CHILD:
      return [...state, action.childId]
      //id削除
    case REMOVE_CHILD:
      return state.filter(id => id !== action.childId)
    default:
      return state
  }
}

const node = (state, action) => {
  switch (action.type) {
    // 新要素作成
    case CREATE_NODE:
      return {
        id: action.nodeId,
        counter: 0,
        childIds: []
      }
    //インクリメント
    case INCREMENT:
      return {
        ...state,
        counter: state.counter + 1
      }
    //それぞれの場合のchildIdsの変更
    case ADD_CHILD:
    case REMOVE_CHILD:
      return {
        ...state,
        childIds: childIds(state.childIds, action)
      }
    default:
      return state
  }
}
// idがnodeIdのオブジェクトより下層の子ノードのidを配列で取得する関数
//　再帰的な処理に注意
//acc は　accumulatorの略か。配列のバスケット替わり。
const getAllDescendantIds = (state, nodeId) => (
  state[nodeId].childIds.reduce((acc, childId) => (
    [...acc, childId, ...getAllDescendantIds(state, childId)]
  ), [])
)
// ids配列に入っているidのオブジェクトを一括削除してから、stateを返す関数
const deleteMany = (state, ids) => {
  state = { ...state }
  ids.forEach(id => delete state[id])
  return state
}

export default (state = {}, action) => {
  const { nodeId } = action

  //★★★ はじめの初期化のとき
  //type: "@@redux/INITa.v.f.d.s.g" なるアクションが送られてきて
  // nodeIdは undefined であるため、次の処理がないと、
  //stateのノード管理オブジェクトに undefinedプロパティにundefiendの値が
  // 登録されてしまう。
  if (typeof nodeId === 'undefined') {
    return state
  }
// DELETE_NODE はこっち
  if (action.type === DELETE_NODE) {
    const descendantIds = getAllDescendantIds(state, nodeId)
    //stateから自身と下層のノードを一括削除
    return deleteMany(state, [nodeId, ...descendantIds])
  }
// CREATE_NODE, INCREMENT, ADD_CHILD, REMOVE_CHILD などはこっち
  return {
    ...state,
    [nodeId]: node(state[nodeId], action)
  }

}

```

###アクション

すべてのアクションで nodeIdが使われいる。

```javascript:actions\index.js
export const INCREMENT = 'INCREMENT'
export const CREATE_NODE = 'CREATE_NODE'
export const DELETE_NODE = 'DELETE_NODE'
export const ADD_CHILD = 'ADD_CHILD'
export const REMOVE_CHILD = 'REMOVE_CHILD'

export const increment = (nodeId) => ({
  type: INCREMENT,
  nodeId
})

let nextId = 0

//ユーザによって新規ノードの追加
export const createNode = () => ({
  type: CREATE_NODE,
  nodeId: `new_${nextId++}`
})
// id指定でノード削除　その子ノードも孫ノードなども下層は再帰的に削除
export const deleteNode = (nodeId) => ({
  type: DELETE_NODE,
  nodeId
})
// nodeIdの親のchildIdsの配列にchildIdを追加
export const addChild = (nodeId, childId) => ({
  type: ADD_CHILD,
  nodeId,
  childId
})
// 子ノード削除 nodeIdの親のchildIdsから childeIdを削除
export const removeChild = (nodeId, childId) => ({
  type: REMOVE_CHILD,
  nodeId,
  childId
})

```

###Nodeコンテナをつくる

NodeをラップにするConnectedNodeコンポーネントには idとparentIdをownPropsとしてわたす。
ただし、ルートはidだけ。
また、Propsには state[ownProps.id] を渡す。
Nodeコンポーネントの内部では、ConnectedNodeコンポーネントを再帰的に呼び出し。

```jsx:containers\Node.js
import React from 'react'
import { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../actions'

export class Node extends Component {
  // カウンターのインクリメントのクリックイベント
  handleIncrementClick = () => {
    const { increment, id } = this.props
    increment(id)
  }
// 子ノード追加のクリックイベント
  handleAddChildClick = e => {
    e.preventDefault()

    const { addChild, createNode, id } = this.props
    const childId = createNode().nodeId
    // 親ノードのchildIdsに追加
    addChild(id, childId)
  }
//親ノードから指定したidの子ノードを削除
  handleRemoveClick = e => {
    e.preventDefault()

    const { removeChild, deleteNode, parentId, id } = this.props
    //自身が属する親ノードのchildIdsから自身のidを削除
    removeChild(parentId, id)
    //自身のオブジェクトを削除
    deleteNode(id)
  }
// 子ノードを表示する関数
// 自身(Node)をラップするConnectedNodeコンポーネントを再帰的に呼び出し
  renderChild = childId => {
    const { id } = this.props
    return (
      <li key={childId}>
        <ConnectedNode id={childId} parentId={id} />
      </li>
    )
  }

  render() {
    const { id, counter, parentId, childIds } = this.props
    return (
      <div>
        <p className={"d-inline-block bg-info text-white  rounded-left"}>🌺{id}</p>
        <p className={"d-inline-block bg-dark text-white mr-1 px-3 rounded-right"}>{counter}</p>
        <button className={'btn btn-primary mr-1 mb-1 p-0'} onClick={this.handleIncrementClick}>
          増加
        </button>
        {typeof parentId !== 'undefined' &&
          <button className={'btn btn-danger mr-1 mb-1 p-0'} onClick={this.handleRemoveClick}>
            ← 要素の削除
          </button>
        }
        <ul>
          {childIds.map(this.renderChild)}
          <li key="add">
            <p className={"d-inline-block alert alert-success p-0 rounded"} style={{cursor:"pointer"}}　 onClick={this.handleAddChildClick}>🌺子要素追加</p>
          </li>
        </ul>
      </div>
    )
  }
}

function mapStateToProps(state, ownProps) {
  return state[ownProps.id]
}

const ConnectedNode = connect(mapStateToProps, actions)(Node)
export default ConnectedNode

```

###エントリーポイント

```jsx:index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers'
import generateTree from './generateTree'
import Node from './containers/Node'

const tree = generateTree()
const store = createStore(reducer, tree)


console.log(tree);

store.subscribe(() =>
  console.log(store.getState())
);


render(
  <Provider store={store}>
    <Node id={0} />
  </Provider>,
  document.getElementById('root')
)

```

###実行結果

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ReduxのサンプルのTree View <a href="https://t.co/8qgbCtNcGJ">https://t.co/8qgbCtNcGJ</a> <a href="https://twitter.com/YouTube?ref_src=twsrc%5Etfw">@YouTube</a>さんから</p>&mdash; tkarasuma (@tkarasuma) <a href="https://twitter.com/tkarasuma/status/1007504959938543616?ref_src=twsrc%5Etfw">2018年6月15日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


[ソースコード](https://github.com/tkarasuma/JavaScript/tree/master/Redux_Tree_View)

###【2018/06/16　追記】
GitHubに誤ったソースコードがアップロードされていたのを修正