Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gem] Update docs #182

Closed
mantou132 opened this issue Jul 19, 2024 · 2 comments
Closed

[gem] Update docs #182

mantou132 opened this issue Jul 19, 2024 · 2 comments
Labels

Comments

@mantou132
Copy link
Owner

mantou132 commented Jul 19, 2024

  • blog
    • 从 React 迁移到 Gem
    • v2 介绍
  • style/theme
  • devtools
  • state/setState

esm.sh v136 上线就要发布 v2,因为例子中的装饰器都是使用 v1,例外需要更新:

  • gem-book // hello decorators
  • sandbox example
@mantou132
Copy link
Owner Author

v2 介绍

经过漫长的开发实践,Gem 终于开始迈入了 v2,Gem 以让用户简单的方式编写自定义元素为宗旨进行了此次迭代。下面将介绍 v2 的一些重大更新。

装饰器

v2 使用 ES 装饰器代替了以前的 TS 装饰器,并且将 GemElement.constructor 的参数用装饰器代替:

@customElement('my-element')
+@aria({ foucusable: true, role: 'button' })
+@shadow()
+@asnyc()
class MyElement extends GemElement {
-  constructor() {
-    super({ focusable: true, isAsync: true, isLight: false });
-    this.internals.role = 'button';
-  }
}

使用装饰器具有更好的可扩展性,另外也降低了代码复杂度。基于同样的目的,还添加了 @effect @memo 等装饰器让你编写更简洁的自定义元素:

@customElement("my-element")
class MyElement extends GemElement {
  @attribute name: string;

  #content: string;

  @memo((myElement) => [myElemnt.name])
  #caleContent() {
    this.#content = this.name;
  }

  @effect((myElement) => [myElement.name])
  #fetchData() {
    // request
  }
}

Warning

未来 Gem 可能会弃用生命周期回调函数,全面使用装饰器代替

内部状态

v1 使用特定的字段 state 来表示元素内部状态,并使用 this.setState 来更新状态,在 v2 中,可以使用任意字段,因为定义状态的同时定义了更新方法:

@customElement("my-element")
class MyElement extends GemElement {
  #state = createState({ a: true })

  render() {
    this.#state({ a: false });
    console.log(this.#state.a);
  }
}

Note

v2 支持在任意地方更新状态,这在 v1 中将造成死循环

默认使用 Light DOM

Gem 使用 Shadow DOM 的一个理由是样式隔离性,他让用户可以直接编写“模块化”的 CSS,但是使用 Shadow DOM 编写 WebApp 也有一些缺点:

