-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
323 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -228,7 +228,7 @@ | |
position: relative; | ||
} | ||
|
||
li::before { | ||
ul > li::before { | ||
position: absolute; | ||
top: 8px; | ||
left: -20px; | ||
|
316 changes: 316 additions & 0 deletions
316
src/markdowns/articles/web-component_user_experience.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
--- | ||
title: Web Components 使用体验 | ||
date: 2024-08-18 02:22:00 | ||
author: yuanyxh | ||
imageUrl: http://qkc148.bvimg.com/18470/bb5bb2c21cc501bf.png | ||
description: 记录第一次使用 Web Component 的感受,总结它的优势与局限性,以及衍生出来的个人想法。 | ||
--- | ||
|
||
<Header frontmatter={frontmatter} /> | ||
|
||
<Toc toc={toc} /> | ||
|
||
## 前言 | ||
|
||
前一段时间公司的 web forms 项目中有几个页面需要修改,老项目大家都懂的,原生 html、jquery 一把梭,组件化、模块化是没有的,改起来有多难受谁改谁知道。为了自己的开发体验及后续考虑,上手了自己以前没用过的 web components,这篇文章就来聊一下个人的使用体验和看法。 | ||
|
||
## Web Components 基础 | ||
|
||
MDN 对于 Web Components 的定义: | ||
|
||
> Web 组件是一套不同的技术,允许您创建可重复使用的自定义元素(其功能与您的其余代码隔离开来)并在您的 Web 应用程序中使用它们。 | ||
他的简单使用: | ||
|
||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Document</title> | ||
</head> | ||
<body> | ||
<script> | ||
{ | ||
// 可复用的模板 | ||
const template = window.document.createElement("template"); | ||
// 内部的 style 选择器只会选择自定义元素内的元素 | ||
template.innerHTML = ` | ||
<style> | ||
input { | ||
padding: 4px 6px; | ||
outline: none; | ||
border: none; | ||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.6); | ||
border-radius: 4px; | ||
} | ||
</style> | ||
<input type="text" /> | ||
`; | ||
// 自定义元素 | ||
class WInput extends HTMLElement { | ||
// 定义需要观察的属性集 | ||
static observedAttributes = ["color", "size"]; | ||
// 实例被构造时调用,比如通过 document.createElement 或书写标签 | ||
constructor() { | ||
super(); | ||
// 将影子 DOM 附加到自定义元素 | ||
this.root = this.attachShadow({ mode: "open" }); | ||
// 将模板拷贝到影子 DOM | ||
this.root.appendChild(template.content.cloneNode(true)); | ||
} | ||
// 被添加到文档时调用 | ||
connectedCallback() {} | ||
// 从文档中删除时调用 | ||
disconnectedCallback() {} | ||
// 移动到新文档时调用 | ||
adoptedCallback() {} | ||
// observedAttributes 属性集中属性值变化时调用 | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
console.log(`Attribute ${name} has changed.`); | ||
} | ||
} | ||
// 定义元素 | ||
window.customElements.define("w-input", WInput); | ||
} | ||
</script> | ||
|
||
<w-input></w-input> | ||
</body> | ||
</html> | ||
``` | ||
|
||
渲染出来的效果: | ||
|
||
<Image url={'http://qkc148.bvimg.com/18470/f2c58e4456bac0f1.png'} width={428} height={200} /> | ||
|
||
关于 Web Components 的完整知识可以看 MDN 官网文档 [Web_Components],这里不再赘述。 | ||
|
||
## 使用体验 | ||
|
||
老项目表单居多,所以基于 Web Components 封装了常用的表单组件,如 Input、Select、AutoComponent 等,带来的好处显而易见,不说兼容性、微前端项目中作为可通用的组件这些不常见的场景,最能直观感受的好处就是可复用、样式统一,只需要封装一次可以多处使用,且具有高内聚的特性,新的功能、修复 bug 可以在组件内部解决。 | ||
|
||
但同时还需要看到他的局限性,理解他与 UI 框架的区别,Web Components 与 React、Vue 这类框架不同,他只提供了组织结构的一种方式,没有 UI 框架的数据驱动、响应式等概念,仍需要编写命令式代码来操作 DOM,这也是开发 Web Components 体验较为不好的一点。 | ||
|
||
使用 [knockoutjs] 可以一定程度上减少手动操作 DOM 的代码。 | ||
|
||
> Knockout 是一个 JavaScript 库,可帮助您使用干净的底层数据模型创建丰富、响应迅速的显示和编辑器用户界面。只要您的 UI 部分需要动态更新(例如,根据用户的操作或外部数据源的更改而更改),KO 都可以帮助您更简单、更易于维护地实现它。 | ||
使用 Knockout 实现一个简单的例子: | ||
|
||
```html | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Document</title> | ||
</head> | ||
<body> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js"></script> | ||
<script> | ||
{ | ||
const template = window.document.createElement('template'); | ||
template.innerHTML = ` | ||
<style> | ||
input { | ||
padding: 4px 6px; | ||
outline: none; | ||
border: none; | ||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.6); | ||
border-radius: 4px; | ||
} | ||
</style> | ||
<input class="w-input" type="text" /> | ||
`; | ||
class WInput extends HTMLElement { | ||
static observedAttributes = ['color', 'size']; | ||
constructor() { | ||
super(); | ||
this.root = this.attachShadow({ mode: 'open' }); | ||
this.root.appendChild(template.content.cloneNode(true)); | ||
// 侦听输入 | ||
this.input = this.root.querySelector('.w-input'); | ||
this.input.addEventListener('input', this.onInput); | ||
} | ||
// 暴露 value 属性供 Knockout 访问 | ||
get value() { | ||
return this.input.value; | ||
} | ||
set value(v) { | ||
this.input.value = v; | ||
} | ||
onInput = () => { | ||
// 派发 change 事件供 Knockout 访问 | ||
this.dispatchEvent(new InputEvent('change')); | ||
}; | ||
connectedCallback() {} | ||
// 从文档中删除时调用 | ||
disconnectedCallback() { | ||
this.input.removeEventListener('change', this.onInput); | ||
} | ||
adoptedCallback() {} | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
console.log(`Attribute ${name} has changed.`); | ||
} | ||
} | ||
// 定义元素 | ||
window.customElements.define('w-input', WInput); | ||
} | ||
</script> | ||
|
||
<div> | ||
<!-- 绑定 username --> | ||
<label> | ||
name: | ||
<w-input data-bind="value: username"></w-input> | ||
</label> | ||
<br /> | ||
<br /> | ||
|
||
<!-- 绑定 password --> | ||
<label> | ||
password: | ||
<w-input data-bind="value: password"></w-input> | ||
</label> | ||
</div> | ||
|
||
<p>输入的用户名是: <span data-bind="text: username"></span></p> | ||
<p>输入的密码是: <span data-bind="text: password"></span></p> | ||
|
||
<script> | ||
// 声明 model | ||
const model = { | ||
username: ko.observable(''), | ||
password: ko.observable('') | ||
}; | ||
// 执行绑定 | ||
ko.applyBindings(model); | ||
</script> | ||
</body> | ||
</html> | ||
``` | ||
|
||
得到的效果如下: | ||
|
||
<Image url={'http://qkc148.bvimg.com/18470/77031b53dda804cb.gif'} width={600} height={288} /> | ||
|
||
对 Knockout 感兴趣的可以自行去官网查看。 | ||
|
||
除了需要大量的 DOM 操作导致的开发体验差外,个人认为还有一点体验不好的就是过于封闭,组件化开发遵守开闭原则诚然是好的,但对于 css 而言有点太过了,我们知道在 React、Vue 项目开发时经常会需要修改组件库内部的样式,一般通过 css 选择器选择内部元素来覆盖默认样式,但对于影子 DOM 中的元素来说无效: | ||
|
||
```html | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Document</title> | ||
</head> | ||
|
||
<!-- 修改内部 input 的 color --> | ||
<style> | ||
input { | ||
color: red; | ||
} | ||
.w-input input { | ||
color: red; | ||
} | ||
w-input input { | ||
color: red; | ||
} | ||
w-input { | ||
color: red; | ||
} | ||
.w-input { | ||
color: red; | ||
} | ||
</style> | ||
|
||
<body> | ||
<script> | ||
{ | ||
const template = window.document.createElement('template'); | ||
template.innerHTML = ` | ||
<style> | ||
input { | ||
padding: 4px 6px; | ||
outline: none; | ||
border: none; | ||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.6); | ||
border-radius: 4px; | ||
} | ||
</style> | ||
<input class="w-input" type="text" /> | ||
`; | ||
class WInput extends HTMLElement { | ||
static observedAttributes = ['color', 'size']; | ||
constructor() { | ||
super(); | ||
this.root = this.attachShadow({ mode: 'open' }); | ||
this.root.appendChild(template.content.cloneNode(true)); | ||
} | ||
connectedCallback() {} | ||
disconnectedCallback() { | ||
this.input.removeEventListener('change', this.onInput); | ||
} | ||
adoptedCallback() {} | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
console.log(`Attribute ${name} has changed.`); | ||
} | ||
} | ||
window.customElements.define('w-input', WInput); | ||
} | ||
</script> | ||
|
||
<w-input class="w-input" style="color: red;"></w-input> | ||
</body> | ||
</html> | ||
``` | ||
<Image url={'http://qkc148.bvimg.com/18470/2ef29e5af7457ba4.png'} width={412} height={190} /> | ||
|
||
可以看到没有效果,只能通过暴露接口如 `color`、`border` 属性来修改,如果自定义元素没有暴露对应的接口,那我们是无法通过外部的 css 来修改内部组件的样式的。 | ||
|
||
--end | ||
|
||
[knockoutjs]: https://knockoutjs.com/ | ||
[Web_Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters