WebCell

WebCell

WebCell logo

简体中文 | English

基于 VDOM、JSXMobXTypeScriptWeb 组件 引擎

NPM 依赖性 CI 和 CD

反 996 许可证 UI 库推荐榜单

幻灯片 Gitter

编辑 WebCell 示例

NPM

特性

引擎比较

特性 WebCell 3 WebCell 2 React Vue
JS 语言 TypeScript 5 TypeScript 4 ECMAScript 或 TypeScript ECMAScript 或 TypeScript
JS 语法 ES 装饰器 stage-3 ES 装饰器 stage-2
XML 语法 JSX import JSX factory JSX factory/import HTML/Vue 模板或 JSX(可选)
DOM API Web 组件 Web 组件 HTML 5+ HTML 5+
视图渲染器 DOM Renderer 2 SnabbDOM (内置) SnabbDOM(分叉)
state API MobX @observable this.state this.stateuseState() this.$dataref()
props API MobX @observable @watch this.propsprops => {} this.$propsdefineProps()
状态管理 MobX 6+ MobX 4/5 Redux VueX
页面路由器 JSX 标签 JSX 标签 + JSON 数据 JSX 标签 JSON 数据
资源打包工具 Parcel 2 Parcel 1 webpack Vite

安装

npm install dom-renderer mobx web-cell

Web 浏览器用法

演示和 GitHub 模板

项目引导

工具链

npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D

package.json

{
"scripts": {
"start": "parcel source/index.html --open",
"build": "parcel build source/index.html --public-url ."
}
}

tsconfig.json

{
"compilerOptions": {
"target": "ES6",
"module": "ES2020",
"moduleResolution": "Node",
"useDefineForClassFields": true,
"jsx": "react-jsx",
"jsxImportSource": "dom-renderer"
}
}

.parcelrc

{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}

source/index.html

<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script>
<script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script>

<script src="source/MyTag.tsx"></script>

<my-tag></my-tag>

函数组件

import { DOMRenderer } from 'dom-renderer';
import { FC, PropsWithChildren } from 'web-cell';

const Hello: FC<PropsWithChildren> = ({ children = '世界' }) => (
<h1>你好,{children}</h1>
);

new DOMRenderer().render(<Hello>WebCell</Hello>);

类组件

子元素插槽

import { DOMRenderer } from 'dom-renderer';
import { component } from 'web-cell';

@component({
tagName: 'hello-world',
mode: 'open'
})
class Hello extends HTMLElement {
render() {
return (
<h1>
你好, <slot />!
</h1>
);
}
}

new DOMRenderer().render(
<>
<Hello>WebCell</Hello>
{/* 或 */}
<hello-world>WebCell</hello-world>
</>
);

DOM 属性

import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { WebCell, component, attribute, observer } from 'web-cell';

interface HelloProps {
name?: string;
}

interface Hello extends WebCell<HelloProps> {}

@component({ tagName: 'hello-world' })
@observer
class Hello extends HTMLElement implements WebCell<HelloProps> {
@attribute
@observable
accessor name = '';

render() {
return <h1>你好,{this.name}!</h1>;
}
}

new DOMRenderer().render(<Hello name="WebCell" />);

// 或在 TypeScript 中提示 HTML 标签属性

declare global {
namespace JSX {
interface IntrinsicElements {
'hello-world': HelloProps;
}
}
}
new DOMRenderer().render(<hello-world name="WebCell" />);

内部状态

函数组件

import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { FC, observer } from 'web-cell';

class CounterModel {
@observable
accessor times = 0;
}

const couterStore = new CounterModel();

const Counter: FC = observer(() => (
<button onClick={() => (couterStore.times += 1)}>
计数:{couterStore.times}
</button>
));

new DOMRenderer().render(<Counter />);

类组件

import { DOMRenderer } from 'dom-renderer';
import { observable } from 'mobx';
import { component, observer } from 'web-cell';

@component({ tagName: 'my-counter' })
@observer
class Counter extends HTMLElement {
@observable
accessor times = 0;

handleClick = () => (this.times += 1);

render() {
return <button onClick={this.handleClick}>计数:{this.times}</button>;
}
}

new DOMRenderer().render(<Counter />);

