如何使用普通 JS Web 组件从子组件引用父组件中的方法? (不是任何框架或库)
How to reference to a method in parent component from child component with vanilla JS Web Components? (Not any framework or Library)
问题是,如何从子组件调用方法?
例如:假设我在父组件中有一个登录表单组件。所以我需要在单击登录按钮时显示该表单。显示登录表单的函数将写在父组件中,我需要在单击位于子组件中的按钮时调用该函数。
这是父组件
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_2_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)
这是基础class是我写的(以防万一看懂)
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
}
}
您可以使用事件将数据从子组件传递到父组件。
在您的子组件中,您可以创建自定义事件并在您想要调用父组件的方法时启动它。
// 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 web docs。
事件是防止组件之间紧密耦合的一个很好的解决方案。但需要一些工作。
有时您只知道您需要 DIV 元素 3 级别 up/down DOM
UP DOM
标准 element.closest(selector)
“遍历”DOM 以找到您要查找的选择器。
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
但是.closest()
不会逃离阴影DOM
为此,您必须编写一个递归 closestNode(selector)
函数,该函数将所有影子 DOM 与 .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>
向下DOM
你想阻止的事情,但有时会派上用场
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));
}
问题是,如何从子组件调用方法?
例如:假设我在父组件中有一个登录表单组件。所以我需要在单击登录按钮时显示该表单。显示登录表单的函数将写在父组件中,我需要在单击位于子组件中的按钮时调用该函数。
这是父组件
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_2_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)
这是基础class是我写的(以防万一看懂)
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
}
}
您可以使用事件将数据从子组件传递到父组件。
在您的子组件中,您可以创建自定义事件并在您想要调用父组件的方法时启动它。
// 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 web docs。
事件是防止组件之间紧密耦合的一个很好的解决方案。但需要一些工作。
有时您只知道您需要 DIV 元素 3 级别 up/down DOM
UP DOM
标准 element.closest(selector)
“遍历”DOM 以找到您要查找的选择器。
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
但是.closest()
不会逃离阴影DOM
为此,您必须编写一个递归 closestNode(selector)
函数,该函数将所有影子 DOM 与 .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>
向下DOM
你想阻止的事情,但有时会派上用场
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));
}