ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Brython 自定义 HTML 元素实战:browser.webcomponent 模块完全指南

Brython 自定义 HTML 元素实战:browser.webcomponent 模块完全指南 编程语言语言运行时编译器前端【免费下载链接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser项目地址https://gitcode.com/gh_mirrors/br/brython点击查看免费下载导读browser.webcomponent是 Brython运行在浏览器中的 Python 3 实现提供的官方模块用于基于标准 DOM 的 Web Components 为骨架结合英文版 browser.webcomponent 文档、底层 JS 实现 与 测试用例系统讲解define()/get()两个函数、Shadow DOM 的用法、生命周期回调以及属性监听机制。读完本文你将能够在 Brython 项目中用纯 Python 定义、注册并动态使用自己的 HTML 组件。一、模块概览用 Python 定义自己的 HTML 标签browser.webcomponent模块封装了浏览器原生的 Web Components 标准Custom Elements让开发者用 Python 类定义自定义 HTML 标签的行为。一个自定义元素custom element在 HTML 页面中可以这样使用popup-window¡Hola!/popup-window也就是说你可以在 HTML 中直接书写一个尚未内置的标签名并在 Brython 脚本里通过 Python 类告诉浏览器当遇到popup-window标签时应该如何渲染、如何响应生命周期事件。该模块从 www/src/Lib/browser/webcomponent.py 导入而该文件本身只是对底层 JavaScript 实现的一行再导出from _webcomponent import *真正实现逻辑位于 www/src/libs/_webcomponent.js它以$B.addToImported(_webcomponent, module)的方式挂载到 Brython 运行时最终暴露给 Python 侧两个函数define和get。二、define()注册自定义元素的核心函数模块对外暴露的唯一核心函数是define(tag_name, component_class[, options])2.1 tag_name自定义标签名tag_name是自定义标签的名称。Web Component 规范强制要求标签名必须包含一个连字符-例如popup-window、bold-italic、observed-element。这是为了与原生 HTML 元素区分开避免冲突。这一点在底层实现中有硬性校验www/src/libs/_webcomponent.js 第 49-55 行if (typeof tag_name ! string) { $B.RAISE(_b_.TypeError, first argument of define() must be a string, ...) } else if (tag_name.indexOf(-) -1) { $B.RAISE(_b_.ValueError, custom tag name must contain a hyphen (-)) }因此如果标签名不含-define()会抛出ValueError如果第一个参数不是字符串则抛出TypeError。2.2 component_class定义组件行为的 Python 类component_class是定义组件行为的类。类的__init__方法用于创建组件其中的self参数引用的是该自定义组件对应的 DOM 元素。换句话说self就是一个 Brython 的 DOM 节点对象因此你可以在__init__里访问self.attrs元素属性字典、self.attachShadow()创建影子根、self.textContent、self.style等所有 DOM 能力。底层实现在构造 JS 类时通过$B.DOMNode.$factory(this)将原生元素包装成 Python 可操作的 DOMNode并执行$B.$call(init, _self)调用 Python 的__init__见 www/src/libs/_webcomponent.js 第 80-90 行。component_class必须是类class否则同样会抛出TypeError源码第 56-59 行。2.3 options可选的 extends 选项define()的第三个参数options是一个字典对应原生 API CustomElementRegistry.define 的选项。目前唯一可用的选项是extends用于创建自定义化内置元素customized built-in element即基于某个现有 HTML 标签如p、div扩展出的自定义元素class MyParagraph: def __init__(self): self.shadow self.attachShadow({mode: open}) self.shadow html.B(hello) define(my-paragraph, MyParagraph, {extends: p})2.4 继承 browser.html 类时自动推断 extends如果组件类继承了 browser.html 模块中定义的类如html.P、html.DIVextends选项会被自动添加其值取为父类的类名。上面的代码可以简化为from browser import html class MyParagraph(html.P): def __init__(self): self.shadow self.attachShadow({mode: open}) self.shadow html.B(hello) define(my-paragraph, MyParagraph)底层实现会在未显式传入options时沿着类的 MRO方法解析顺序向上查找一旦发现某个基类定义在browser.html模块中就以该基类名转为小写作为extends值www/src/libs/_webcomponent.js 第 24-34 行let stack [...cls.tp_bases]; while (stack.length) { base stack.pop(); if (base.__module__ browser.html) { _extends base.__name__.toLowerCase() break } stack.push(...base.tp_bases); }extends值必须是字符串且必须是合法标签名——底层会通过document.createElement(_extends)验证若结果为HTMLUnknownElement则抛出ValueError源码第 36-48 行。测试用例 www/tests/test_webcomponent.py 中的 PR 2295 用例即验证了从html.DIV继承并多层继承的场景class MyDivA2295(html.DIV): def __init__(self): self A class MyDivB2295(MyDivA2295): def __init__(self): self B webcomponent.define(my-div_a_2295, MyDivA2295) webcomponent.define(my-div_b_2295, MyDivB2295)三、get()按标签名查询组件类get(tag_name)返回与tag_name关联的组件类如果该标签名尚未注册任何组件则返回None。底层实现www/src/libs/_webcomponent.js 第 174-178 行直接查询浏览器原生的customElements注册表function get(name) { var ce customElements.get(name) if (ce ce.$cls) {return ce.$cls} return _b_.None }注意define()在注册时会把 Python 类引用挂在 JS 类上webcomp.$cls cls因此get()返回的是原始的 Python 类对象而非内部的 JS 类。若注册表里没有对应条目则返回 Brython 的None即_b_.None。四、完整示例定义bold-italic组件假设我们要定义一个带data-val属性的自定义标签bold-italicbold-italic>from browser import webcomponent class BoldItalic: def __init__(self): # Create a shadow root shadow self.attachShadow({mode: open}) # Insert the value of attribute data-val in bold italic # in the shadow root shadow html.B(html.I(self.attrs[data-val])) # Tell the browser to manage bold-italic tags with the class BoldItalic webcomponent.define(bold-italic, BoldItalic)这段代码做了三件事self.attachShadow({mode: open})为元素创建一个影子根ShadowRootmode: open表示外部脚本可以通过element.shadowRoot访问该子树通过self.attrs[data-val]读取自定义元素上的属性值使用html.B(html.I(...))构建 粗体 斜体 的嵌套节点并用运算符Brython 中向节点追加子节点的语法插入影子根。4.1 关于 ShadowRoot这里用到了另一种 DOM 技术——ShadowRoot影子根它的作用是定义一个与主 DOM 树相互隔离的子树见 英文文档 对 ShadowRoot 的说明。影子树内的样式与结构不会泄漏到主文档主文档中的样式也不会意外作用于影子树内部这是 Web Component 实现样式封装的基石。五、生命周期回调管理Web Component 标准定义了一组生命周期回调函数lifecycle callbacks在组件的不同阶段被浏览器自动调用例如元素被插入文档、从文档移除、属性变化等。在 Brython 中实现它们只需在组件类中直接添加同名的方法即可底层会把 Python 类中的函数逐一拷贝到生成的 JS 类原型上www/src/libs/_webcomponent.js 第 144-163 行会遍历类 MRO 上的所有函数挂载为webcomp.prototype[key]。5.1 connectedCallback元素被接入文档时import browser.webcomponent class BoldItalic: def __init__(self): # Create a shadow root shadow self.attachShadow({mode: open}) # Insert the value of attribute data-val in bold italic # in the shadow root shadow html.B(html.I(self.attrs[data-val])) def connectedCallback(self): print(connected callback, self) webcomponent.define(bold-italic, BoldItalic)当每个bold-italic元素被插入文档时connectedCallback都会被调用一次self同样是该元素对应的 DOM 节点。这是做初始化工作绑定事件、加载数据、读取子节点的典型位置。5.2 observedAttributes 与 attributeChangedCallback监听属性变化要处理某些属性的变化需要在类中定义列表observedAttributes声明要观察哪些属性并实现方法attributeChangedCallback(name, old, new, ns)分别在属性名、旧值、新值、命名空间发生变化时被调用。下面的例子还展示了一个重要技巧用 browser.html 模块中的maketag函数动态创建新的自定义标签类再在运行时动态地把元素加入文档observed_tag html.maketag(observed-element) class Observed: observedAttributes [data] def attributeChangedCallback(self, name, old, new, ns): print(fattribute {name} changed from {old} to {new}) webcomponent.define(observed-element, Observed) elt observed_tag() document elt elt.attrs[data] info执行流程是html.maketag(observed-element)生成一个对应标签的 Python 构造类其底层实现在 www/src/brython.js 第 14819-14829 行maketag校验标签名为字符串后用makeTagClass生成类、绑定$factory并注册到browser.html模块命名空间与tags字典中observed_tag()实例化元素document elt把元素动态插入文档触发connectedCallback修改elt.attrs[data] info由于data在observedAttributes中浏览器回调attributeChangedCallback打印attribute data changed from None to info。注意observedAttributes应定义为类属性列表。底层实现www/src/libs/_webcomponent.js 第 109-133 行中静态 getterobservedAttributes优先读取类属性若定义成属性property对象也能兼容对应 issue 2454 的测试若定义成方法则已废弃会触发DeprecationWarning。六、源码级解析define() 的底层实现www/src/libs/_webcomponent.js 中define函数第 6-172 行完整展示了注册一个自定义元素需要经过的步骤参数规范化与校验通过$B.args处理三个参数options默认为Noneoptions若非字典则抛TypeErroroptions can only be None or a dicttag_name必须含连字符cls必须是类解析 extends显式传入options[extends]或沿 MRO 自动推断自browser.html基类见 2.4 节注入 DOMNode将cls.$webcomponent置为true并把$B.DOMNode插入类 MRO 的末尾cls.tp_mro.splice(...)使 Python 组件实例天然具备 DOM 节点的全部方法动态生成 JS 类以 Python 类名为类名eval一段模板代码生成class Xxx extends HTMLElement或 extends 指定的原生类的 JS 类。其构造函数中会调用html.maketag确保browser.html模块中存在对应标签类、用$B.DOMNode.$factory(this)包装元素、调用 Python 的__init__合规性检查按 Custom Element 规范 的要求如果__init__在构造过程中给元素新增了原本不存在的属性会抛出TypeErrorCustom element must not create attributes, found: ...这是自定义元素构造器的硬性约束拷贝 Python 方法到 JS 原型遍历类 MRO 上的所有函数挂载到webcomp.prototype从而让connectedCallback、attributeChangedCallback、disconnectedCallback等原生回调能调用到 Python 实现注册到 customElements调用浏览器原生customElements.define(tag_name, webcomp, {extends: extend_tag})有 extends 时或customElements.define(tag_name, webcomp)完成注册。七、实战进阶从测试与示例中提炼的组件设计模式7.1 用_initialized守卫避免重复初始化connectedCallback在元素每次被移入文档时都会触发例如被移动位置、被重新插入。测试用例issue 2062展示的惯用做法是用实例标志位防止重复初始化class App(BaseElement): def connectedCallback(self): if not self._initialized: self._get_childs() self._initialized True def _get_childs(self): self._main_view_elements [child for child in self.children]7.2 基于多继承的 Mixin 组合由于define()会把 MRO 上的方法都挂到 JS 原型Brython 组件可以借助 Python 多继承把能力拆分为多个 Mixinissue 1893 / 1894 / 2082 的测试覆盖了属性继承、回调中异常处理、方法解析顺序等场景。例如class UIPlugin: property def prefix(self): return self._prefix class TestComponent(UIPlugin, BaseComponent): pass webcomponent.define(ui-test1893, TestComponent)7.3 用__init_subclass__自动注册组件测试中还出现了一种自动化注册模式issue 2169在基类的__init_subclass__中把类名转成带ui-前缀的 kebab-case 标签名并自动define()从而免去手写注册代码class UIPlugin2169: classmethod def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) tag_name ui- un_camel(cls.__name__) webcomponent.define(tag_name, cls)7.4 用自定义元素承载 JSON 配置测试用例issue 2075展示了一个实用场景自定义元素内部存放 JSON 配置文本connectedCallback时解析并批量应用到父元素或其子元素上class Config(BaseComponent): def connectedCallback(self): if not self._initialized: self.style.display none self._initialized True child_config json.loads(self.text) selector self.attrs.get(selector, None) for child in parent.select(selector): for attr_name, value in child_config.items(): setattr(child, attr_name, value)7.5 完整的演示页面仓库中的 www/gallery/webcomponent.html 是一个可直接运行的完整示例包含一个带图标与 CSS 样式封装的popup-info组件在 Shadow DOM 内插入style、span、img并实现connectedCallback以及父/子组件继承、属性监听、动态创建、事件绑定bind/unbind、addEventListener/removeEventListener等多种组合用法是学习 Brython Web Component 的最佳参考页面。八、常见错误与注意事项情况结果tag_name不含连字符-抛出ValueError: custom tag name must contain a hyphen (-)define()第一个参数非字符串抛出TypeError第二个参数不是类抛出TypeErroroptions既不是None也不是字典抛出TypeError: options can only be None or a dictextends不是字符串抛出TypeError: value for extends must be a stringextends指向不存在的 HTML 标签抛出ValueError: ... is not a valid tag name__init__中给元素创建了原本不存在的属性抛出TypeError: Custom element must not create attributesobservedAttributes声明为方法而非类属性/列表触发DeprecationWarning建议改为类属性此外还需注意options中目前只有extends一个键可用自定义元素构造器必须遵循规范约束不要在__init__阶段擅自添加属性属性监听的回调签名固定为attributeChangedCallback(self, name, old, new, ns)。结语browser.webcomponent用一套极简的 Python APIdefineget 类方法约定完整承接了浏览器原生 Web Components 能力自定义标签、Shadow DOM 封装、生命周期回调、属性监听、动态创建一应俱全。结合 模块文档、英文文档、底层 JS 实现、测试用例 与 演示页面你可以在 Brython 项目中把可复用的 UI 能力封装成真正的 HTML 标签让页面代码更简洁、更语义化。赞分享编程语言语言运行时编译器前端【免费下载链接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser项目地址https://gitcode.com/gh_mirrors/br/brython点击查看免费下载相关推荐Brython browser.webcomponent 模块用 Python 定义自定义 HTML 标签与 Web Components 实战指南Brython browser.webcomponent 模块用 Python 定义自定义 HTML 标签与 Web Components 实战指南 本指南系编程语言语言运行时编译器前端Brython 自定义 Web 组件实战用 browser.webcomponent 打造属于自己的 HTML 标签Brython 自定义 Web 组件实战用 browser.webcomponent 打造属于自己的 HTML 标签 browser.webcomponent编程语言语言运行时编译器前端HTML Purifier自定义元素与属性完全指南HTML Purifier自定义元素与属性完全指南 前言HTML Purifier的定制能力 HTML Purifier作为一款强大的HTML过滤工具其核心应用安全后端上一篇免费解锁WeMod专业版Wand-Enhancer完全使用指南下一篇城通网盘解析器三步获取高速直连下载地址的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表