Непреднамеренное изменение значения свойства с помощью Array.from() или insertAdjacentElement

#javascript #typescript #class #properties

Вопрос:

Я сталкиваюсь с проблемой, когда свойство моего класса непреднамеренно преобразуется.

 import { Draggable, DragTarget } from '../Models/eventlisteners';
import { HeroValues } from '../Models/responseModels';
import { Util } from './Util';

export class Heroes implements Draggable, DragTarget {
  static instance: Heroes;
  hostElement: HTMLDivElement = document.getElementById(
    'app'
  )! as HTMLDivElement;
  templateElement: HTMLTemplateElement = document.getElementById(
    'tmpl-hero-overview'
  ) as HTMLTemplateElement;
  element: HTMLCollection;

  heroes!: HeroValues;
  imagesLoaded: number = 0;

  constructor() {
    const importedNode = document.importNode(
      this.templateElement.content,
      true
    );
    this.element = importedNode.children as HTMLCollection;
  }

  async retrieveHeroes() {
    const data = await Util.getData(
      'https://api.opendota.com/api',
      '/constants/heroes'
    );
    this.heroes = data;

    for (const key in this.heroes) {
      this.heroes[data[key]['id']] = {
        img: 'https://api.opendota.com'   data[key]['img'],
        agi_gain: data[key]['agi_gain'],
        attack_range: data[key]['attack_range'],
        attack_rate: data[key]['attack_rate'],
        attack_type: data[key]['attack_type'],
        base_agi: data[key]['base_agi'],
        base_armor: data[key]['base_armor'],
        base_attack_max: data[key]['base_attack_max'],
        base_attack_min: data[key]['base_attack_min'],
        base_health: data[key]['base_health'],
        base_health_regen: data[key]['base_health_regen'],
        base_int: data[key]['base_int'],
        base_mana: data[key]['base_mana'],
        base_mana_regen: data[key]['base_mana_regen'],
        base_mr: data[key]['base_mr'],
        base_str: data[key]['base_str'],
        int_gain: data[key]['int_gain'],
        localized_name: data[key]['localized_name'],
        move_speed: data[key]['move_speed'],
        primary_attr: data[key]['primary_attr'],
        projectile_speed: data[key]['projectile_speed'],
        str_gain: data[key]['str_gain'],
        id: data[key]['id'],
      };
    }
  }

  render() {
    for (const key in this.heroes) {
      const img = document.createElement('img');
      img.id = this.heroes[key].id.toString();
      img.classList.add('hero');
      img.onerror = () => this.updateDOM();
      img.onload = () => this.updateDOM();
      img.src = this.heroes[key].img;
      this.element[0].appendChild(img);
    }
    this.configure();
  }

  dragStartHandler(event: DragEvent) {
    event.dataTransfer!.setData('text/plain', (<HTMLElement>event.target).id);
    event.dataTransfer!.effectAllowed = 'copy';
  }

  dragEndHandler(_: DragEvent) {
    console.log('dragend');
  }

  dragOverHandler(event: DragEvent) {
    event.preventDefault();
    (<HTMLElement>event.target).classList.add('droppable');
  }

  dragLeaveHandler(event: DragEvent) {
    (<HTMLElement>event.target).classList.remove('droppable');
  }

  dropHandler(event: DragEvent) {
    event.preventDefault();
    const heroId = event.dataTransfer!.getData('text/plain');
    const img = document.createElement('img');
    img.id = this.heroes[event.dataTransfer!.getData('text/plain')]['id'];
    img.src = this.heroes[event.dataTransfer!.getData('text/plain')]['img'];
    console.log(this.element);
    if ((<HTMLElement>event.target).firstElementChild) {
      (<HTMLElement>event.target).firstElementChild?.remove();
    }
    (<HTMLElement>event.target).appendChild(img);
  }

