Skip to content

Commit

Permalink
Feat: support keepalive without experimental version of react (#6768)
Browse files Browse the repository at this point in the history
* feat: support keepalive without experimental version of react

* feat: add keep alive example

* fix: optimize code
ClarkXia authored Feb 20, 2024
1 parent 45bf24b commit 591a9ab
Showing 24 changed files with 254 additions and 40 deletions.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/with-keep-alive-react/ice.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from '@ice/app';

export default defineConfig(() => ({}));
21 changes: 21 additions & 0 deletions examples/with-keep-alive-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@examples/with-keep-alive-react",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "ice start",
"build": "ice build"
},
"dependencies": {
"react": "0.0.0-experimental-0cdfef19b-20231211",
"react-dom": "0.0.0-experimental-0cdfef19b-20231211"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2"
},
"resolutions": {
"react": "0.0.0-experimental-0cdfef19b-20231211",
"react-dom": "0.0.0-experimental-0cdfef19b-20231211"
}
}
3 changes: 3 additions & 0 deletions examples/with-keep-alive-react/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineAppConfig } from 'ice';

export default defineAppConfig(() => ({}));
22 changes: 22 additions & 0 deletions examples/with-keep-alive-react/src/document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, Title, Links, Main, Scripts } from 'ice';

function Document() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="description" content="ICE Demo" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Title />
<Links />
</head>
<body>
<Main />
<Scripts />
</body>
</html>
);
}

export default Document;
18 changes: 18 additions & 0 deletions examples/with-keep-alive-react/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Link } from 'ice';
import Counter from '@/components/Counter';

export default function Home() {
return (
<main>
<h2>Home</h2>
<Counter />
<Link to="/about">About</Link>
</main>
);
}

export function pageConfig() {
return {
title: 'Home',
};
}
10 changes: 10 additions & 0 deletions examples/with-keep-alive-react/src/pages/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { KeepAliveOutlet } from 'ice';

export default function Layout() {
return (
<>
<h1>I'm Keep Alive</h1>
<KeepAliveOutlet />
</>
);
}
32 changes: 32 additions & 0 deletions examples/with-keep-alive-react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compileOnSave": false,
"buildOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"module": "esnext",
"target": "es6",
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"rootDir": "./",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"],
"ice": [".ice"]
}
},
"include": ["src", ".ice", "ice.config.*"],
"exclude": ["build", "public"]
}
10 changes: 4 additions & 6 deletions examples/with-keep-alive/package.json
Original file line number Diff line number Diff line change
@@ -7,15 +7,13 @@
"build": "ice build"
},
"dependencies": {
"react": "0.0.0-experimental-0cdfef19b-20231211",
"react-dom": "0.0.0-experimental-0cdfef19b-20231211"
"@ice/app": "workspace:*",
"@ice/runtime": "workspace:*",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2"
},
"resolutions": {
"react": "0.0.0-experimental-0cdfef19b-20231211",
"react-dom": "0.0.0-experimental-0cdfef19b-20231211"
}
}
11 changes: 11 additions & 0 deletions examples/with-keep-alive/src/components/Count.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from 'react';

export default function Count() {
const [count, setCount] = useState(0);
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
12 changes: 12 additions & 0 deletions examples/with-keep-alive/src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Link } from 'ice';
import Count from '@/components/Count';

export default function Home() {
return (
<div>
<h4>Home</h4>
<Count />
<Link to="/">Index</Link>
</div>
);
}
31 changes: 18 additions & 13 deletions examples/with-keep-alive/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Link } from 'ice';
import Counter from '@/components/Counter';
import { useEffect } from 'react';
import { useActive, Link } from 'ice';
import Count from '@/components/Count';

export default function Home() {
const active = useActive();

useEffect(() => {
if (active) {
console.log('Page Index is actived');
} else {
console.log('Page Index is deactived');
}
}, [active]);

return (
<main>
<h2>Home</h2>
<Counter />
<Link to="/about">About</Link>
</main>
<div>
<h4>Index</h4>
<Count />
<Link to="/home">Home</Link>
</div>
);
}

export function pageConfig() {
return {
title: 'Home',
};
}
6 changes: 3 additions & 3 deletions examples/with-keep-alive/src/pages/layout.tsx
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@ import { KeepAliveOutlet } from 'ice';