  • 不能使用 SVG 符号
  • URL Hash 无效
  • document.activeElement 无效
  • 不方便集成 React/Vue 组件
  • 性能较差

如果不是写需要高度封装的自定义元素(例如 UI 库),使用 Light DOM 是更合适的选择。现在,CSS 规范带来了 @scope,所以 Gem 充分利用 @scope 并默认使用 Light DOM,并且同样具备“模块化”(v1 不支持 Light DOM 样式“模块化”),下面的例子中,div 选择器将只应用在 <my-element> 的内容上:

const styles = createCSSSheet(css`
  :scope {
    display: block;
  }
  div {
    color: red;
  }
`);

@customElement('my-element')
@adoptedStyle(styles)
class MyElement extends GemElement {}

Note

就像开头的例子,如果想要使用 Shadow DOM,需要添加 @shadow,并将 :scope 替换成 :host

主题增强

v1 只支持全局主题,v2 支持范围主题,并且支持主题覆盖:

// 全局主题将自动添加到 `document`
const [theme] = useTheme({ textColor: '#eee' });

const [scopedTheme] = useScopedTheme({ scopeTextColor: '#333' });

const [overrideTheme] = overrideTheme(theme, { textColor: '#eff' })

const styles = createCSSSheet(css`
  :scope {
    color: ${theme.textColor};
    background: ${scopedTheme.scopeTextColor};
  }
`);

@customElement('my-element')
@adoptedStyle(styles)
@adoptedStyle(scopedTheme)
@adoptedStyle(overrideTheme)
class MyElement extends GemElement {}

此外,得益于相对颜色语法,主题中以 Color 结尾的颜色直接支持使用“重量”(类似字重)调节亮度:theme.textColor500,这是一个比原 textColor 稍亮的颜色。

一起创造更好的 Gem

希望 Gem 能以卓越的设计成为创建自定义元素的首选方案,如果你有任何建议和想法,请创建 Issue

@mantou132 mantou132 added the v2 label Aug 4, 2024
@mantou132
Copy link
Owner Author

从 React 迁移到 Gem

React 是一个非常优秀的 UI 构建库,其生态中也有很多优秀的工具,Gem 在许多地方都有借鉴,目的是打造一个基于原生、无需编译、易于使用的 WebApp 开发框架。

从编写组件到编写自定义元素

先来看一个简单的 React 组件,这里的函数名称可以作为标签名在其他组件中使用,该组件的属性使用 IProps 标记,最后返回组件的渲染内容:

interface IProps {
  name: string;
  data?: Record<string, string>;
}

function MyComponent(props: IProps) {
  const str = JSON.stringify(props.data);

  return (
    <div>
      {props.name}
      <pre>{str}</pre>
    </div>
  );
}

在 Gem 中使用 Classes 定义自定义元素,并且必须使用 @customElement 注册元素,这让元素能在模板中使用;使用类字段来定义元素属性,例如 @attribute 等装饰器来比较属性为反应性,这让字段具有上面 React 组件中 IProps 中属性类似的作用;最后定义一个 render 方法,该方法最终返回元素内容模板,它和 React 组件返回的内容相当,只不过模板使用 JavaScript 模板字符串而非 JSX,正是因为这样,Gem 才不需要编译,能直接运行在 Vanilla JavaScript 中。

@customElement("my-element")
class MyElement extends GemElement {
  @attribute name: string;
  @property data?: Record<string, string>;

  get str() {
    return JSON.stringify(this.data);
  }

  render() {
    return html`
      <div>
        ${this.name}
        <pre>${this.str}</pre>
      </div>
    `;
  }
}

一般情况下 React 组件没有那么简单,组件很可能有副作用,或者一些重计算需要记忆化,这些需求在 React 中使用 Hooks,例如将上面 React 组件中的序列化结果记忆化,并在挂载后打印日志:

function MyComponent(props: IProps) {
  userEffect(() => {
    console.log("mounted!");
  }, []);

  const str = React.useMemo(() => {
    return JSON.stringify(props.data);
  }, [props.data]);

  return (
    <div>
      {props.name}
      <pre>{str}</pre>
    </div>
  );
}

在 Gem 中,是通过装饰器装饰函数来完成中:

@customElement("my-element")
class MyElement extends GemElement {
  @attribute name: string;
  @property data?: Record<string, string>;

  @effect(() => [])
  log() {
    console.log("mounted!");
  }

  // 注意:不能从 `this` 访问 `data`
  @memo((e) => [e.data])
  get str() {
    return JSON.stringify(this.data);
  }

  render() {
    return html`
      <div>
        ${this.name}
        <pre>${this.str}</pre>
      </div>
    `;
  }
}

总的来说 Gem 写的自定义组件要比 React 组件复杂,带来的好处是可以在 Vanilla JavaScript 中使用。

从 CSS Modules 迁移到 Gem

编写一个组件绕不开样式,在 React 中,为了让样式模块化,通常使用 CSS Modules 或者 CSS in JS 方案如 Styled Components,来看看在 React 组件中使用 CSS Modules:

.title {
  font-size: medium;
}
import styles from "./styles.css";

function MyComponent() {
  return <div className={styles.title}></div>;
}

在 Gem 中,需要手动创建对象并应用到元素上(应该自动应用?):

const styles = createCSSStyle({
  title: `
    font-size: medium;
  `,
});

@customElement("my-element")
@adoptedStyle(styles)
class MyElement extends GemElement {
  render() {
    return html`<div class=${styles.title}></div>`;
  }
}

其他工具迁移到 Gem

  • React Router => <gem-light-route>
  • React Redux => Gem.useStore
  • Theming => @mantou/gem/helper/theme
  • I18n => @mantou/gem/helper/i18n
  • Request => @mantou/gem/helper/request

@mantou132 mantou132 pinned this issue Aug 12, 2024
@mantou132 mantou132 unpinned this issue Aug 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant