1.カスタムノード
ant designで Tree コンポーネント を使用すると、基本的なツリー コンポーネントは、treeData プロパティをツリー構造の値 (treeData={treeData}) にバインドするだけで済みます。
ただし、単純な表示名ではニーズを満たせないため、treeData によって処理された TreeNode を使用して Tree 内部に追加します。コードは次のとおりで、ツリー ノードがクリックされたときに onSelect がトリガーされます。
// 点击节点,第一次点击节点是选中,第二次点击同一个节点是取消选中,用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>
TreeData を取得します。ここでの TreeData の形式は次のとおりです。
[{
id:'1',
name:'所有',
count:'21',
suffix:'江苏',
childNodes:[
{
id:'1-1',
name:'南京',
count:'21',
suffix:'',
childNodes:[]
}
]
}]
インターフェースから取得:
const _getTreeData= async () => {
setTreeData([]);
try {
let result = await getTreeData();
setTreeData(result);
} catch (error) {
}
};
次に、作成者はデータを処理する必要がありますが、上記のコードからわかるように、データ TreeData は handleTreeData 関数を通じて処理されます。基本的なツリー構造を分析します。Tree のノードは、実際には TreeNode です。公式 Web サイトの紹介によると、各 TreeNode ノードに対して、title 属性と key 属性を設定するだけです。通常、key 属性は、そのノードのノード ID です。ツリー データ、つまり再帰的にすべての 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} />;
};
これまでのところ、ツリーは作成者のニーズに従って表示されています。
2. ノードの名前を変更します
各ノードの後ろにボタンを追加します ボタンをクリックするとノードが編集状態に切り替わります デフォルトは元のノード名です 上記によると、handleNodeData()のtreeNode.titleに編集ボタンを追加することは容易に考えられますそしてバインディング rename( ):
<Button onClick={() => rename()} size={"small"}>重命名</Button>
さらに、 isEditおよびdefaultValue (名前変更をキャンセルした後に元のノード名を使用するために使用)の属性 を各ノードに追加する必要があります。 isEdit が true の場合は編集状態を示し、そうでない場合はノードは通常表示されます。データを初期化し、すべてのノードの isEdit を false に設定します。defaultValue は 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);
//...
};
rename をクリックして rename() をトリガーします。ツリー データ内のノード データを検索します (ターゲット ノード データを検索するためにdeepTree関数を定義し、 3 番目の入力パラメーターという名前のキーの値を4 番目の入力パラメーターに変更します)。isEdit を次のように変更します。真実。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} />;
};
この時点で名前変更ボタンをクリックするとノードは編集状態となり、入力後にOKとキャンセルの2つのボタンが表示されます。新しい名前が入力に入力されると、changeNodeName () がトリガーされます。この手順がなければ、入力の値は変更できません (ノード名は常にバインドされており、ノード名は変更されていないため)。
// 修改节点名称
const changeNodeName = (e, key) => {
let data = deepTree(treeData, key, "name", e.target.value);
setTreeData(data);
};
「キャンセル」をクリックすると、名前の変更がキャンセルされ、元の名前が使用されます。ノード データ内の現在のノードを見つけて、値を前の値に変更します (この値はdefaultValue に保存しました)。
// 取消修改节点名称
const cancelRename = (treeNode) => {
let dataHasReset = deepTree(
treeData,
treeNode.id,
"name",
treeNode.defaultValue
);
let data = setAllNotEdit(dataHasReset);
setTreeData(data);
};
「OK」をクリックしてノード名を変更します。
- 現時点でページを更新する必要があるだけの場合は、ノード データ内でノードを見つけて、defaultValue を更新するだけで済みます。
const saveTreeNode = (treeNode) => {
let dataHasChangeDefaultVal = deepTree(
treeData,
treeNode.id,
"defaultValue",
treeNode.name
);
let data = setAllNotEdit(dataHasChangeDefaultVal);
setTreeData(data);
};
- インターフェイスを呼び出してノードを更新する場合は、インターフェイスを呼び出して、インターフェイスが成功した後にツリーをリロードするだけです。
// 保存修改的节点名称
const saveTreeNode = async (treeNode) => {
try {
await updateNode({
//...
});
_getTreeData();
} catch (e) {
}
};
Tree コントロールでは、ノードを 1 回クリックすると現在のノードが選択され、再度クリックすると選択が解除されますが、編集状態に切り替えるときに複数回クリックすることがあります。データの損失を防ぐために、onSelect を次のように変更します (ここで、dataref は TreeNode の小道具です):
// 点击节点
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} />;
})
選択したノードの名前を変更すると、ツリーは新規ですが、ノードの選択状態と選択したノードのデータが保存されます。データの同期ずれによる誤解を防ぐため、ここでは作成者が新しいツリーを取得します。ツリーが表示され、選択状態が解除され、選択データも空になります。選択したデータを空にするには、 setSelectNode({})のみが必要です。ただし、選択状態を削除するには、ツリー コントロールの別の属性 (選択されたノードを示すselectedKeys )を使用する必要があります。この属性が追加されるとき、ノードがクリックされるときに、バインドされた値も更新する必要があります。
// 点击节点
const onSelect = (keys, info) => {
setSelectedKeys(keys);
//...
};
//...
<Tree
showLine={false}
showIcon={true}
defaultExpandAll={true}
onSelect={onSelect}
selectedKeys={selectedKeys}
>
{formatTreeData(treeData)}
</Tree>
これまでのところ、ノード名の変更は実装されています。
3. 新しいノードを追加する
著者はここで子ノードを追加しますが、兄弟ノードも同様であるため、ここでは繰り返しません。
いわゆる新しいノードは、実際にはツリー構造でデータを処理します。
- 現時点でページを更新するだけの場合は、現在選択されているノードの childNodes にオブジェクトを追加して、選択されているノードを再帰的に見つけて新しいノードをプッシュします。
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("请选择节点");
}
};
- インターフェイスを呼び出してノードを更新する場合は、インターフェイスを呼び出して、インターフェイスが成功した後にツリーをリロードするだけです。
// 添加下级
const addNode = async () => {
if (selectNode && selectNode.key) {
try {
let result = await addNode({
//...
});
_getTreeData();
} catch (e) {
}
}
};
これまでのところ、新しいノードが実装されています。
4. ノードの削除
ノードの追加とは対照的に、ノードの削除では、データ内で選択したノードを見つけて、childNodes から要素を削除します。また、次の 2 つのシナリオから開始します。
- 現時点でページを更新するだけの場合は、現在選択されているノードの childNodes 内のオブジェクトを削除して、選択されているノードを再帰的に検索し、次のように接続するだけです。
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("请选择节点");
}
};
- インターフェイスを呼び出してノードを更新する場合は、インターフェイスを呼び出して、インターフェイスが成功した後にツリーをリロードするだけです。
// 删除节点
const delNode = () => {
if (selectNode && selectNode.key) {
try {
let result = await deleteNode({
//...
});
_getTreeData();
} catch (e) {}
} else {}
};
これまでのところ、ノードの削除が実装されています。
要約する
この記事では、react + ant デザインのツリー コントロールのカスタム ノードと、ノードの追加、削除、変更について詳しく説明します。