Как ссылаться на метод в родительском компоненте из дочернего компонента с веб-компонентами vanilla JS? (Не какой-либо фреймворк или библиотека)

Вопрос в том, как вызвать метод из дочернего компонента?

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

Это родительский компонент ????

import Nav from './componets/navigation-bar.js'
import Comp from './componets/footer.js'
import UserComp from './componets/user-comp.js'

import Base from './componets/Base.js'

const style = `
    .container {
        display: flex;
        flex-direction: row;
        justify-content: center;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

    .container > user-comp {
        padding: 1em;
    }

`
const content = `
<navigation-bar></navigation-bar>
    <div class="container">
        <user-comp mirror="true">
            <img slot="image" src="https://www.zricks.com/img/UpdatesBlog/44b94c9d-ab13-401d-9e5b-86a00f9da6496%20Must%20Follow%20Tips%20to%20Market%20a%20Luxury%20Home.jpg" alt="Image"></img>
            <h1 slot="title">Rent or Lease your own property</h1>
        </user-comp>
        <user-comp mirror="true">
            <img slot="image" src="https://s3.amazonaws.com/clients.granalacantadvertiser.images/wp-content/uploads/2017/06/14072232/2236775
import Base from './Base.js'

const style = `
    header {
        position: absolute;
        top:0;
        left:0;
        right:0;
        background-color: #111111;
        color: #eeeeee;
        z-index:1;
    }
    
    .logo {
        margin-left: 2em;
    }

    nav {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
    }

    #login-button {
        height: 2.5em;
        width: 10em;
        margin: auto 2em;
        text-transform: uppercase;
        color: #eeeeee;
        background-color: #239710;
        border: none;
        box-shadow: 1px 1px 5px 1px rgba(23,97,10,0.64);
        outline: none;
        cursor: pointer;
        transition: 0.4s;
    }
    
    #login-button:hover {
        background-color: #34a832;
    }

`
const content = `
    <header>
        <nav>
            <h3 class="logo">Homey</h3>
            <button id="login-button"> login </button>
        </nav
    </header>
`

export default class Nav extends Base {
    constructor() {
        super()
        this.render(style, content)
        this.attachShadow({ mode: 'open' })
        this.shadowRoot.appendChild(this.template.content.cloneNode(true))
    }

    connectedCallback() {
        this.shadowRoot
            .querySelector('#login-button')
            .addEventListener('click', clicked())
    }
}
window.customElements.define('navigation-bar', Nav)
O.jpg" alt="Image"></img> <h1 slot="title">Looking for a place</h1> </user-comp> </div> <footer-c></footer-c> ` export default class UI extends Base { constructor() { super() this.render(style, content) this.attachShadow({ mode: 'open' }) this.shadowRoot.appendChild(this.template.content.cloneNode(true)) } clicked = () => { console.log('clicked') } } window.customElements.define('ui-c', UI) document.querySelector('#root').innerHTML = '<ui-c></ui-c>'

Это дочерний компонент ????

import Base from './Base.js'

const style = `
    header {
        position: absolute;
        top:0;
        left:0;
        right:0;
        background-color: #111111;
        color: #eeeeee;
        z-index:1;
    }
    
    .logo {
        margin-left: 2em;
    }

    nav {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
    }

    #login-button {
        height: 2.5em;
        width: 10em;
        margin: auto 2em;
        text-transform: uppercase;
        color: #eeeeee;
        background-color: #239710;
        border: none;
        box-shadow: 1px 1px 5px 1px rgba(23,97,10,0.64);
        outline: none;
        cursor: pointer;
        transition: 0.4s;
    }
    
    #login-button:hover {
        background-color: #34a832;
    }

`
const content = `
    <header>
        <nav>
            <h3 class="logo">Homey</h3>
            <button id="login-button"> login </button>
        </nav
    </header>
`

export default class Nav extends Base {
    constructor() {
        super()
        this.render(style, content)
        this.attachShadow({ mode: 'open' })
        this.shadowRoot.appendChild(this.template.content.cloneNode(true))
    }

