Describe the nodes of the react + ant desgin custom tree, rename the nodes, add, delete, etc.

1. Custom Node

        Using the Tree component in ant design , the basic tree component only needs to bind the treeData property to a tree structure value (treeData={treeData}):

        However, since the simple display name can no longer meet the needs here, use the TreeNode processed by treeData to add to the inside of the Tree. The code is as follows, where onSelect is triggered when the tree node is clicked:

  // 点击节点,第一次点击节点是选中,第二次点击同一个节点是取消选中,用keys来判断是否有选中
  const onSelect = (keys, info) => {
    if (keys.length > 0) {
      setSelectNode(info.node);
    } else {
      setSelectNode({});
    }
  };

//...

      <Tree
        style={
   
   { marginTop: "20px" }}
        showLine={false}
        showIcon={true}
        onSelect={onSelect}
      >
        {handleTreeData(treeData)}
      </Tree>

        Get treeData, the format of treeData here is:

      [{
        id:'1',
        name:'所有',
        count:'21',
        suffix:'江苏',
        childNodes:[
          {
            id:'1-1',
            name:'南京',
            count:'21',
            suffix:'',
            childNodes:[]
          }
        ]
      }]

        Obtained from the interface:

  const _getTreeData= async () => {
    setTreeData([]);
    try {
      let result = await getTreeData();
      setTreeData(result);
    } catch (error) {
    }
  };

        Next, the author needs to process the data. As can be seen from the above code, the data treeData is processed through the handleTreeData function. Analyze the basic tree structure. The node of Tree is actually TreeNode. For each treeNode node, according to the introduction on the official website , you only need to set its title and key attributes. Generally, the key attribute is the node id of the tree data, and thus recursively Out all TreeNodes:

  //  重写树
  const handleTreeData = (treeData) => {
    return treeData?.map((treeNode) => handleNodeData(treeNode));
  };
  const handleNodeData = (treeNode) => {
    if (treeNode.toString() === "[object Object]") {
        treeNode.title = (
          <div>
            <span className="text-overflow" title={treeNode.name}>{treeNode.name}</span>
            <span>({treeNode.other.count}) _{treeNode.suffix}</span>
          </div>
        );

        return (
            <TreeNode title={treeNode.title} key={treeNode.id}>
                {treeNode?.childNodes?.map((n) => handleNodeData(n))}
            </TreeNode>
        );
    }
    return <TreeNode {...treeNode} />;
  };

        So far, the tree has been displayed according to the author's needs:


 2. Rename the node

        Add a button behind each node. Click the button to switch the node to the edit state. The default is the original node name. According to the above, it is easy to think of adding an edit button to treeNode.title in handleNodeData() and binding rename( ):

<Button onClick={() => rename()} size={"small"}>重命名</Button>

In addition, the attributes of isEdit and defaultValue (used to use the original node name after canceling the rename)         need to be added to each node . If isEdit is true, it indicates the edit state, otherwise the node is displayed normally . Initialize data and set isEdit of all nodes to false. defaultValue is the value of name .

  // 设置不可编辑
  const setAllNotEdit = (arr) => {
    let data = [].concat(arr);
    data.forEach((val) => {
      val.isEdit = false;
      if (val.childNodes && val.childNodes.length) {
        setAllNotEdit(val.childNodes);
      }
    });
    return data;
  };
  // 查询组织树
  const _getTreeData= async () => {
    //...
      let data = setAllNotEdit(result);
      setTreeData(data);
    //...
  };

        Click rename to trigger rename(): find the node data in the tree data (define the deepTree function to find the target node data, and change the value of the key named the third input parameter to the fourth input parameter ), change isEdit to true. Make separate node definitions for edit state and normal state in handleNodeData:

  const deepTree = (arr, key, keyName, value, otherValue) => {
    let data = [].concat(arr);
    for (let i = 0; i < data.length; i++) {

      if (data[i].id === key) {
        data[i][keyName] = value;
      } else if (typeof otherValue === "boolean") {
        data[i][keyName] = otherValue;
      }
      if (data[i].childNodes && data[i].childNodes.length) {
        deepTree(data[i].childNodes, key, keyName, value, otherValue);
      }
    }
    return data;
  };
 
 // 重命名
  const rename = () => {
    if (selectNode && selectNode.key) {
      let data = deepTree(treeData, selectNode.key, "isEdit", true, false);
      setTreeData(data);
    } else {
      message.warning("请选择节点");
    }
  };

//...

