Skip to content

Commit 8d88f43

Browse files
authored
Merge pull request #2 from easyops-cn/steve/useEffect
Steve/useEffect
2 parents 111bc4f + 6b4225c commit 8d88f43

23 files changed

+1133
-121
lines changed

apps/test/src/Pages/Layout.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,57 @@
1-
import { createPortal, useState } from "@next-tsx/core";
1+
import {
2+
createPortal,
3+
useState,
4+
useEffect,
5+
showMessage,
6+
sessionStore,
7+
} from "@next-tsx/core";
28
import MyModal from "../Components/MyModal";
39

410
export default function Layout() {
5-
const [a, _setA] = useState<string | null>(null);
11+
const [a, setA] = useState<string | null>(null);
12+
const b = a ? "value" : "null";
13+
const [c, setC] = useState<number>(0);
14+
15+
useEffect(() => {
16+
if (a) {
17+
showMessage({
18+
type: "success",
19+
content: "Value of a changed: " + a + " / " + b + " / " + c,
20+
});
21+
22+
sessionStore.setItem("myKey", { a, b, c });
23+
}
24+
}, [a, b, c]);
25+
26+
const dataSource = {
27+
name: "Tom",
28+
age: 30,
29+
};
630

731
return (
832
<>
9-
<div title={a!.length > 0 ? (a as string) : "default"}>
10-
<h1>My App</h1>
11-
</div>
33+
<button
34+
onClick={() => {
35+
setA("New Value");
36+
setC((prev) => prev + 42);
37+
}}
38+
>
39+
My App
40+
</button>
41+
<pre>{JSON.stringify(sessionStore.getItem("myKey"), null, 2)}</pre>
42+
<eo-descriptions
43+
dataSource={dataSource}
44+
list={[
45+
{
46+
label: "Name",
47+
render: (d) => <strong>{d.name}</strong>,
48+
},
49+
{
50+
label: "Age",
51+
render: (d) => <em>{d.age}</em>,
52+
},
53+
]}
54+
/>
1255
{createPortal(<MyModal />)}
1356
</>
1457
);

docs/DESIGN.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## 文件目录结构
2+
3+
```
4+
apps/
5+
└── YOUR-APP-ID/
6+
└── src/
7+
├── Components/
8+
├── Contexts/
9+
├── Pages/
10+
├── Utils/
11+
├── app.json
12+
├── i18n.json
13+
└── index.tsx
14+
```
15+
16+
### 组件
17+
18+
```tsx
19+
// ./src/Components/
20+
```

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"license": "GPL-3.0",
1010
"scripts": {
1111
"prepare": "husky",
12+
"serve": "brick-container-serve",
1213
"start": "lerna run start",
1314
"build": "lerna run build --concurrency=2",
1415
"test": "test-next-project",
@@ -21,6 +22,7 @@
2122
"devDependencies": {
2223
"@babel/types": "^7.27.6",
2324
"@next-core/babel-preset-next": "^1.0.26",
25+
"@next-core/brick-container": "^3.23.7",
2426
"@next-core/eslint-config-next": "^3.0.1",
2527
"@types/jest": "^29.5.14",
2628
"@types/js-yaml": "^3.12.10",

packages/converter/src/convertComponent.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import convertCodeBlock from "./convertCodeBlock.js";
2929
import { getAppTplName, getViewTplName } from "./modules/getTplName.js";
3030
import { convertRoutes } from "./modules/convertRoutes.js";
3131
import { convertLifeCycle } from "./convertLifeCycle.js";
32+
import { convertProperties } from "./convertProperties.js";
3233

3334
const PORTAL_COMPONENTS = ["eo-modal", "eo-drawer"];
3435

@@ -135,8 +136,14 @@ export async function convertComponent(
135136
component.name.toLowerCase() === component.name
136137
) {
137138
brick = {
138-
brick: component.name.replaceAll("_", ".").replaceAll("--", "."),
139-
properties: component.properties,
139+
brick: component.name.replaceAll("--", "."),
140+
properties: await convertProperties(
141+
component.properties,
142+
mod,
143+
state,
144+
options,
145+
scope
146+
),
140147
};
141148
} else {
142149
// eslint-disable-next-line no-console
@@ -164,7 +171,13 @@ export async function convertComponent(
164171
state.app.appType === "app"
165172
? getAppTplName(tplName)
166173
: getViewTplName(tplName, options.rootId),
167-
properties: component.properties,
174+
properties: await convertProperties(
175+
component.properties,
176+
mod,
177+
state,
178+
options,
179+
scope
180+
),
168181
};
169182
} else {
170183
// eslint-disable-next-line no-console

packages/converter/src/convertEvents.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ function convertEventHandler(
158158
action: `history.${handler.payload.method}`,
159159
args: handler.payload.args,
160160
};
161+
case "store":
162+
return {
163+
action: `${handler.payload.type}Storage.${handler.payload.method}`,
164+
args: handler.payload.args,
165+
};
161166
case "show_message":
162167
return {
163168
action: `message.${handler.payload.type}` as "message.info",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { isRenderUseBrick, type ParsedModule } from "@next-tsx/parser";
2+
import { isObject } from "@next-core/utils/general";
3+
import type { BrickConf } from "@next-core/types";
4+
import type { ConvertOptions, ConvertState } from "./interfaces.js";
5+
import { convertComponent } from "./convertComponent.js";
6+
import { deepReplaceVariables } from "./deepReplaceVariables.js";
7+
8+
export async function convertProperties(
9+
properties: Record<string, unknown>,
10+
mod: ParsedModule,
11+
state: ConvertState,
12+
options: ConvertOptions,
13+
scope: "page" | "view" | "template"
14+
) {
15+
const convertPropValue = async (value: unknown): Promise<unknown> => {
16+
if (Array.isArray(value)) {
17+
return Promise.all(value.map((item) => convertPropValue(item)));
18+
}
19+
20+
if (isObject(value)) {
21+
return Object.fromEntries(
22+
await Promise.all(
23+
Object.entries(value).map(async ([k, v]) => {
24+
if (k === "render" && isObject(v) && isRenderUseBrick(v)) {
25+
const patterns = new Map<string, string>();
26+
if (v.params.length > 0) {
27+
patterns.set(v.params[0], "DATA");
28+
}
29+
const useBrick = (
30+
await Promise.all(
31+
v.children.map(
32+
(child) =>
33+
convertComponent(
34+
child,
35+
mod,
36+
state,
37+
options,
38+
scope
39+
) as Promise<BrickConf | BrickConf[]>
40+
)
41+
)
42+
).flatMap((child) => deepReplaceVariables(child, patterns));
43+
return ["useBrick", useBrick];
44+
}
45+
return [k, await convertPropValue(v)];
46+
})
47+
)
48+
);
49+
}
50+
51+
return value;
52+
};
53+
54+
const props = (await convertPropValue(properties)) as Record<string, unknown>;
55+
56+
return props;
57+
}

packages/converter/src/modules/convertModule.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,34 @@ async function parseModulePart(
9191
const { component } = part;
9292

9393
for (const binding of component.bindingMap.values()) {
94+
let conf: ContextConf;
9495
if (binding.kind === "resource") {
95-
context.push(convertDataSource(binding.resource!));
96+
conf = convertDataSource(binding.resource!);
9697
} else if (
9798
binding.kind === "state" ||
9899
binding.kind === "constant" ||
99100
binding.kind === "param"
100101
) {
101-
context.push({
102+
conf = {
102103
name: binding.id.name,
103104
value: binding.initialValue,
104105
expose: binding.kind === "param",
105106
track: true,
106-
});
107+
};
108+
} else {
109+
continue;
107110
}
111+
const effects = component.effectsMap.get(binding.id);
112+
if (effects && effects.length > 0) {
113+
conf.onChange = effects.map((id) => ({
114+
...(part.type === "template"
115+
? { targetRef: id }
116+
: { target: `#${id}` }),
117+
args: ["effect"],
118+
method: "trigger",
119+
}));
120+
}
121+
context.push(conf);
108122
}
109123

110124
let children: ComponentChild[] | undefined;

packages/core/index.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ export function useResource<T = any>(
2828
}
2929
): [data: T, refetch: () => void];
3030

31+
/**
32+
* 在依赖项数组变化时执行回调函数。
33+
*
34+
* 注意与 React 中的 useEffect 行为略有不同:
35+
* - 初始渲染时不会执行 callback,仅在 deps 变化时执行 callback
36+
* - 不支持返回清理函数
37+
*/
38+
export function useEffect(callback: () => void, deps: unknown[]): void;
39+
3140
/** 返回一个 ref 对象,其 `.current` 属性初始化为传入的参数 (`initialValue`) */
3241
export function useRef<T>(initialValue: T): RefObject<T>;
3342

@@ -190,6 +199,24 @@ export function showDialog(options: {
190199
/** 拷贝一段文本 */
191200
export function copyText(text: string): Promise<void>;
192201

202+
/**
203+
* 本地存储对象,数据存储在浏览器的 Local Storage 中,生命周期为永久。
204+
* 可存储任意能被 JSON 序列化的数据。
205+
*/
206+
export const localStore: Store;
207+
208+
/**
209+
* 会话存储对象,数据存储在浏览器的 Session Storage 中,生命周期为当前会话。
210+
* 可存储任意能被 JSON 序列化的数据。
211+
*/
212+
export const sessionStore: Store;
213+
214+
export interface Store {
215+
getItem<T = any>(key: string): T | null;
216+
setItem<T = any>(key: string, value: T): void;
217+
removeItem(key: string): void;
218+
}
219+
193220
/**
194221
* 平台部署的基础路径,通常为 `/next/`。
195222
*

packages/parser/src/__snapshots__/parseApp.spec.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ exports[`parseApp should work 1`] = `
482482
"slot": undefined,
483483
},
484484
],
485+
"effectsMap": Map {},
485486
"id": Node {
486487
"end": 263,
487488
"loc": SourceLocation {
@@ -535,6 +536,7 @@ exports[`parseApp should work 1`] = `
535536
"slot": undefined,
536537
},
537538
],
539+
"effectsMap": Map {},
538540
"id": Node {
539541
"end": 1303,
540542
"loc": SourceLocation {
@@ -718,6 +720,7 @@ exports[`parseApp should work 1`] = `
718720
"slot": undefined,
719721
},
720722
],
723+
"effectsMap": Map {},
721724
"id": Node {
722725
"end": 124,
723726
"loc": SourceLocation {
@@ -787,6 +790,7 @@ exports[`parseApp should work 1`] = `
787790
"slot": undefined,
788791
},
789792
],
793+
"effectsMap": Map {},
790794
"id": Node {
791795
"end": 29,
792796
"loc": SourceLocation {
@@ -849,6 +853,7 @@ exports[`parseApp should work 1`] = `
849853
"slot": undefined,
850854
},
851855
],
856+
"effectsMap": Map {},
852857
"id": Node {
853858
"end": 31,
854859
"loc": SourceLocation {

packages/parser/src/__snapshots__/parseTemplate.spec.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ exports[`parseTemplate should work 1`] = `
253253
"slot": undefined,
254254
},
255255
],
256+
"effectsMap": Map {},
256257
"id": Node {
257258
"end": 49,
258259
"loc": SourceLocation {

0 commit comments

Comments
 (0)