  configure() {
    (<HTMLInputElement>this.element[0]).addEventListener(
      'dragstart',
      this.dragStartHandler.bind(this)
    );
    (<HTMLInputElement>this.element[0]).addEventListener(
      'dragend',
      this.dragEndHandler.bind(this)
    );
    (<HTMLInputElement>this.element[1].children[0]).addEventListener(
      'dragover',
      this.dragOverHandler.bind(this)
    );
    (<HTMLInputElement>this.element[1].children[0]).addEventListener(
      'dragleave',
      this.dragLeaveHandler.bind(this)
    );
    (<HTMLInputElement>this.element[1].children[0]).addEventListener(
      'drop',
      this.dropHandler.bind(this)
    );
  }

  private updateDOM() {
    this.imagesLoaded  = 1;
    if (this.imagesLoaded === 121) {
      console.log(this.element);
      Array.from(this.hostElement.children).forEach((el) => {
        el.remove();
      });
      console.log(this.element);
      Array.from(this.element).forEach((el) => {
        this.hostElement.insertAdjacentElement('beforeend', el);
      });
      console.log(this.element);
    }
  }

  // private function attach drag/drop listeners

  static getInstance() {
    if (this.instance) {
      return this.instance;
    }
    this.instance = new Heroes();
    return this.instance;
  }
} 

Я сузил проблему до этого кода. this.element это HTMLCollection то, что мне нужно снова использовать на более поздних этапах. В первых 2 console.logs он имеет ожидаемые свойства, определенные в конструкторе заранее. Но после второго цикла forEach он теряет все свои свойства и имеет длину 0.

Я подумал , что, возможно, мне нужно сделать копию this.element , прежде чем передавать ее в цикл forEach. Но это не сработало, или я сделал это хуже. Для этого у меня была переменная со значением Array.from(this.element).slice()

Затем я подумал, что, возможно, Array.from() преобразуется this.element непреднамеренно. Но по состоянию на документы он создает копию начальной цели и не преобразует ее.

Кто-нибудь может мне здесь помочь?

Правка: Я думал, что не копирую весь свой код, так как проблема, похоже, довольно сильно сузилась. Но я могу предоставить больше, если в этом нет ничего плохого.

Комментарии:

1. Простое перемещение элемента в DOM (что вы делаете) может привести к его удалению из коллекции — покажите код, в котором this.element = I want to see what is here

2. @Bravo спасибо за ответ. Я вставил весь код целиком. этот.элемент определен в конструкторе. В нем хранится коллекция HTMLCollection с 2 разделами, которые изначально взяты из шаблона. Я использую его для заполнения изображениями из запроса Apire

3. хорошо… importedNode.children изменится, когда вы будете перемещать детей — попробуйте this.element = Array.from(importedNode.children) сделать снимок детей

4. @Браво Отлично, это сработало. Спасибо! 🙂 если вы не возражаете, не могли бы вы сказать мне, почему это меняется на основе этого? Я думал, что с тех пор, как я определил эти вещи в конструкторе, все остается статичным. Я еще не очень хорошо разбираюсь в классах машинописи..

5. Я объяснил это в своем ответе

Ответ №1:

Воспользуйся

 this.element = Array.from(importedNode.children)
 

таким образом, этот элемент.является моментальным importedNode.children снимком — не имеет значения, куда перемещаются дочерние узлы

Ваша проблема в том, что вы делаете

 Array.from(this.element).forEach....
 

не перестает this.element importedNode.children существовать на самом деле — поэтому любые изменения, такие как перемещение элемента из importedNode.children неподвижных средств this.element , больше не будут иметь этого элемента, поскольку они оба относятся к одному и тому же объекту

элемент.дети динамичны … если вы добавляете или удаляете дочерний элемент, то element.children отражает изменения — очевидно, это полезно

установка this.element=importedNode.children не создает копию, это ссылка на один и тот же объект

И последнее замечание

 this.hostElement.insertAdjacentElement('beforeend', el);
 

удаляет el оттуда, где он есть ( importedNode.children ), и перемещает его в новое локальное местоположение, которое было бы ( this.hostElement.children ) — таким образом, эта операция удаляет элемент из importedNode.children и, следовательно this.element