const handleNodeData = (treeNode) => {
    if (treeNode.toString() === "[object Object]") {
      if (treeNode.isEdit) {
        treeNode.title = (
          <div>
            <input value={treeNode.name} onChange={(e) => { changeNodeName(e,treeNode.id); }}/>
            ({treeNode.count})
            _{treeNode.suffix}
            <Button onClick={() => { saveTreeNode(treeNode); }} size={"small"} type="link" >确定</Button>
            <Button onClick={() => { cancelRename(treeNode); }} size={"small"} type="link" >取消</Button>
          </div>
        );
      } else {
            //...
        }
        //...
    }
    return <TreeNode {...treeNode} />;
};

        At this point, when the rename button is clicked, the node has changed to the editing state, and there are two buttons of OK and Cancel after the input. When a new name is entered in the input, changeNodeName () is triggered. Without this step, the value of the input cannot be modified (because the node name is always bound, and the node name has not changed):

  // 修改节点名称
  const changeNodeName = (e, key) => {
    let data = deepTree(treeData, key, "name", e.target.value);
    setTreeData(data);
  };

        When you click Cancel, it means to cancel the renaming and use the original name. Find the current node in the node data, and modify the value to the previous value (we have saved this value in defaultValue):

  // 取消修改节点名称
  const cancelRename = (treeNode) => {
    let dataHasReset = deepTree(
      treeData,
      treeNode.id,
      "name",
      treeNode.defaultValue
    );
    let data = setAllNotEdit(dataHasReset);
    setTreeData(data);
  };

        Click OK to modify the node name.

  • If you only need to update the page at this time , you only need to find the node in the node data and update the defaultValue:
  const saveTreeNode = (treeNode) => {
    let dataHasChangeDefaultVal = deepTree(
      treeData,
      treeNode.id,
      "defaultValue",
      treeNode.name
    );
    let data = setAllNotEdit(dataHasChangeDefaultVal);
    setTreeData(data);
  };
  • If you call the interface to update the node, you only need to call the interface, and reload the tree after the interface succeeds:
  // 保存修改的节点名称
  const saveTreeNode = async (treeNode) => {
    try {
      await updateNode({
        //...
      });
      _getTreeData();
    } catch (e) {
    }
  };

        In the Tree control, clicking a node once means selecting the current node, and clicking it again means canceling the selection, but when switching to the edit state, we may click multiple times. In order to prevent data loss, modify onSelect as follows (where dataref is TreeNode’s props):

  // 点击节点
  const onSelect = (keys, info) => {
    if (keys.length > 0 || info.node?.dataRef?.isEdit) {
      setSelectNode(info.node);
    } else {
      setSelectNode({});
    }
  };

const formatNodeData = (treeNode) => {
    if (treeNode.toString() === "[object Object]") {
      //...
      return (
        <TreeNode title={treeNode.title} key={treeNode.id} dataRef={treeNode}>
          {treeNode?.childNodes?.map((d) => formatNodeData(d))}
        </TreeNode>
      );
    }
    return <TreeNode {...treeData} />;
})

        After renaming the selected node, although the tree is a new tree, the selected state of the node and the data of the selected node are saved. In order to prevent misunderstandings caused by data out of sync, here the author gets a new When the tree is displayed, the selected state will be removed, and the selected data will also be empty . Only setSelectNode({}) is needed to empty the selected data . However, removing the selected state requires the use of another attribute of the Tree control: selectedKeys , which indicates the selected node. When this attribute is added, when the node is clicked, the bound value needs to be updated as well:

  // 点击节点
  const onSelect = (keys, info) => {
    setSelectedKeys(keys);
    //...
  };

//...
      <Tree
        showLine={false}
        showIcon={true}
        defaultExpandAll={true}
        onSelect={onSelect}
        selectedKeys={selectedKeys}
      >
        {formatTreeData(treeData)}
      </Tree>

        So far, renaming node names has been implemented.


3. Add new nodes

        The author adds child nodes here, and sibling nodes are similar, so I won’t repeat them here.

        The so-called new nodes are actually processing data in a tree structure.

  • If you only need to update the page at this time, add an object to the childNodes of the currently selected node, so find the selected node recursively and push a new node:
  const onAdd = (arr) => {
    let data = [].concat(arr);
    data.forEach((item) => {
      if (item.id === selectNode.key) {
        if (!item.childNodes) {
          item.childNodes = [];
        }
        item.childNodes.push({
          name: "新节点",
          defaultValue: "新节点",
          id: selectNode.key + Math.random(100),
          suffix:'',
          count:'',
          isEditable: false,
          childNodes: [],
        });
        return;
      }
      if (item.childNodes) {
        onAdd(item.childNodes);
      }
    });
    return data;
  };
  const addNode = () => {
    if (selectNode && selectNode.key) {
        let data = onAdd(treeData);
        setTreeData(data);
    } else {
      message.warning("请选择节点");
    }
  };
  • If you call the interface to update the node, you only need to call the interface, and reload the tree after the interface succeeds:
  // 添加下级
  const addNode = async () => {
    if (selectNode && selectNode.key) {
        try {
          let result = await addNode({
            //...
          });
          _getTreeData();
        } catch (e) {
        }
    }
  };

        So far, the new node has been implemented.


4. Delete node

        As opposed to adding a node, deleting a node is to find the selected node in the data and delete the element from childNodes. Also start from two scenarios:

  • If you only need to update the page at this time, delete an object in the childNodes of the currently selected node, so find the selected node recursively, just splice:
  const onDelete = (arr) => {
    arr.forEach((item, index) => {
      if (item.id === selectNode.key) {
        arr.splice(index, 1);
        return;
      }
      if (item.childNodes) {
        onDelete(item.childNodes);
      }
    });
    return arr;
  };
  const delNode = () => {
    if (selectNode && selectNode.key) {
        let data = onDelete(treeData);
        setTreeData([].concat(data));
        setSelectNode({});
    } else {
      message.warning("请选择节点");
    }
  };
  • If you call the interface to update the node, you only need to call the interface, and reload the tree after the interface succeeds:
  // 删除节点
  const delNode = () => {
    if (selectNode && selectNode.key) {
      try {
        let result = await deleteNode({
           //...
        });
        _getTreeData();
      } catch (e) {}
    } else {}
  };

        So far, deleting nodes has been implemented.


Summarize

        This article details the custom nodes for the tree control of react + ant design, as well as the addition, deletion and modification of nodes. If you have any suggestions, please advise~

Guess you like

Origin blog.csdn.net/sxww_zyt/article/details/129927356