implement nested router

This commit is contained in:
Sczlog 2021-08-12 18:44:49 +08:00
parent 61b9e897c6
commit 575e999785
3 changed files with 266 additions and 24 deletions

View File

@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>meta-ui runtime example: nested components</title>
</head>
<body>
<div id="root"></div>
<script type="module">
import renderApp from "../../src/main.tsx";
renderApp({
version: "example/v1",
metadata: {
name: "nested_components",
description: "nested components example",
},
spec: {
components: [
{
id: "parent",
type: "core/v1/router",
properties: {
routerPolicy: [
{
type: "route",
path: "/1",
cid: "child1",
default: true,
},
{
type: "route",
path: "/2",
cid: "child2",
default: false,
},
],
},
traits: [],
},
{
id: "child1",
type: "core/v1/router",
properties: {
routerPolicy: [
{
type: "route",
path: "/1",
cid: "grandchild1_1",
default: true,
},
{
type: "route",
path: "/2",
cid: "grandchild1_2",
default: false,
},
{
type: "route",
path: "/3",
cid: "grandchild1_3",
default: false,
},
],
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "parent",
},
},
],
},
{
id: "child2",
type: "core/v1/router",
properties: {
routerPolicy: [
{
type: "route",
path: "/1",
cid: "grandchild2_1",
default: true,
},
{
type: "route",
path: "/2",
cid: "grandchild2_2",
default: false,
},
{
type: "route",
path: "/3",
cid: "grandchild2_3",
default: false,
},
],
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "parent",
},
},
],
},
{
id: "grandchild1_1",
type: "core/v1/text",
properties: {
value: {
raw: "1_1",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child1",
},
},
],
},
{
id: "grandchild1_2",
type: "core/v1/text",
properties: {
value: {
raw: "1_2",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child1",
},
},
],
},
{
id: "grandchild1_3",
type: "core/v1/text",
properties: {
value: {
raw: "1_3",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child1",
},
},
],
},
{
id: "grandchild2_1",
type: "core/v1/text",
properties: {
value: {
raw: "2_1",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child2",
},
},
],
},
{
id: "grandchild2_2",
type: "core/v1/text",
properties: {
value: {
raw: "2_2",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child2",
},
},
],
},
{
id: "grandchild2_3",
type: "core/v1/text",
properties: {
value: {
raw: "2_3",
format: "plain",
},
},
traits: [
{
type: "core/v1/route",
properties: {
routerId: "child2",
},
},
],
},
],
},
});
</script>
</body>
</html>

View File

@ -233,7 +233,7 @@ export type SlotsMap = Map<
>;
export type RouterComponentsMap = Map<string, RouterComponentMap>;
export type RouterComponentMap = Map<string, React.FC>;
export type RouterComponentMap = Map<string, React.FC<any>>;
export function resolveAppComponents(
app: RuntimeApplication

View File

@ -1,10 +1,4 @@
import React, {
useEffect,
useMemo,
createElement,
useState,
useRef,
} from "react";
import React, { useEffect, useMemo, useState, useRef } from "react";
import { createComponent } from "@meta-ui/core";
import { Static, Type } from "@sinclair/typebox";
import { ComponentImplementation } from "../../registry";
@ -47,14 +41,10 @@ const matcher = makeCachedMatcher((path: string) => {
return { keys, regexp };
});
export const RouterProvider: React.FC<{ useRouter: boolean }> = ({
useRouter,
children,
}) => {
// TODO: use history api here;
const hook =
//useRef(useHashLocation);
useRef(window.history ? undefined : useHashLocation);
export const RouterProvider: React.FC<{
useRouter: boolean;
}> = ({ useRouter, children }) => {
const hook = useRef(window.history ? undefined : useHashLocation);
const base = useRef(location.pathname);
return useRouter ? (
<Wouter base={base.current} hook={hook.current} matcher={matcher}>
@ -65,10 +55,37 @@ export const RouterProvider: React.FC<{ useRouter: boolean }> = ({
);
};
// all route-like component must have path property, router use path to determined if a component match the route
const NestedWouter: React.FC<{ path: string; base: string }> = ({
children,
path,
base,
}) => {
const router = useRouter();
const [parentLocation] = useLocation();
const nestedBase = `${router.base}${base}`;
// don't render anything outside of the scope
if (!parentLocation.startsWith(base)) {
return null;
}
// we need key to make sure the router will remount if the base changes
return (
<Wouter base={nestedBase} key={nestedBase}>
{children}
</Wouter>
);
};
// const Default = React.FC<{}>
const Router: ComponentImplementation<{
routerPolicy: Static<typeof RouterPolicyPropertySchema>;
}> = ({ routerMap, routerPolicy, subscribeMethods }) => {
const [parentPath, naviagte] = useLocation();
const [, naviagte] = useLocation();
const router = useRouter();
const routes = useMemo(() => {
let defaultPath: string | undefined = undefined;
@ -81,7 +98,7 @@ const Router: ComponentImplementation<{
switch (type.toUpperCase()) {
case RouteType.REDIRECT:
return (
<Woute path={path}>
<Woute key={path} path={path}>
<Redirect href={href || path} />
</Woute>
);
@ -92,15 +109,18 @@ const Router: ComponentImplementation<{
}
if (C.displayName === "router") {
return (
<Woute path={path}>
<Wouter base={`${parentPath}/${path}`}>
<C key={cid}></C>
</Wouter>
</Woute>
// it should match both itself and its children path, so we need to match its path itself
<NestedWouter
path={`(${path}|${path}/.*)`}
base={path}
key={path}
>
<C key={cid}></C>
</NestedWouter>
);
}
return (
<Woute path={path}>
<Woute key={path} path={path}>
<C key={cid}></C>
</Woute>
);