Skip to content

Commit 0878140

Browse files
committed
fix: fix dynamic list input error
1 parent 45d5554 commit 0878140

File tree

3 files changed

+211
-21
lines changed

3 files changed

+211
-21
lines changed

src/driver/ControllerDriver/controller.ts

Lines changed: 207 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,33 @@ import { isObjectOrArray } from 'react-form-simple/utils/util';
44

55
export type ObserverOptions = {
66
path?: string[];
7+
// 添加对象路径追踪,解决动态数组重排序问题
8+
pathTracker?: WeakMap<any, string>;
9+
// 根代理引用
10+
rootProxy?: any;
11+
// 是否强制重新创建代理(用于 reset 场景)
12+
forceRecreate?: boolean;
713
};
814

915
export type ObserverCb = { path: string; value: any };
1016

17+
// 用于存储对象到代理的映射
18+
const objectToProxyMap = new WeakMap<any, any>();
19+
// 用于存储代理到原始对象的映射
20+
const proxyToObjectMap = new WeakMap<any, any>();
21+
1122
export const replaceTarget = (proxyObject: any, values: any) => {
23+
// 简单方式:直接替换属性,让代理系统自己处理
24+
// 先删除不在新值中的属性
25+
Object.keys(proxyObject).forEach((key) => {
26+
if (!(key in values)) {
27+
delete proxyObject[key];
28+
}
29+
});
30+
31+
// 然后设置新值
1232
Object.entries(values).forEach(([key, value]) => {
13-
updateProxyValue(proxyObject, key, value);
33+
proxyObject[key] = value;
1434
});
1535

1636
return proxyObject;
@@ -35,6 +55,57 @@ export const getProxyValue = (
3555
return currentObj;
3656
};
3757

58+
// 获取对象在当前代理中的实际路径
59+
const getActualPath = (rootProxy: any, target: any): string[] => {
60+
if (!rootProxy || !target) return [];
61+
62+
const findPath = (
63+
current: any,
64+
searchTarget: any,
65+
path: string[] = [],
66+
): string[] | null => {
67+
if (current === searchTarget) {
68+
return path;
69+
}
70+
71+
if (current && typeof current === 'object') {
72+
// 获取原始对象进行比较
73+
const originalCurrent = proxyToObjectMap.get(current) || current;
74+
75+
if (Array.isArray(originalCurrent)) {
76+
for (let i = 0; i < originalCurrent.length; i++) {
77+
const item = originalCurrent[i];
78+
if (item === searchTarget) {
79+
return [...path, i.toString()];
80+
}
81+
if (item && typeof item === 'object') {
82+
const result = findPath(item, searchTarget, [
83+
...path,
84+
i.toString(),
85+
]);
86+
if (result) return result;
87+
}
88+
}
89+
} else {
90+
for (const [key, value] of Object.entries(originalCurrent)) {
91+
if (value === searchTarget) {
92+
return [...path, key];
93+
}
94+
if (value && typeof value === 'object') {
95+
const result = findPath(value, searchTarget, [...path, key]);
96+
if (result) return result;
97+
}
98+
}
99+
}
100+
}
101+
102+
return null;
103+
};
104+
105+
const originalRoot = proxyToObjectMap.get(rootProxy) || rootProxy;
106+
return findPath(originalRoot, target) || [];
107+
};
108+
38109
export const updateProxyValue = (
39110
obj: any,
40111
path: string,
@@ -54,15 +125,48 @@ export const updateProxyValue = (
54125

55126
if (current[key] === undefined) {
56127
if (options.createPath) {
57-
current[key] = {};
128+
// 检查下一个key是否为数字,如果是则创建数组
129+
const nextKey = keys[i + 1];
130+
if (!isNaN(Number(nextKey))) {
131+
current[key] = [];
132+
} else {
133+
current[key] = {};
134+
}
58135
} else {
59136
return false;
60137
}
61138
}
62139

63-
if (typeof current[key] !== 'object' || current[key] === null) {
140+
// 处理类型不匹配的情况
141+
if (current[key] !== null && typeof current[key] === 'object') {
142+
// 检查是否需要从对象转为数组或从数组转为对象
143+
const nextKey = keys[i + 1];
144+
const isNextKeyNumeric = !isNaN(Number(nextKey));
145+
const currentIsArray = Array.isArray(current[key]);
146+
147+
if (isNextKeyNumeric && !currentIsArray) {
148+
// 需要转为数组
149+
if (options.createPath) {
150+
current[key] = [];
151+
} else {
152+
return false;
153+
}
154+
} else if (!isNextKeyNumeric && currentIsArray) {
155+
// 需要转为对象
156+
if (options.createPath) {
157+
current[key] = {};
158+
} else {
159+
return false;
160+
}
161+
}
162+
} else if (current[key] === null || typeof current[key] !== 'object') {
64163
if (options.createPath) {
65-
current[key] = {};
164+
const nextKey = keys[i + 1];
165+
if (!isNaN(Number(nextKey))) {
166+
current[key] = [];
167+
} else {
168+
current[key] = {};
169+
}
66170
} else {
67171
return false;
68172
}
@@ -72,7 +176,17 @@ export const updateProxyValue = (
72176
}
73177

74178
const lastKey = keys[keys.length - 1];
75-
if (options.forceUpdate || lastKey in current) {
179+
const lastKeyIndex = Number(lastKey);
180+
181+
// 如果是数组且key是数字索引
182+
if (Array.isArray(current) && !isNaN(lastKeyIndex)) {
183+
// 确保数组有足够的长度
184+
while (current.length <= lastKeyIndex) {
185+
current.push(undefined);
186+
}
187+
current[lastKeyIndex] = newValue;
188+
return true;
189+
} else if (options.forceUpdate || lastKey in current) {
76190
current[lastKey] = newValue;
77191
return true;
78192
}
@@ -85,29 +199,108 @@ export const observer = <T extends DefaultRecord>(
85199
cb?: (args: ObserverCb) => void,
86200
options?: ObserverOptions,
87201
): T => {
88-
const { path = [] } = (options || {}) as ObserverOptions;
202+
const {
203+
path = [],
204+
pathTracker = new WeakMap(),
205+
rootProxy,
206+
forceRecreate = false,
207+
} = (options || {}) as ObserverOptions;
208+
209+
// 如果已经为这个对象创建过代理且不强制重创建,直接返回
210+
if (!forceRecreate && objectToProxyMap.has(initialVal)) {
211+
return objectToProxyMap.get(initialVal);
212+
}
213+
214+
const actualRootProxy = rootProxy || null;
89215

90216
const proxy = new Proxy(initialVal, {
91217
get(target, key, receiver) {
92218
const ret = Reflect.get(target, key, receiver);
93219
if (React.isValidElement(ret)) return ret;
94-
return isObjectOrArray(ret)
95-
? observer(ret as T, cb, {
96-
...(options as ObserverOptions),
97-
path: [...path, key.toString()],
98-
})
99-
: ret;
220+
221+
if (isObjectOrArray(ret)) {
222+
// 对于嵌套对象,检查是否已经有代理(除非强制重创建)
223+
if (!forceRecreate && objectToProxyMap.has(ret)) {
224+
return objectToProxyMap.get(ret);
225+
}
226+
227+
// 计算当前路径
228+
let currentPath: string[];
229+
if (actualRootProxy && target !== initialVal) {
230+
// 动态计算实际路径
231+
currentPath = getActualPath(actualRootProxy, target);
232+
currentPath.push(key.toString());
233+
} else {
234+
currentPath = [...path, key.toString()];
235+
}
236+
237+
const childProxy = observer(ret as T, cb, {
238+
...(options as ObserverOptions),
239+
path: currentPath,
240+
pathTracker,
241+
rootProxy: actualRootProxy || proxy,
242+
forceRecreate,
243+
});
244+
245+
return childProxy;
246+
}
247+
248+
return ret;
100249
},
101250
set(target, key, val) {
102-
const newPath = [...path, key.toString()];
251+
let currentPath: string[];
252+
253+
// 动态计算当前设置操作的实际路径
254+
if (actualRootProxy) {
255+
currentPath = getActualPath(actualRootProxy, target);
256+
currentPath.push(key.toString());
257+
} else {
258+
currentPath = [...path, key.toString()];
259+
}
103260

104261
const ret = Reflect.set(target, key, val);
105262

106-
cb?.({ path: newPath.join('.'), value: val });
263+
// 如果设置的是对象/数组,清理旧的代理映射
264+
if (isObjectOrArray(val)) {
265+
// 清理可能存在的旧映射
266+
const oldVal = target[key as keyof typeof target];
267+
if (oldVal && objectToProxyMap.has(oldVal)) {
268+
const oldProxy = objectToProxyMap.get(oldVal);
269+
objectToProxyMap.delete(oldVal);
270+
proxyToObjectMap.delete(oldProxy);
271+
}
272+
}
273+
274+
cb?.({ path: currentPath.join('.'), value: val });
275+
return ret;
276+
},
277+
deleteProperty(target, key) {
278+
// 处理删除操作
279+
const deletedValue = target[key as keyof typeof target];
280+
if (deletedValue && objectToProxyMap.has(deletedValue)) {
281+
const deletedProxy = objectToProxyMap.get(deletedValue);
282+
objectToProxyMap.delete(deletedValue);
283+
proxyToObjectMap.delete(deletedProxy);
284+
}
107285

286+
const ret = Reflect.deleteProperty(target, key);
287+
288+
let currentPath: string[];
289+
if (actualRootProxy) {
290+
currentPath = getActualPath(actualRootProxy, target);
291+
currentPath.push(key.toString());
292+
} else {
293+
currentPath = [...path, key.toString()];
294+
}
295+
296+
cb?.({ path: currentPath.join('.'), value: undefined });
108297
return ret;
109298
},
110299
});
111300

301+
// 建立双向映射
302+
objectToProxyMap.set(initialVal, proxy);
303+
proxyToObjectMap.set(proxy, initialVal);
304+
112305
return proxy;
113306
};

src/driver/RenderDriver/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {
33
GlobalProps,
44
UseFormNamespace,
55
} from 'react-form-simple';
6-
import { getProxyValue } from '../ControllerDriver';
76
import { renderer } from './Renderer';
87

98
interface CreateOptions<T = DefaultRecord>
@@ -20,10 +19,8 @@ export const create = (options: CreateOptions) => {
2019
contextProps: { model },
2120
} = options;
2221

23-
const set = () => {
24-
uidWithFormItemAPIs.forEach((v) => {
25-
v.setValue(getProxyValue(model, v.bindId));
26-
});
22+
const set = (path: string, value: string) => {
23+
uidWithFormItemAPIs.get(path)?.setValue(value);
2724
};
2825

2926
const render: UseFormNamespace.UseFormReturnType['render'] =

src/use/useForm.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ const useForm = <T extends DefaultRecord>(
3232
() =>
3333
createObserverForm(
3434
proxyTarget.current as T,
35-
({ path }) => {
35+
({ path, value }) => {
3636
console.log(path);
37-
set();
37+
set(path, value);
3838
observerFactory.subscribeManager.notify();
3939
debounceFn.watch();
4040
},

0 commit comments

Comments
 (0)