mirror of
https://github.com/alibaba/ice.git
synced 2024-10-23 07:04:52 +08:00
Feat: support keepalive without experimental version of react (#6768)
* feat: support keepalive without experimental version of react * feat: add keep alive example * fix: optimize code
This commit is contained in:
parent
45bf24bf3c
commit
591a9abe96
3
examples/with-keep-alive-react/ice.config.mts
Normal file
3
examples/with-keep-alive-react/ice.config.mts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineConfig } from '@ice/app';
|
||||
|
||||
export default defineConfig(() => ({}));
|
21
examples/with-keep-alive-react/package.json
Normal file
21
examples/with-keep-alive-react/package.json
Normal file
@ -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
examples/with-keep-alive-react/src/app.ts
Normal file
3
examples/with-keep-alive-react/src/app.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineAppConfig } from 'ice';
|
||||
|
||||
export default defineAppConfig(() => ({}));
|
22
examples/with-keep-alive-react/src/document.tsx
Normal file
22
examples/with-keep-alive-react/src/document.tsx
Normal file
@ -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
examples/with-keep-alive-react/src/pages/index.tsx
Normal file
18
examples/with-keep-alive-react/src/pages/index.tsx
Normal file
@ -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
examples/with-keep-alive-react/src/pages/layout.tsx
Normal file
10
examples/with-keep-alive-react/src/pages/layout.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { KeepAliveOutlet } from 'ice';
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<>
|
||||
<h1>I'm Keep Alive</h1>
|
||||
<KeepAliveOutlet />
|
||||
</>
|
||||
);
|
||||
}
|
32
examples/with-keep-alive-react/tsconfig.json
Normal file
32
examples/with-keep-alive-react/tsconfig.json
Normal file
@ -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"]
|
||||
}
|
@ -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
examples/with-keep-alive/src/components/Count.tsx
Normal file
11
examples/with-keep-alive/src/components/Count.tsx
Normal file
@ -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
examples/with-keep-alive/src/pages/home.tsx
Normal file
12
examples/with-keep-alive/src/pages/home.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
@ -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',
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ export const RUNTIME_EXPORTS = [
|
||||
'defineAppConfig',
|
||||
'useAppData',
|
||||
'history',
|
||||
'useActive',
|
||||
'KeepAliveOutlet',
|
||||
'useMounted',
|
||||
'ClientOnly',
|
||||
|
33
packages/runtime/src/Activity.tsx
Normal file
33
packages/runtime/src/Activity.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
@ -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}
|
||||
|
@ -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,
|
||||
|
@ -993,6 +993,28 @@ importers:
|
||||
specifier: ^18.0.6
|
||||
version: 18.0.11
|
||||
|
||||
examples/with-keep-alive:
|
||||
dependencies:
|
||||
'@ice/app':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ice
|
||||
'@ice/runtime':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/runtime
|
||||
react:
|
||||
specifier: ^18.0.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: ^18.0.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.0.0
|
||||
version: 18.0.34
|
||||
'@types/react-dom':
|
||||
specifier: ^18.0.2
|
||||
version: 18.0.11
|
||||
|
||||
examples/with-nested-routes:
|
||||
dependencies:
|
||||
'@ice/app':
|
||||
|
@ -2,4 +2,4 @@ packages:
|
||||
- 'packages/*'
|
||||
- 'examples/*'
|
||||
- 'website'
|
||||
- '!examples/with-keep-alive/'
|
||||
- '!examples/with-keep-alive-react/'
|
||||
|
Loading…
Reference in New Issue
Block a user