原文链接:https://ayushgp.github.io/htm...
译者:阿里云 - 也树
Web Component 出现有一阵子了。 Google 费了很大力气去推动它更广泛的应用,但是除 Opera 和 Chrome 以外的多数主流浏览器对它的支持仍然不够理想。
但是通过 polyfill,你可以从现在开始构建你自己的 Web Component,你可以在这里找到相关支持:https://www.webcomponents.org/polyfills
在这篇文章中,我会演示如何创建带有样式,拥有交互功能并且在各自文件中优雅组织的 html 标签。
Web Component 是一系列 web 平台的 api,它们可以允许你创建全新可定制、可重用并且封装的 HTML 标签,从而在普通网页及 web 应用中使用。
定制的组件基于 Web Component 标准构建,可以在现在浏览器上使用,也可以和任意与 HTML 交互的 JavaScript 库和框架配合使用。
用于支持 Web Component 的特性正逐渐加入 HTML 和 dom 的规范,web 开发者使用封装好样式和定制行为的新元素来拓展 HTML 会变得轻而易举。
它赋予了仅仅使用纯粹的JS/HTML/css就可以创建可重用组件的能力。如果 HTML 不能满足需求,我们可以创建一个可以满足需求的 Web Component。
举个例子,你的用户数据和一个 ID 有关,你希望有一个可以填入用户 ID 并且可以获取相应数据的组件。HTML 可能是下面这个样子:
<user-card user-id="1"></user-card>
这是一个 Web Component 最基本的应用。下面的教程将会聚焦在如何构建这个用户卡片组件。
HTML 和 DOM 标准定义了四种新的标准来帮助定义 Web Component。这些标准如下:
我们首先需要声明一个类,定义元素如何表现。这个类需要继承 HTMLElement 类,但让我们先绕过这部分,先来讨论定制元素的生命周期方法。你可以使用下面的生命周期回调函数:
在 UserCard 文件夹下创建 UserCard.js:
class UserCard extends HTMLElement {
constructor() {
super();
this.addEventListener('click', e => {
this.toggleCard();
});
}
toggleCard() {
console.log("Element was clicked!");
}
}
customElements.define('user-card', UserCard);
这个例子里我们已经创建了一个定义了定制元素行为的类。customElements.define('user-card', UserCard); 函数调用告知 DOM 我们已经创建了一个新的定制元素叫 user-card,它的行为被 UserCard 类定义。现在可以在我们的 HTML 里使用 user-card 元素了。
我们会用到 https://jsonplaceholder.typicode.com/ 的 API 来创建我们的用户卡片。下面是数据的样例:
{
id: 1,
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496"
}
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org"
}
现在,让我们创建一个将在屏幕上渲染的模板。创建一个名为 UserCard.html 的新文件,内容如下:
<template id="user-card-template">
<div>
<h2>
<span></span> (
<span></span>)
</h2>
<p>Website: <a></a></p>
<div>
<p></p>
</div>
<button class="card__details-btn">More Details</button>
</div>
</template>
<script src="/UserCard/UserCard.js"></script>
注意:我们在类名前加了一个 card__ 前缀。在较早版本的浏览器中,我们不能使用 shadow DOM 来隔离组件 DOM。这样当我们为组件编写样式时,可以避免意外的样式覆盖。
我们创建好了卡片的模板,现在来用 CSS 装饰它。创建一个 UserCard.css 文件,内容如下:
.card__user-card-container {
text-align: center;
display: inline-block;
border-radius: 5px;
border: 1px solid grey;
font-family: Helvetica;
margin: 3px;
width: 30%;
}
.card__user-card-container:hover {
box-shadow: 3px 3px 3px;
}
.card__hidden-content {
display: none;
}
.card__details-btn {
background-color: #dedede;
padding: 6px;
margin-bottom: 8px;
}
现在,在 UserCard.html 文件的最前面引入这个 CSS 文件:
<link rel="stylesheet" href="/UserCard/UserCard.css">
样式已经就绪,接下来可以继续完善我们组件的功能。
现在我们需要定义创建元素并且添加到 DOM 中会发生什么。注意这里 constructor 和 connectedCallback 方法的区别。
constructor 方法是元素被实例化时调用,而 connectedCallback 方法是每次元素插入 DOM 时被调用。connectedCallback 方法在执行初始化代码时是很有用的,比如获取数据或渲染。
小贴士: 在 UserCard.js 的顶部,定义一个常量 currentDocument。它在被引入的 HTML 脚本中是必要的,允许这些脚本有途径操作引入模板的 DOM。像下面这样定义:
const currentDocument = document.currentScript.ownerDocument;
接下来定义我们的 connectedCallback 方法:
// 元素插入 DOM 时调用
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
// 选取模板并且克隆它。最终将克隆后的节点添加到 shadowDOM 的根节点。
// 当前文档需要被定义从而获取引入 HTML 的 DOM 权限。
const template = currentDocument.querySelector('#user-card-template');
const instance = template.content.cloneNode(true);
shadowRoot.appendChild(instance);
// 从元素中选取 user-id 属性
// 注意我们要像这样指定卡片:
// <user-card user-id="1"></user-card>
const userId = this.getAttribute('user-id');
// 根据 user ID 获取数据,并且使用返回的数据渲染
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((response) => response.text())
.then((responseText) => {
this.render(JSON.parse(responseText));
})
.catch((error) => {
console.error(error);
});
}
我们已经定义好了 connectedCallback 方法,并且把克隆好的模板绑定到了 shadow root 上。现在我们需要填充模板内容,然后在 fetch 方法获取数据后触发 render 方法。下面来编写 render 和 toggleCard 方法。
render(userData) {
// 使用操作 DOM 的 API 来填充卡片的不同区域
// 组件的所有元素都存在于 shadow dom 中,所以我们使用了 this.shadowRoot 这个属性来获取 DOM
// DOM 只可以在这个子树种被查找到
this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4>
${userData.address.suite}, <br />
${userData.address.street},<br />
${userData.address.city},<br />
Zipcode: ${userData.address.zipcode}`
}
toggleCard() {
let elem = this.shadowRoot.querySelector('.card__hidden-content');
let btn = this.shadowRoot.querySelector('.card__details-btn');
btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
}
既然组件已经完成,我们就可以把它用在任意项目中了。为了继续教程,我们需要创建一个 index.html 文件,然后写入下面的代码:
<html>
<head>
<title>Web Component</title>
</head>
<body>
<user-card user-id="1"></user-card>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.14/webcomponents-hi.js"></script>
<link rel="import" href="./UserCard/UserCard.html">
</body>
</html>
因为并不是所有浏览器都支持 Web Component,我们需要引入 webcomponents.js 这个文件。注意我们用到 HTML 引用语句来引入我们的组件。
为了运行这些代码,你需要创建一个静态文件服务器。如果你不清楚如何创建,你可以使用像 static-server 或者 json-server 这样的简易静态服务。教程里,我们安装 static-server:
$ npm install -g static-server
接着在你的项目目录下,使用下面的命令运行服务器:
$ static-server
打开你的浏览器并访问localhost:3000,你就可以看到我们刚刚创建的组件了。
还有很多关于 Web Component 的东西没有在这篇短文中写到,我想简单的陈述一些开发 Web Component 的小贴士和技巧。
创建组件时可以使用继承的方式。举个例子,如果想要为两种不同的用户创建一个 UserCard,你可以先创建一个基本的 UserCard 然后将它拓展为两种特定的用户卡片。想要了解更多组件继承的知识,可以查看Google web developers’ article。
我们创建了当元素加入 DOM 后自动触发的 connectedCallback 方法。我们同样有元素从 DOM 中移除时触发的 disconnectedCallback 方法。 attributesChangedCallback(attribute, oldval, newval)方法会在我们改变定制组件的属性时被触发。
既然组件元素是类的实例,就可以在这些类中定义公用方法。这些公用方法可以用来允许其它定制组件/脚本来和这些组件产生交互,而不是只能改变这些组件的属性。
可以通过多种方式定义私有方法。我倾向于使用(立即执行函数),因为它们易写和易理解。举个例子,如果你创建的组件有非常复杂的内部功能,你可以像下面这样做:
(function() {
// 使用第一个self参数来定义私有函数
// 当调用这些函数时,从类中传递参数
function _privateFunc(self, otherArgs) { ... }
// 现在函数只可以在你的类的作用域中可用
class MyComponent extends HTMLElement {
...
// 定义下面这样的函数可以让你有途径和这个元素交互
doSomething() {
...
_privateFunc(this, args)
}
...
}
customElements.define('my-component', MyComponent);
})()
为了防止新的属性被添加,需要冻结你的类。这样可以防止类的已有属性被移除,或者已有属性的可枚举、可配置或可写属性被改变,同样也可以防止原型被修改。你可以使用下面的方法:
class MyComponent extends HTMLElement { ... }
const FrozenMyComponent = Object.freeze(MyComponent);
customElements.define('my-component', FrozenMyComponent);
注意: 冻结类会阻止你在运行时添加补丁并且会让你的代码难以调试。
这篇关于 Web Component 的教程作用非常有限。这可以部分归咎于对 Web Component 的影响很大的 react。我希望这篇文章可以提供给你足够的信息来让你尝试不添加任何依赖来构建自己的定制组件。你可以在 定制组件 API 规范(Custom components API spec) 找到更多关于 Web Component 的信息。
你可以在这里阅读第二部分的教程:使用纯粹的JS构建 Web Component - Part 2!
js对字符串进行编码的方法。ECMAScript v3 反对使用escape方法,用 encodeURI() 和 encodeURIComponent() 替代它。encodeURI对URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的。
创建和管理React组件的各种方式,涌现的大量状态管理工具等等都是这些挑战的焦点。我们今天能做的就是在React(基于社区选择)中将最常用的做法引入桌面并讨论它们。
在开发小程序的时候,我们总是期望用以往的技术规范和语法特点来书写当前的小程序,所以才会有各色的小程序框架,例如 mpvue、taro 等这些编译型框架
在react开发中,经常会遇到组件重复渲染的问题,父组件一个state的变化,就会导致以该组件的所有子组件都重写render,尽管绝大多数子组件的props没有变化
在React中无论是class形式(render函数)还是function形式(return的内容)的组件,最后返回的jsx其实质是React.createElement函数的结果,而React.createElement函数返回的结果是一个对象树,我们可以称之为元素描述树
我们来看一看 Component 和 PureComponent 的区别,我们先从问题出发,通过解决实际的问题来查询出 PureComponent 和 Component 之间区别。这里创建 Greeting 的组件,其中我们用 setInterval 每间隔 2 秒就更新状态title一次
react 的不可变,纯函数。直接导致 hooks 必须使用 const 关键字,不能是 let,这也是 hooks 的奇迹之一;Hooks对Fiber更好 -> Hooks是Fiber的产物 -> 没有Fiber就不是Hooks
前段时间 React 团队发布了一项用于解决 React 页面在多接口请求下的性能问题的解决方案 React Server Components。当然该方案目前还在草案阶段,官方也只是发了视频和一个示例 demo 来说明这个草案。
使用 Vue 3 的 defineAsyncComponent 特性可以让我们延迟加载组件。这意味着它们仅在需要时从服务器加载。这是改善初始页面加载的好方法,因为我们的应用程序将以较小的块加载,而不必在页面加载时加载每个组件。
开发中,还是会遇到需要引入外部CSS到Shadow DOM情况,那么如何处理呢?作者就最近遇到的情况给出如下几种方案。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!