    connectedCallback() {
        this.shadowRoot
            .querySelector('#login-button')
            .addEventListener('click', clicked())
    }
}
window.customElements.define('navigation-bar', Nav)

Это базовый класс был написан мной (на всякий случай) ????

export default class Base extends HTMLElement {
    template = document.createElement('template')

    style(style) {
        if (style === null) return ' '
        return '<style>' + style + '</style>'
    }
    render(style, content) {
        if (content === null) content = ''
        this.template.innerHTML = this.style(style) + content
    }
}

person Dimuthu Lakmal    schedule 19.08.2020    source источник
comment
Вы пытались импортировать родительский компонент внутри дочернего компонента?   -  person AidOnline01    schedule 19.08.2020
comment
@ AidOnline01 Он не сделает ту работу, которую я хотел. Это должно быть похоже на реакцию, но с ванильным JS.   -  person Dimuthu Lakmal    schedule 19.08.2020


Ответы (2)


Вы можете передавать данные из дочернего компонента в родительский компонент с помощью события.

В своем дочернем компоненте вы можете создать настраиваемое событие и запускать его, когда вы хотите вызвать метод родительского компонента.

// create and dispatch the event
var event = new CustomEvent("cat", {
  detail: {
    hazcheeseburger: true
  }
});
obj.dispatchEvent(event);

Затем в родительском компоненте вы можете прослушивать это событие. Как только событие запускается, прослушиватель событий улавливает и действует соответствующим образом. Это будет выглядеть примерно так.

obj.addEventListener("cat", function(e) { process(e.detail) });

Пример взят из веб-документов MDN.

person Suvin Nimnaka Sukka    schedule 19.08.2020

События - отличное решение для предотвращения тесной связи между компонентами. Но требуется некоторая работа.

Иногда вы просто знаете, что вам нужен этот элемент DIV на 3 уровня вверх / вниз по DOM.

ВВЕРХ ДОМА

Стандартный element.closest(selector) проходит по DOM, чтобы найти нужный вам селектор.

https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Но .closest() не избегает shadowDOM

Для этого вам нужно написать рекурсивную closestNode(selector) функцию, которая пересекает все shadowDOM с .getRootNode(), пока не найдет селектор

customElements.define("my-element", class extends HTMLElement {

  closestNode(
    selector, // selector like in .closest()
    start = this, // extra functionality to skip a parent
    closest = (el, found = el && el.closest(selector)) =>
    !el || el === document || el === window 
      ? null // standard .closest() returns null for non-found selectors also
      : found || closest(el.getRootNode().host) // recursion!! break out to parent DOM
  ) {
    return closest(start); // look from start
  }

  connectedCallback() {
    this.attachShadow({
           mode: 'closed'// just to show it works with closed mode
         }).append(document.getElementById(this.nodeName).content.cloneNode(true));

    this.onclick = (evt) => {
      evt.stopPropagation();
      let container = this.closestNode('div');
      let color = evt.target.childNodes[0].nodeValue;
      container.style.background = color;
    }
  }
})
<template id=MY-ELEMENT>
  <style>
    button {
      font: 16px Arial, sans;
      margin:.5em;
    }
    button:hover{
      background:lightgreen;
    }
  </style>
  <button><slot></slot></button>
</template>
<div>
  <my-element>red
    <my-element>green
      <my-element>orange
        <my-element>blue
          <my-element>yellow
            <my-element>hotpink
            </my-element>
          </my-element>
        </my-element>
      </my-element>
    </my-element>
  </my-element>
</div>

ВНИЗ ДОМ

Что-то, что вы хотите предотвратить, но иногда может пригодиться

  const shadowDive = (
          el, 
          selector, 
          match = (m, r) => console.warn('match', m, r)
  ) => {
    let root = el.shadowRoot || el;
    root.querySelector(selector) && match(root.querySelector(selector), root);
    [...root.children].map(el => shadowDive(el, selector, match));
  }
person Danny '365CSI' Engelman    schedule 19.08.2020