Skip to content

Commit

Permalink
[gem] Support error boundary
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Sep 8, 2024
1 parent 61b3b43 commit 1d26f0d
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 122 deletions.
8 changes: 5 additions & 3 deletions packages/gem-devtools/src/scripts/get-gem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export const getSelectedGem = function (data: PanelStore): PanelStore | string {
const lifecycleMethod = new Set(['willMount', 'render', 'mounted', 'shouldUpdate', 'updated', 'unmounted']);
const buildInMethod = new Set(['update', 'setState', 'effect', 'memo']);
const buildInProperty = new Set(['internals']);
const buildInAttribute = new Set(['ref']);
const buildInAttribute = new Set(['ref', 'data-gem-reflect', 'data-gem-style']);
const buildInCSSState = new Set(['gem-style-boundary']);
const memberSet = getProps($0);
metadata.observedAttributes?.forEach((attr) => {
const prop = kebabToCamelCase(attr);
Expand Down Expand Up @@ -225,6 +226,7 @@ export const getSelectedGem = function (data: PanelStore): PanelStore | string {
name: state,
value: $0[prop],
type: 'boolean',
buildIn: buildInCSSState.has(state) ? 1 : 0,
});
});
$0.internals?.stateList?.forEach((state: any) => {
Expand Down Expand Up @@ -271,7 +273,7 @@ export const getSelectedGem = function (data: PanelStore): PanelStore | string {
value: objectToString($0[key]),
type: 'function',
path: [key],
buildIn: buildInMethod.has(key) ? 2 : 0,
buildIn: buildInMethod.has(key) ? 1 : 0,
});
return;
}
Expand All @@ -280,7 +282,7 @@ export const getSelectedGem = function (data: PanelStore): PanelStore | string {
value: objectToString($0[key]),
type: typeof $0[key],
path: [key],
buildIn: buildInProperty.has(key) ? 2 : 0,
buildIn: buildInProperty.has(key) ? 1 : 0,
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/gem-examples/src/life-cycle/children.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class Descendant extends GemElement {

render() {
console.log(`descendant${this.key} render`);
if (Math.random() > 0.8) throw new Error('random error');
return html``;
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/gem-examples/src/life-cycle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
render,
useStore,
mounted,
fallback,
} from '@mantou/gem';

import '../elements/layout';
Expand Down Expand Up @@ -67,6 +68,12 @@ export class App extends GemElement {
console.log({ firstName, lastName, disabled, count });
};

@fallback()
#error = (detail?: Error) => {
console.error(detail);
return html`${detail?.message}`;
};

render() {
console.log('parent render');
return html`
Expand Down
22 changes: 9 additions & 13 deletions packages/gem/docs/en/001-guide/001-basic/001-reactive-element.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ render(

## Life cycle

> [!WARNING]
> The lifecycle may be [replaced](https://github.com/mantou132/gem/issues/159) in the future by decorators based on `@effect` `@memo` `@willMount` `@template` `@mounted` `@unmounted`
You can specify life cycle functions for GemElement. Sometimes they are useful, for example:

```js
Expand All @@ -127,29 +130,22 @@ Complete life cycle:
| constructor | |attr/prop/store update|
+-------------+ +----------------------+
| |
| |
+------v------+ +--------v-------+
| willMount | | shouldUpdate |
+-------------+ +----------------+
| |
| (shouldUpdate)
| |
+------v-------------------------v------+
| render |
| @memo(willMount) |
+---------------------------------------+
| |
| |
+------v------+ +------v------+
| mounted | | updated |
+-------------+ +-------------+
+------v-------------------------v------+
| @template(render) |
+---------------------------------------+
| |
| |
+------v-------------------------v------+
| unmounted |
| @effect(mounted/updated/unmounted) |
+---------------------------------------+
```

> [!NOTE]
> The `constructor` and `unmounted` of the parent element are executed before the child element, but the `mounted` is executed after the child element
> [!WARNING]
> The lifecycle may be replaced in the future by decorators based on `@effect` `@memo` `@willMount` `@renderTemplate` `@mounted` `@unmounted
18 changes: 10 additions & 8 deletions packages/gem/docs/en/003-api/001-gem-element.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ Type with decorator

## Method Decorator

| name | description |
| ----------------- | ------------------------------------------------------- |
| `@memo` | Similar `GemElement.memo` |
| `@effect` | Similar `GemElement.effect` |
| `@willMount` | Similar `GemElement.willMount` |
| `@mounted` | Similar `GemElement.mounted` |
| `@unmounted` | Similar `GemElement.unmounted` |
| `@renderTemplate` | Similar `GemElement.render` + `GemElement.shouldUpdate` |
| name | description |
| ------------ | ------------------------------------------------------------ |
| `@memo` | Similar `GemElement.memo` |
| `@effect` | Similar `GemElement.effect` |
| `@willMount` | Similar `GemElement.willMount` |
| `@mounted` | Similar `GemElement.mounted` |
| `@unmounted` | Similar `GemElement.unmounted` |
| `@template` | Similar `GemElement.render` + `GemElement.shouldUpdate` |
| `@fallback` | When the content render error, rendering the reserve content |


## Utils

Expand Down
21 changes: 9 additions & 12 deletions packages/gem/docs/zh/001-guide/001-basic/001-reactive-element.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ render(

## 生命周期

> [!WARNING]
> 生命周期在未来可能被基于 `@effect` `@memo` 的装饰器 `@willMount` `@template` `@mounted` `@unmounted` [替代](https://github.com/mantou132/gem/issues/159)
你可以为 GemElement 指定生命周期函数,有时候他们会很有用,例如:

```js
Expand All @@ -129,29 +132,23 @@ class MyElement extends GemElement {
| constructor | |attr/prop/store update|
+-------------+ +----------------------+
| |
| |
+------v------+ +--------v-------+
| willMount | | shouldUpdate |
+-------------+ +----------------+
| |
| (shouldUpdate)
| |
+------v-------------------------v------+
| render |
| @memo(willMount) |
+---------------------------------------+
| |
| |
+------v------+ +------v------+
| mounted | | updated |
+-------------+ +-------------+
+------v-------------------------v------+
| @template(render) |
+---------------------------------------+
| |
| |
+------v-------------------------v------+
| unmounted |
| @effect(mounted/updated/unmounted) |
+---------------------------------------+
```

> [!NOTE]
> 父元素的 `constructor``unmounted` 先于子元素执行,但 `mounted` 后于子元素执行
> [!WARNING]
> 生命周期在未来可能被基于 `@effect` `@memo` 的装饰器 `@willMount` `@renderTemplate` `@mounted` `@unmounted` 替代
17 changes: 9 additions & 8 deletions packages/gem/docs/zh/003-api/001-gem-element.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ class GemElement extends HTMLElement {

## 方法/函数装饰器

| 名称 | 描述 |
| ----------------- | ---------------------------------------------------- |
| `@memo` | 类似 `GemElement.memo` |
| `@effect` | 类似 `GemElement.effect` |
| `@willMount` | 类似 `GemElement.willMount` |
| `@mounted` | 类似 `GemElement.mounted` |
| `@unmounted` | 类似 `GemElement.unmounted` |
| `@renderTemplate` | 类似 `GemElement.render` + `GemElement.shouldUpdate` |
| 名称 | 描述 |
| ------------ | ---------------------------------------------------- |
| `@memo` | 类似 `GemElement.memo` |
| `@effect` | 类似 `GemElement.effect` |
| `@willMount` | 类似 `GemElement.willMount` |
| `@mounted` | 类似 `GemElement.mounted` |
| `@unmounted` | 类似 `GemElement.unmounted` |
| `@template` | 类似 `GemElement.render` + `GemElement.shouldUpdate` |
| `@fallback` | 当内容渲染失败时渲染后备内容 |

## 工具函数

Expand Down
17 changes: 4 additions & 13 deletions packages/gem/src/elements/base/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { BoundaryCSSState, createState, GemElement, html, TemplateResult } from '../../lib/element';
import {
property,
emitter,
Emitter,
boolattribute,
shadow,
effect,
renderTemplate,
willMount,
} from '../../lib/decorators';
import { property, emitter, Emitter, boolattribute, shadow, effect, template, willMount } from '../../lib/decorators';
import { createStore, updateStore, Store, connect } from '../../lib/store';
import { titleStore, history, UpdateHistoryParams } from '../../lib/history';
import { addListener, QueryString } from '../../lib/utils';
Expand Down Expand Up @@ -270,7 +261,7 @@ export class GemLightRouteElement extends GemElement {
};

@effect((i) => i.#updateContentDep())
#updateContent = ([key, path]: any, old: any) => {
#updateContent = ([key, path]: any, old?: any) => {
// 只有查询参数改变
if (old && key === old[0] && path === old[1]) {
this.#updateLocationStore();
Expand All @@ -279,8 +270,8 @@ export class GemLightRouteElement extends GemElement {
this.update();
};

@renderTemplate((i) => !i.inert)
#render = () => {
@template((i) => !i.inert)
#content = () => {
const { content } = this.#state;
if (content === undefined) return;

Expand Down
4 changes: 2 additions & 2 deletions packages/gem/src/helper/react-utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
// eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies
import { ReactNode, useEffect, useRef, useState } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
// eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies
import { Root, createRoot } from 'react-dom/client';

import { Store, connect } from '../lib/store';
Expand Down
36 changes: 30 additions & 6 deletions packages/gem/src/lib/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createCSSSheet, GemElement, UpdateToken, Metadata, TemplateResult } from './element';
import {
createCSSSheet,
GemElement,
UpdateToken,
Metadata,
TemplateResult,
createTemplate,
RenderErrorEvent,
render,
} from './element';
import { camelToKebabCase, PropProxyMap, GemError } from './utils';
import { Store } from './store';
import * as elementExports from './element';
Expand Down Expand Up @@ -263,17 +272,32 @@ export function mounted() {
return effect(() => []);
}

export function renderTemplate<T extends GemElement, V extends () => TemplateResult | null | undefined>(
/**当返回 `false` 时不进行更新,包括 `memo` */
shouldRender?: (instance: T) => boolean,
export function template<T extends GemElement, V extends () => TemplateResult | null | undefined>(
/** 当所有 `template` 返回 `false` 时不进行更新,包括 `memo` */
condition?: (instance: T) => boolean,
) {
return function (
_: any,
{ addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
) {
addInitializer(function (this: T) {
if (shouldRender) this.shouldUpdate = () => shouldRender(this);
this.render = access.get(this).bind(this);
createTemplate(this, {
render: access.get(this).bind(this),
condition: condition && (() => condition(this) as any),
});
});
};
}

export function fallback<T extends GemElement, V extends (err: any) => TemplateResult | null | undefined>() {
return function (
_: any,
{ addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
) {
addInitializer(function (this: T) {
this.addEventListener(RenderErrorEvent, ({ detail }: CustomEvent) => {
render(access.get(this).apply(this, [detail]), this.internals.shadowRoot || this);
});
});
};
}
Expand Down
Loading

0 comments on commit 1d26f0d

Please sign in to comment.