CSS 作用域

内联样式

import { component } from 'web-cell';
import { stringifyCSS } from 'web-utility';

@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement {
style = stringifyCSS({
'.btn': {
color: 'white',
background: 'lightblue'
}
});

render() {
return (
<>
<style>{this.style}</style>

<a className="btn">
<slot />
</a>
</>
);
}
}

链接样式表

import { component } from 'web-cell';

@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement {
render() {
return (
<>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css"
/>
<a className="btn">
<slot />
</a>
</>
);
}
}

CSS 模块

scoped.css
.btn {
color: white;
background: lightblue;
}
MyButton.tsx
import { WebCell, component } from 'web-cell';

import styles from './scoped.css' assert { type: 'css' };

interface MyButton extends WebCell {}

@component({
tagName: 'my-button',
mode: 'open'
})
export class MyButton extends HTMLElement implements WebCell {
connectedCallback() {
this.root.adoptedStyleSheets = [styles];
}

render() {
return (
<a className="btn">
<slot />
</a>
);
}
}

事件委托

import { component, on } from 'web-cell';

@component({ tagName: 'my-table' })
export class MyTable extends HTMLElement {
@on('click', ':host td > button')
handleEdit(event: MouseEvent, { dataset: { id } }: HTMLButtonElement) {
console.log(`编辑行:${id}`);
}

render() {
return (
<table>
<tr>
<td>1</td>
<td>A</td>
<td>
<button data-id="1">编辑</button>
</td>
</tr>
<tr>
<td>2</td>
<td>B</td>
<td>
<button data-id="2">编辑</button>
</td>
</tr>
<tr>
<td>3</td>
<td>C</td>
<td>
<button data-id="3">编辑</button>
</td>
</tr>
</table>
);
}
}

MobX reaction

import { observable } from 'mobx';
import { component, observer, reaction } from 'web-cell';

@component({ tagName: 'my-counter' })
@observer
export class Counter extends HTMLElement {
@observable
accessor times = 0;

handleClick = () => (this.times += 1);

@reaction(({ times }) => times)
echoTimes(newValue: number, oldValue: number) {
console.log(`新值:${newValue},旧值:${oldValue}`);
}

render() {
return <button onClick={this.handleClick}>计数:{this.times}</button>;
}
}

表单关联

import { DOMRenderer } from 'dom-renderer';
import { WebField, component, formField, observer } from 'web-cell';

interface MyField extends WebField {}

@component({
tagName: 'my-field',
mode: 'open'
})
@formField
@observer
class MyField extends HTMLElement implements WebField {
render() {
const { name } = this;

return (
<input
name={name}
onChange={({ currentTarget: { value } }) =>
(this.value = value)
}
/>
);
}
}

new DOMRenderer().render(
<form method="POST" action="/api/data">
<MyField name="test" />

<button>提交</button>
</form>
);

异步组件

AsyncTag.tsx

import { FC } from 'web-cell';

const AsyncTag: FC = () => <div>异步</div>;

export default AsyncTag;

index.tsx

import { DOMRenderer } from 'dom-renderer';
import { lazy } from 'web-cell';

const AsyncTag = lazy(() => import('./AsyncTag'));

new DOMRenderer().render(<AsyncTag />);

Animate CSS 组件

import { DOMRenderer } from 'dom-renderer';
import { AnimateCSS } from 'web-cell';

new DOMRenderer().render(
<AnimateCSS type="fadeIn" component={props => <h1 {...props}>淡入</h1>} />
);

Node.js 用法

工具链

npm install jsdom element-internals-polyfill

Polyfill

import 'web-cell/polyfill';

基础知识

生命周期钩子

  1. connectedCallback
  2. disconnectedCallback
  3. attributeChangedCallback
  4. adoptedCallback
  5. updatedCallback
  6. mountedCallback
  7. formAssociatedCallback
  8. formDisabledCallback
  9. formResetCallback
  10. formStateRestoreCallback

脚手架

  1. 基础
  2. 仪表盘
  3. 静态网站

生态系统

我们建议将这些库与 WebCell 一起使用:

路线图

v2 到 v3 迁移

更多指南

  1. 开发贡献

Generated using TypeDoc