Skip to content

Commit 91b97b6

Browse files
committed
🚧 初步新增了web/react两个模块
1 parent b50c65e commit 91b97b6

20 files changed

+628
-0
lines changed

deno.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"workspace": [
1717
"flow",
1818
"util",
19+
"react",
20+
"web",
1921
"denokit",
2022
"nodekit"
2123
],

react/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @gaubee/react
2+
3+
该项目提供了一些 react 开发相关的封装,旨在提升 react 项目开发相关的易用性

react/deno.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@gaubee/react",
3+
"version": "0.1.0",
4+
"exports": {
5+
".": "./src/mod.ts"
6+
},
7+
"publish": {
8+
"include": [
9+
"LICENSE",
10+
"README.md",
11+
"src/**/*.ts"
12+
],
13+
"exclude": [
14+
"src/**/*.bench.ts",
15+
"src/**/*.test.ts"
16+
]
17+
}
18+
}

react/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"private": true,
3+
"peerDependencies": {
4+
"react": ">=18"
5+
}
6+
}

react/src/async_effect.mts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { abort_signal_race, type PromiseMaybe } from "@gaubee/util";
2+
import type React, { type DependencyList, useEffect, useInsertionEffect, useLayoutEffect } from "react";
3+
4+
export const asyncEffectCallback = (fn: (signal: AbortSignalWithRace) => Promise<void>): React.EffectCallback => {
5+
return () => {
6+
const abort = new AbortController();
7+
fn(
8+
Object.assign(abort.signal, {
9+
race: (...args: Slice1<Parameters<typeof abort_signal_race>>) => abort_signal_race(abort.signal, ...args),
10+
}) as AbortSignalWithRace,
11+
);
12+
return () => abort.abort();
13+
};
14+
};
15+
type Slice1<T> = T extends [any, ...infer R] ? R : never;
16+
export type AbortSignalWithRace = AbortSignal & {
17+
race: <T>(fn_or_promise: PromiseLike<T> | (() => PromiseLike<T>)) => PromiseMaybe<T>;
18+
};
19+
20+
export const useAsyncEffect = (fn: (signal: AbortSignalWithRace) => Promise<void>, deps?: DependencyList): void => {
21+
useEffect(asyncEffectCallback(fn), deps);
22+
};
23+
24+
export const useAsyncLayoutEffect = (fn: (signal: AbortSignalWithRace) => Promise<void>, deps?: DependencyList): void => {
25+
useLayoutEffect(asyncEffectCallback(fn), deps);
26+
};
27+
28+
export const useAsyncInsertionEffect = (fn: (signal: AbortSignalWithRace) => Promise<void>, deps?: DependencyList): void => {
29+
useInsertionEffect(asyncEffectCallback(fn), deps);
30+
};

react/src/mod.mts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from "./async_effect.mts";
2+
export * from "./types.mts";
3+
export * from "./use_easy_state.mts";
4+
export * from "./use_flow.mts";
5+
export * from "./use_fun_query_state.tsx";
6+
export * from "./use_page_query_state.tsx";
7+
export * from "./use_ref.mts";
8+
export * from "./use_refresh.mts";

react/src/types.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type FunctionComponentType<T> = T extends React.FunctionComponent<infer P> ? P : never;
2+
export type FCType<T> = FunctionComponentType<T>;

react/src/use_easy_state.mts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { obj_assign_props } from "@gaubee/util";
2+
import { type Dispatch, type SetStateAction, useState } from "react";
3+
4+
export type ReactState<T> = [T, Dispatch<SetStateAction<T>>];
5+
export interface EasyState<T> extends ReactState<T> {
6+
value: T;
7+
dispatch: Dispatch<SetStateAction<T>>;
8+
}
9+
export interface UseEasyState {
10+
<T>(initialState: T | (() => T)): EasyState<T>;
11+
<T>(initialState?: undefined): EasyState<T | undefined>;
12+
}
13+
export const easy_state_proto = obj_assign_props([], {
14+
get value() {
15+
return this[0];
16+
},
17+
set value(value) {
18+
this[1](value);
19+
},
20+
} as ThisType<ReactState<any>>);
21+
22+
export const useEasyState: UseEasyState = <T extends unknown>(initialState?: T) => {
23+
const state = useState(initialState);
24+
Object.setPrototypeOf(state, easy_state_proto);
25+
return state as EasyState<T>;
26+
};

