feat: add layered render recovery boundaries
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @param {{
|
||||
* kind?: string,
|
||||
* code?: string,
|
||||
* buildId?: string,
|
||||
* configSchemaVersion?: string,
|
||||
* releaseId?: string,
|
||||
* supportReference: string
|
||||
* }} props
|
||||
*/
|
||||
export function BootErrorShell({
|
||||
kind = "BOOT_CONFIG_FAILURE",
|
||||
code = "BOOT_FAILED",
|
||||
buildId,
|
||||
configSchemaVersion,
|
||||
releaseId,
|
||||
supportReference,
|
||||
}) {
|
||||
return (
|
||||
<main role="alert">
|
||||
<h1>애플리케이션을 시작할 수 없습니다.</h1>
|
||||
<dl>
|
||||
<dt>오류</dt>
|
||||
<dd>{kind}</dd>
|
||||
<dt>코드</dt>
|
||||
<dd>{code}</dd>
|
||||
{buildId && (
|
||||
<>
|
||||
<dt>빌드</dt>
|
||||
<dd>{buildId}</dd>
|
||||
</>
|
||||
)}
|
||||
{configSchemaVersion && (
|
||||
<>
|
||||
<dt>설정 스키마</dt>
|
||||
<dd>{configSchemaVersion}</dd>
|
||||
</>
|
||||
)}
|
||||
{releaseId && (
|
||||
<>
|
||||
<dt>릴리스</dt>
|
||||
<dd>{releaseId}</dd>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
<p>지원 참조: {supportReference}</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Component } from "react";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* children: React.ReactNode,
|
||||
* boundaryName: string,
|
||||
* routeId: string,
|
||||
* buildId: string,
|
||||
* telemetry?: import("../../application/ports/telemetry-port.js").TelemetryPort,
|
||||
* fallback?: React.ReactNode
|
||||
* }} RenderBoundaryProps
|
||||
* @typedef {{ hasError: boolean }} RenderBoundaryState
|
||||
*/
|
||||
|
||||
/** @extends {Component<RenderBoundaryProps, RenderBoundaryState>} */
|
||||
export class RenderErrorBoundary extends Component {
|
||||
/** @param {RenderBoundaryProps} props */
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
try {
|
||||
this.props.telemetry?.emit("ui.render.failed", {
|
||||
route_id: this.props.routeId,
|
||||
build_id: this.props.buildId,
|
||||
component_boundary: this.props.boundaryName,
|
||||
});
|
||||
} catch {
|
||||
// Telemetry must never recurse into another render failure.
|
||||
}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({ hasError: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
this.props.fallback ?? (
|
||||
<section role="alert">
|
||||
<p>error.render_failure</p>
|
||||
<button type="button" onClick={this.reset}>
|
||||
retry
|
||||
</button>
|
||||
</section>
|
||||
)
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Omit<RenderBoundaryProps, "boundaryName">} props */
|
||||
export function RouteBoundary(props) {
|
||||
return <RenderErrorBoundary {...props} boundaryName="route" />;
|
||||
}
|
||||
|
||||
/** @param {Omit<RenderBoundaryProps, "boundaryName">} props */
|
||||
export function FeatureBoundary(props) {
|
||||
return <RenderErrorBoundary {...props} boundaryName="feature" />;
|
||||
}
|
||||
Reference in New Issue
Block a user