ANt-design-vueのtreeの添削操作方法


1.使用背景
新規プロジェクトではant-design-vueコンポーネントライブラリを使用しています。このコンポーネントライブラリは完全に基礎データと双方向結合されたモードで実現されています。フォームコンポーネントだけが少量の方法を提供しています。したがって、ant-design-vueを使用するときは、データを変更する角度からUI表示効果を切り替える必要があります。しかし、ツリーコントロールa-treeの使用においては、データ駆動だけを考慮すると、体感効果は本当によくないです。
2.現在の痛み
公式ヘルプドキュメントを読むことによって、ツリーコントロールに対してデータをバインディングする必要があります。children,title,key属性を含む大きなオブジェクトを構成する必要があります。このようなオブジェクトは後端を通して、このようなJsonオブジェクトを作成するか、後端を先頭のjson配列にするか、先端ルートは上下関係によって構築されます。データはバインディングされています。成功したら、私たちが欲しいUI効果になります。どこが痛いですか?
  • ツリーロードが成功したら、現在のツリーに同じクラスと下級ノードを追加したいです。どうやって操作すればいいですか?
  • ツリーロードが成功したら、ツリーノードのいずれかを変更したいです。どうやって操作すればいいですか?
  • ツリーロードが成功したら、ツリーノードを削除したいです。どうやって操作すればいいですか?
    以上の操作は、ツリーコントロールを再ロードしないで完成することを要求します。テストを経て、三つの実行可能な方案を整理しました。
  • データ駆動
  • 作用分域スロット
  • ノードイベント

  • 3.データ駆動はツリーノードの添削を実現する。
    私たちはヘルプドキュメントにselectedKeys(.sync)という属性を見つけることができます。syncはこの属性が双方向操作をサポートしていることを示しています。しかし、ここで取得したのはkeyの値だけで、必要なバインディングオブジェクトではありません。したがって、このkey値によってこのオブジェクトを見つける必要があります。このオブジェクトを探すのはかなり気持ちが悪いです。
  • バックエンドが構築されたデータである場合、このツリーデータの中を巡回してこのkey値に対応するオブジェクトを見つける必要があります。トップノードを通して再帰的に検索することが考えられます。しかし、コントロールはすべてレンダリングされました。ノードごとのデータを知っています。なぜ再検索しますか?
  • バックエンドが1つの配列だけに戻ると、これは先ほどこの部分のデータをオブジェクトとして再構築する必要があると述べました。このようにこのオブジェクトを検索するとまた2つのケースに分けられます。
  • a.リストデータと構築されたツリーオブジェクトがクローニングされている場合、リストのオブジェクトのアドレスとツリーの形の同じkey値オブジェクトのアドレスが異なっています。方法1で再構成されたツリーデータを遍歴する必要があります。
    b.リストデータのオブジェクトと構築後のノードが同じオブジェクトアドレスであれば、このリストデータを直接検索して対応するオブジェクトを得ることができる。
    だから、気持ちが悪いところは良い木を作ることにあります。私はこの木を見て、あるノードを探します。あるいは方案bという空間を使って時間を変えます。
    ここでは、データはすでにツリー型のデータフォーマットに構築されていると仮定します。データ駆動を実現するための最初の任務は二つのコア方法を完成する必要があります。
  • は、現在のノードkey値に基づいて、ノードオブジェクトgetTreeDataByKey
  • を検索する。
  • は、現在のノードkey値から親レベルノードchildrenセットgetTreeParentChilds
  • を検索する。
    二つの方法コードはそれぞれ以下の通りです。
    
    // author:herbert date:20201024 qq:464884492
    //   key           
    getTreeDataByKey(childs = [], findKey) {
     let finditem = null;
     for (let i = 0, len = childs.length; i < len; i++) {
      let item = childs[i]
      if (item.key !== findKey && item.children && item.children.length > 0) {
      finditem = this.getTreeDataByKey(item.children, findKey)
      }
      if (item.key == findKey) {
      finditem = item
      }
      if (finditem != null) {
      break
      }
     }
     return finditem
     },
    // author:herbert date:20201024 qq:464884492
    //   key      children  
    getTreeParentChilds(childs = [], findKey) {
     let parentChilds = []
     for (let i = 0, len = childs.length; i < len; i++) {
      let item = childs[i]
      if (item.key !== findKey && item.children && item.children.length > 0) {
      parentChilds = this.getTreeParentChilds(item.children, findKey)
      }
      if (item.key == findKey) {
      parentChilds = childs
      }
      if (parentChilds.length > 0) {
      break
      }
     }
     return parentChilds
    },
    3.1同級ノードを追加
    同じレベルのノードを追加するには、現在選択されているノードの親レベルのchildren配列に追加する必要があります。したがって、追加ノードの難点は、現在選択されているノードのバインディングオブジェクトの親レベルのオブジェクトをどのように見つけるかにあります。ページコードは以下の通りです。
    
    <!-- author:herbert date:20201030 qq:464884492-->
    <a-card style="width: 450px;height:550px;float: left;">
    <div slot="title">
     <h2>    (     )<span style="color:blue">@herbert</span></h2>
     <div>
     <a-button @click="dataDriveAddSame">    </a-button>
     <a-divider type="vertical" />
     <a-button @click="dataDriveAddSub">    </a-button>
     <a-divider type="vertical" />
     <a-button @click="dataDriveModify">  </a-button>
     <a-divider type="vertical" />
     <a-button @click="dataDriveDelete">  </a-button>
     </div>
    </div>
    <a-tree :tree-data="treeData" :defaultExpandAll="true"
      :selectedKeys.sync="selectKeys" showLine />
    <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
    </a-card>
    ページコードから分かるように、2つの属性tree-dataselectedKeysが再ツリーに結合されています。ここではselectedKeysバインディング値を通じて、ツリー形の現在選択されているkey値を取得できます。その後、方法getTreeParentChildsを使用すれば、同級の追加が可能です。したがって、dataDriveAddSameコードの使用については以下のように実現されます。
    
    // author:herbert date:20201030 qq:464884492
    dataDriveAddSame() {
     let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
     parentChilds.forEach(item => console.log(item.title));
     parentChilds.push({
      title: '    ,       ',
      key: new Date().getTime()
     })
    },
    3.2下級を追加する
    上の基礎があれば、下のレベルを追加するのは簡単です。唯一の注意点は、取得したオブジェクトのチルドレン属性が存在しない可能性があります。このとき、私たちは$set方式で属性を追加する必要があります。dataDriveAddSubコードは次のように実現します。
    
    // author:herbert date:20201030 qq:464884492
    dataDriveAddSub() {
     let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
     if (!selectItem.children) {
      this.$set(selectItem, "children", [])
     }
     selectItem.children.push({
      title:     ,     ,
      key: new Date().getTime()
     })
     this.$forceUpdate()
     },
    3.3ノードを変更する
    バインディングオブジェクトを取得することができ、ノード値を変更することも簡単になり、追加したものと同じようにgetTreeDataByKeyを使用して現在のオブジェクトを取得し、値を直接修正することができます。dataDriveModifyコードは次のように実現されます。
    
    // author:herbert date:20201030 qq:464884492
    dataDriveModify() {
     let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
     selectItem.title = '       ,        '
    },
    3.4ノードの削除
    同じレベルの削除と追加は、親レベルのノードchildren配列を見つける必要があります。現在のオブジェクトは親レベルの配列に対応するインデックスを持っています。dataDriveDeleteコードは次のように実現されます。
    
    // author:herbert date:20201030 qq:464884492
    dataDriveDelete() {
     let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
     let delIndex = parentChilds.findIndex(item => item.key == this.selectKeys[0])
     parentChilds.splice(delIndex, 1)
    },
    4.スロット方式によるツリーノードの添削ant-treeのappiにおいて、ツリーノード属性titleタイプは文字列でも良いし、スロットでもいいです。ここでは操作対象を取得したいです。ここでは、スコープスロットを使って、対応するページコードは以下の通りです。
    
    <!-- author:herbert date:20201030 qq:464884492-->
    <a-card style="width: 450px;height:550px;float: left;">
    <div slot="title">
     <h2>    (       )</h2>
     <div>
            ,           <span style="color:blue">@    </span>
     </div>
    </div>
    <a-tree ref="tree1" :tree-data="treeData1" :defaultExpandAll="true" :selectedKeys.sync="selectKeys1" showLine blockNode>
     <template v-slot:title="nodeData">
     <span>{{nodeData.title}}</span>
     <a-button-group style="float:right">
      <a-button size="small" @click="slotAddSame(nodeData)" icon="plus-circle" title="    "></a-button>
      <a-button size="small" @click="slotAddSub(nodeData)" icon="share-alt" title="    "></a-button>
      <a-button size="small" @click="slotModify(nodeData)" icon="form" title="  "></a-button>
      <a-button size="small" @click="slotDelete(nodeData)" icon="close-circle" title="  "></a-button>
     </a-button-group>
     </template>
    </a-tree>
    <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
    </a-card>
    4.1同級を追加する
    スロット方式でオブジェクトを取得すると、現在のノードに対応する属性値であり、浅いコピーである。ソースstring|slot|slot-scopevc-tree\src\TreeNode.jsxは次のようなコードを見つけることができる。
    
    const currentTitle = title;
    let $title = currentTitle ? (
     <span class={`${prefixCls}-title`}>
     {typeof currentTitle === 'function'
      ? currentTitle({ ...this.$props, ...this.$props.dataRef }, h)
      : currentTitle}
     </span>
    ) : (
     <span class={`${prefixCls}-title`}>{defaultTitle}</span>
    );
    このコードからは、datarefが見られます。しかし、公式のヘルプ文書にはこの属性の紹介は全くありません。ソースを見たい学生には福祉としてカウントされていません。コードの面から見ても、デバッグの結果から見ても、スコープを通じて得られたオブジェクトは、親レベルの属性がないので、同級の追加はできません。renderSelectorコードは以下の通りです。
    
    // author:herbert date:20201030 qq:464884492
    slotAddSame(nodeItem) {
    console.log(nodeItem)
    this.$warn({ content: "      ,       ,    !    ,       " })
    },
    4.2下級を追加する
    オブジェクトを入手しましたが、コピーだけですので、slotAddSameをセットしても無駄です。
    
    // author:herbert date:20201030 qq:464884492
    slotAddSub(nodeItem) {
    if (!nodeItem.children) {
     console.log('         ,         ')
     this.$set(nodeItem, "children", [])
    }
    nodeItem.children.push({
     title: this.addSubTitle,
     key: new Date().getTime(),
     scopedSlots: { title: 'title' },
     children: []
    })
    },
    4.3ノードの変更
    修正も同じですが、上にchildrenと書いてあります。ここで簡単に使って、修正titleの値を実現できます。
    
    // author:herbert date:20201030 qq:464884492
    slotModify(nodeItem) {
     console.log(nodeItem)
     console.log('nodeItem     Treenode           ,    Title   ')
     nodeItem.title = '         ,         '
     //       dataRef   
     nodeItem.dataRef.title = nodeItem.title
     },
    4.4ノードを削除する
    明らかに削除してもいけません。
    
    // author:herbert date:20201030 qq:464884492
    slodDelete(nodeItem) {
    console.log(nodeItem)
    this.$warn({ content: "      ,       ,    !   ,         " })
    delete nodeItem.dataRef
    },
    5.ツリー型イベントをdataRefと組み合わせて実現する
    上の方はスロット方式を通して、修正機能を実現しました。がっかりしましたか?でも、設計の角度から考えてみます。相手にカスタマイズをしてあげるだけでいいです。公式アプリを読み続けて、事件の中のdataRef事件の提供値を見つけて、また私達に大きな空間を与えました。どれぐらいの大きさがありますか?私達はソースコードを見に行きます。まず、私達はselectのトリガを見つけました。selectファイルの中で、具体的なコードは以下の通りです。
    
    onSelect(e) {
     if (this.isDisabled()) return;
     const {
      vcTree: { onNodeSelect },
     } = this;
     e.preventDefault();
     onNodeSelect(e, this);
    },
    コードからはcomponents\vc-tree\src\TreeNode.jsx onselectが表示されます。実はTreeNodeの中のonNodeSelectedメソッドを呼び出しています。私たちはTreeに対応するコードを見つけました。
    
    onNodeSelect(e, treeNode) {
     let { _selectedKeys: selectedKeys } = this.$data;
     const { _keyEntities: keyEntities } = this.$data;
     const { multiple } = this.$props;
     const { selected, eventKey } = getOptionProps(treeNode);
     const targetSelected = !selected;
     // Update selected keys
     if (!targetSelected) {
      selectedKeys = arrDel(selectedKeys, eventKey);
     } else if (!multiple) {
      selectedKeys = [eventKey];
     } else {
      selectedKeys = arrAdd(selectedKeys, eventKey);
     }
    
     // [Legacy] Not found related usage in doc or upper libs
     const selectedNodes = selectedKeys
      .map(key => {
      const entity = keyEntities.get(key);
      if (!entity) return null;
    
      return entity.node;
      })
      .filter(node => node);
    
     this.setUncontrolledState({ _selectedKeys: selectedKeys });
    
     const eventObj = {
      event: 'select',
      selected: targetSelected,
      node: treeNode,
      selectedNodes,
      nativeEvent: e,
     };
     this.__emit('update:selectedKeys', selectedKeys);
     this.__emit('select', selectedKeys, eventObj);
    },
    2つの方法に関連して、TreeノードeventObjオブジェクトからコンポーネントcomponents\vc-tree\src\Tree.jsxがTreeノードをTree Nodeキャッシュデータselectだけでなく、実際に対応するTreeNodeノードselectedNodesをレンダリングすることができます。このnode属性があれば、対応するノードの上下関係を得ることができます。
    次に、これを説明します。文書には現れていないnodeはどんな鬼ですか?
    ファイルdataRefが見つかったのは、対応するcomponents\tree\Tree.jsx関数の中で、Treeがvc-treeコンポーネントにrender属性を伝達する必要があることを知ることができます。最終的に使用した転送ノードデータもこの属性名です。二つのキーコードは以下の通りです。
    
    render(){
     ...
     let treeData = props.treeData || treeNodes;
     if (treeData) {
      treeData = this.updateTreeData(treeData);
     }
     ...
     if (treeData) {
      vcTreeProps.props.treeData = treeData;
     }
     return <VcTree {...vcTreeProps} />;
    }
    上のコードから見れば、コンポーネントの下の呼び出し方法treeDataは、私たちが入ってきたデータを処理しました。この方法のキーコードは以下の通りです。
    
    updateTreeData(treeData) {
     const { $slots, $scopedSlots } = this;
     const defaultFields = { children: 'children', title: 'title', key: 'key' };
     const replaceFields = { ...defaultFields, ...this.$props.replaceFields };
     return treeData.map(item => {
      const key = item[replaceFields.key];
      const children = item[replaceFields.children];
      const { on = {}, slots = {}, scopedSlots = {}, class: cls, style, ...restProps } = item;
      const treeNodeProps = {
      ...restProps,
      icon: $scopedSlots[scopedSlots.icon] || $slots[slots.icon] || restProps.icon,
      switcherIcon:
       $scopedSlots[scopedSlots.switcherIcon] ||
       $slots[slots.switcherIcon] ||
       restProps.switcherIcon,
      title:
       $scopedSlots[scopedSlots.title] ||
       $slots[slots.title] ||
       restProps[replaceFields.title],
      dataRef: item,
      on,
      key,
      class: cls,
      style,
      };
      if (children) {
      // herbert 20200928             
      if (this.onlyLeafEnable === true) {
       treeNodeProps.disabled = true;
      }
      return { ...treeNodeProps, children: this.updateTreeData(children) };
      }
      return treeNodeProps;
     });
     },
    }
    この方法から私達はupdateTreeData属性でtreeNodeProps属性を見つけました。その値はdataRefに入力されたデータ項目です。この属性は双方向バインディングをサポートしています。このtreeDataは最終的にtreeNodePropsにレンダリングされます。
    この二つの知识を明らかにしたら、私たちがやるべき操作は简単になります。イベントドライバページのコードは以下の通りです。
    
    <!-- author:herbert date:20201101 qq:464884492 -->
    <a-card style="width: 450px;height:550px;float: left;">
     <div slot="title">
      <h2>    (  dataRef)<span style="color:blue">@464884492</span></h2>
      <div>
      <a-button @click="eventAddSame">    </a-button>
      <a-divider type="vertical" />
      <a-button @click="eventAddSub">    </a-button>
      <a-divider type="vertical" />
      <a-button @click="eventModify">  </a-button>
      <a-divider type="vertical" />
      <a-button @click="eventDelete">  </a-button>
      </div>
     </div>
     <a-tree :tree-data="treeData2" @select="onEventTreeNodeSelected" :defaultExpandAll="true" :selectedKeys.sync="selectKeys2" showLine />
     <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
    </a-card>
    イベントを通じて駆動されるなら、まず該当イベントを登録しなければなりません。コードは以下の通りです。
    
    // author:herbert date:20201101 qq:464884492 
    onEventTreeNodeSelected(seleteKeys, e) {
     if (e.selected) {
      this.eventSelectedNode = e.node
      return
     }
     this.eventSelectedNode = null
    },
    イベントでは、現在の選択TreeNodeを保存しています。その後の追加修正を削除しやすいです。
    5.1同級を追加
    vue仮想domを利用して、親レベルを見つけます。
    
    // author:herbert date:20201101 qq:464884492 
    eventAddSame() {
     //     
     let dataRef = this.eventSelectedNode.$parent.dataRef
     if (!dataRef.children) {
      this.$set(dataRef, 'children', [])
     }
     dataRef.children.push({
      title: '      ,    ',
      key: new Date().getTime()
     })
     },
    5.2下級を追加する
    直接オブジェクトcomponents\vc-tree\src\TreeNode.jsxを使用して、dataRefchildren方法を使用することに注意してください。
    
    // author:herbert date:20201101 qq:464884492
    eventAddSub() {
     let dataRef = this.eventSelectedNode.dataRef
     if (!dataRef.children) {
      this.$set(dataRef, 'children', [])
     }
     dataRef.children.push({
      title: '    ,    bug    ',
      key: new Date().getTime(),
      scopedSlots: { title: 'title' },
      children: []
     })
     }, 
    5.3ノードの変更
    直接修正$setの対応する値を修正する。
    
    // author:herbert date:20201101 qq:464884492 
    eventModify() {
     let dataRef = this.eventSelectedNode.dataRef
     dataRef.title = '    ,     bug'
     },
    5.4ノードを削除する
    vue仮想domによって親レベルdataRefを見つけ、dataRefから選択項目を削除すればいいです。
    
    // author:herbert date:20201101 qq:464884492 
     eventDelete() {
     let parentDataRef = this.eventSelectedNode.$parent.dataRef
     //        
     const children = parentDataRef.children
     const currentDataRef = this.eventSelectedNode.dataRef
     const index = children.indexOf(currentDataRef)
     children.splice(index, 1)
     }
    6.まとめ
    この知識点は、デモから最終的に完成するまでに、前と後の一ヶ月がかかります。ソースを調べたり、テストをしたりして、時間がかかります。でも、この点をはっきり言ってください。価値があると思います。Demoのソースコードが必要なら、下の二次元コードをスキャンしてください。公衆番号に注目してください。childrenを取得します。ant-treeのこのセットのコンポーネントライブラリについては、以前使ったant-desgin-vueのコンポーネントライブラリに比べて、ウェブサイトの展示に適したような感じがします。いくつかのバックグラウンドシステムを作って、大量の操作を提供する必要があります。まだ力がない感じがします。
    ここで、ant-design-vueのtree添削の操作方法についての文章を紹介します。これに関連して、ant-design-vueのtree添削の内容は以前の文章を検索してください。または、下記の関連記事を引き続きご覧ください。これからもよろしくお願いします。