react/src/use_flow.mts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type SetStateAction, useInsertionEffect, useState } from "react";
2+
3+
import { type SharedFlow, StateFlow } from "@gaubee/flow";
4+
import { type EasyState, type ReactState, easy_state_proto } from "./use_easy_state.mts";
5+
interface UseFlow {
6+
<T>(flow: StateFlow<T>, state?: ReactState<T>): EasyState<T>;
7+
<T>(flow: SharedFlow<T>, state: ReactState<T>): EasyState<T>;
8+
<T>(flow: SharedFlow<T | undefined>, state?: ReactState<T | undefined>): EasyState<T | undefined>;
9+
}
10+
11+
export const useFlow: UseFlow = <T extends unknown>(flow: SharedFlow<T>, state?: ReactState<T>) => {
12+
if (state == null) {
13+
state = (flow instanceof StateFlow ? useState(flow.value) : useState()) as ReactState<T>;
14+
}
15+
const [value, setValue] = state;
16+
const setValue2 = (valAc: SetStateAction<T>) => {
17+
flow.emit(typeof valAc === "function" ? (valAc as (prevState: T) => T)(value) : valAc);
18+
};
19+
useInsertionEffect(() => {
20+
// if (flow instanceof StateFlow) {
21+
// setValue(flow.value);
22+
// }
23+
const off = flow.on(setValue);
24+
return () => void off();
25+
}, [flow]);
26+
return Object.setPrototypeOf([value, setValue2], easy_state_proto) as EasyState<T>;
27+
};

react/src/use_fun_query_state.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { obj_assign_props } from "@gaubee/util";
2+
import { useEffect, useState } from "react";
3+
import { renderBuilder } from "./common_render_builder.tsx";
4+
import { f7PtrFactory } from "./page_ptr.mts";
5+
6+
type FunQueryApi<I extends any[] = any[], O = any> = (...args: I) => Promise<O>;
7+
type FunQueryInput<FDef extends FunQueryApi> =
8+
FDef extends FunQueryApi<infer I, any> ? I : never;
9+
type FunQueryOutput<FDef extends FunQueryApi> =
10+
FDef extends FunQueryApi<any, infer O> ? O : never;
11+
type FunQueryActionOptions = {
12+
signal?: AbortSignal;
13+
};
14+
export const useFunQueryState = <FDef extends FunQueryApi>(
15+
initValue: FunQueryOutput<FDef>,
16+
api: FDef,
17+
exec: (
18+
api: FDef,
19+
input: FunQueryInput<FDef>,
20+
opts?: FunQueryActionOptions,
21+
) => Promise<FunQueryOutput<FDef>> = (api, input) => api(...input),
22+
) => {
23+
const [isLoading, setIsLoading] = useState(false);
24+
const [value, setValue] = useState<FunQueryOutput<FDef>>(initValue);
25+
const [error, setError] = useState<unknown>(null);
26+
const doQuery = async (
27+
input: FunQueryInput<FDef>,
28+
opts?: FunQueryActionOptions,
29+
) => {
30+
setIsLoading(true);
31+
try {
32+
const output = await exec(api, input, opts);
33+
setValue(output);
34+
setError(null);
35+
return output;
36+
} catch (err) {
37+
setError(err);
38+
} finally {
39+
setIsLoading(false);
40+
}
41+
};
42+
const queryWithDestructor = (input: FunQueryInput<FDef>) => {
43+
const aborter = new AbortController();
44+
doQuery(input, {
45+
signal: aborter.signal,
46+
});
47+
return () => aborter.abort("cancel");
48+
};
49+
const useQuery = (input: FunQueryInput<FDef>, deps?: any[]) => {
50+
useEffect(() => queryWithDestructor(input), deps ?? input);
51+
return result;
52+
};
53+
const f7OnPageInit = (input: FunQueryInput<FDef>) =>
54+
f7PtrFactory(() => doQuery(input));
55+
56+
const f7OnPtrRefresh = (input: FunQueryInput<FDef>) => {
57+
return (done: () => void) => {
58+
doQuery(input).finally(done);
59+
};
60+
};
61+
const result = obj_assign_props(
62+
{
63+
get value() {
64+
return value;
65+
},
66+
set value(value) {
67+
setValue(value);
68+
},
69+
},
70+
Object.freeze({
71+
isLoading,
72+
doQuery: doQuery,
73+
queryWithDestructor,
74+
useQuery,
75+
f7OnPtrRefresh: f7OnPtrRefresh,
76+
f7OnPageInit,
77+
get render() {
78+
return renderBuilder(!isLoading, value, error);
79+
},
80+
}),
81+
);
82+
return result;
83+
};

0 commit comments

Comments
 (0)