export default function Layout() {
return (
<>
<h1>I'm Keep Alive</h1>
<div>
<h2>Layout</h2>
<KeepAliveOutlet />
</>
</div>
);
}
3 changes: 1 addition & 2 deletions examples/with-keep-alive/tsconfig.json
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
@@ -29,4 +28,4 @@
},
"include": ["src", ".ice", "ice.config.*"],
"exclude": ["build", "public"]
}
}
1 change: 1 addition & 0 deletions packages/ice/src/constant.ts
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ export const RUNTIME_EXPORTS = [
'defineAppConfig',
'useAppData',
'history',
'useActive',
'KeepAliveOutlet',
'useMounted',
'ClientOnly',
33 changes: 33 additions & 0 deletions packages/runtime/src/Activity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

interface ActivityProps {
mode: string;
children: React.ReactElement | null;
}

interface ActivityContext {
active: boolean;
}

const Context = React.createContext<ActivityContext>(null);
const ActivityProvider = Context.Provider;

export const useActive = () => {
const data = React.useContext(Context);
return data?.active;
};

export default function Activity({ mode, children }: ActivityProps) {
const active = mode === 'visible';
return (
<ActivityProvider value={{
active,
}}
>
{/* Additional wrapper for hidden elements */}
<div style={{ display: active ? 'block' : 'none' }}>
{children}
</div>
</ActivityProvider>
);
}
52 changes: 37 additions & 15 deletions packages/runtime/src/KeepAliveOutlet.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,58 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useOutlet, useLocation } from 'react-router-dom';
import ActivityComponent from './Activity.js';

// @ts-ignore
const Activity = React.unstable_Activity;
const Activity = React.unstable_Activity || ActivityComponent;
interface ActivityItem {
outlet: React.ReactElement | null;
key: string;
pathname: string;
}

// ref: https://leomyili.github.io/react-stillness-component/docs/examples/react-router/v6
export default function KeepAliveOutlet() {
if (!Activity) {
throw new Error('`<KeepAliveOutlet />` now requires react experimental version. Please install it first.');
}
const [outlets, setOutlets] = useState([]);
const [outlets, setOutlets] = useState<ActivityItem[]>([]);
const location = useLocation();
const outlet = useOutlet();
// Save the first outlet for SSR hydration.
const outletRef = useRef({
key: location.key,
pathname: location.pathname,
outlet,
});

useEffect(() => {
const result = outlets.some(o => o.pathname === location.pathname);
if (!result) {
setOutlets([
...outlets,
{
key: location.key,
pathname: location.pathname,
outlet,
},
]);
// If outlets is empty, save the first outlet for SSR hydration,
// and should not call setOutlets to avoid re-render.
if (outlets.length !== 0 ||
outletRef.current?.pathname !== location.pathname) {
let currentOutlets = outletRef.current ? [outletRef.current] : outlets;
const result = currentOutlets.some(o => o.pathname === location.pathname);
if (!result) {
setOutlets([
// TODO: the max length of outlets should be configurable.
...currentOutlets,
{
key: location.key,
pathname: location.pathname,
outlet,
},
]);
outletRef.current = null;
}
}
}, [location.pathname, location.key, outlet, outlets]);

// Render initail outlet for SSR hydration.
const renderOutlets = outlets.length === 0 ? [outletRef.current] : outlets;

return (
<>
{
outlets.map((o) => {
renderOutlets.map((o) => {
return (
<Activity key={o.key} mode={location.pathname === o.pathname ? 'visible' : 'hidden'}>
{o.outlet}
2 changes: 2 additions & 0 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ import AppErrorBoundary from './AppErrorBoundary.js';
import getAppConfig, { defineAppConfig } from './appConfig.js';
import { routerHistory as history } from './history.js';
import KeepAliveOutlet from './KeepAliveOutlet.js';
import { useActive } from './Activity.js';
import ClientOnly from './ClientOnly.js';
import useMounted from './useMounted.js';
import usePageLifecycle from './usePageLifecycle.js';
@@ -117,6 +118,7 @@ export {
getRequestContext,
history,

useActive,
KeepAliveOutlet,
AppErrorBoundary,
ClientOnly,
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -2,4 +2,4 @@ packages:
- 'packages/*'
- 'examples/*'
- 'website'
- '!examples/with-keep-alive/'
- '!examples/with-keep-alive-react/'

0 comments on commit 591a9ab

Please sign in to comment.