Skip to content

Commit e04d3f0

Browse files
committed
fix: init
1 parent 1632b05 commit e04d3f0

File tree

6 files changed

+258
-24
lines changed

6 files changed

+258
-24
lines changed

packages/components/form/__tests__/form-list.test.tsx

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { fireEvent, mockTimeout, render, vi } from '@test/utils';
12
import React from 'react';
23
import { MinusCircleIcon } from 'tdesign-icons-react';
3-
import { fireEvent, mockTimeout, render, vi } from '@test/utils';
44

55
import Button from '../../button';
66
import Input from '../../input';
@@ -605,4 +605,152 @@ describe('Form List 组件测试', () => {
605605
await mockTimeout();
606606
expect(queryByText('用户名必填')).not.toBeTruthy();
607607
});
608+
609+
test('FormList with shouldUpdate', async () => {
610+
const TestView = () => {
611+
const [form] = Form.useForm();
612+
613+
const nestedInitialData = {
614+
services: [
615+
{
616+
modelName: 'modelA',
617+
routes: [
618+
{ type: 'weight', weight: 50, abtest: 'cid' },
619+
{ type: 'abtest', weight: 30, abtest: 'uid' },
620+
],
621+
},
622+
],
623+
};
624+
625+
return (
626+
<Form form={form} initialData={nestedInitialData}>
627+
<FormList name="services">
628+
{(fields) => (
629+
<>
630+
{fields.map(({ key, name: serviceName }) => (
631+
<div key={key}>
632+
<FormList name={[serviceName, 'routes']}>
633+
{(routeFields, { add: addRoute }) => (
634+
<div>
635+
{routeFields.map((f) => (
636+
<div key={f.key} data-route-index={f.name}>
637+
<FormItem name={[f.name, 'type']} label="类型">
638+
<Input placeholder={`route-type-${serviceName}-${f.name}`} />
639+
</FormItem>
640+
<FormItem
641+
shouldUpdate={(p, n) =>
642+
p.services?.[serviceName]?.routes?.[f.name]?.type !==
643+
n.services?.[serviceName]?.routes?.[f.name]?.type
644+
}
645+
>
646+
{({ getFieldValue }) => {
647+
const type = getFieldValue(['services', serviceName, 'routes', f.name, 'type']);
648+
if (type === 'weight') {
649+
return (
650+
<FormItem key={type} name={[f.name, 'weight']} label="权重">
651+
<Input placeholder={`route-weight-${serviceName}-${f.name}`} />
652+
</FormItem>
653+
);
654+
}
655+
if (type === 'abtest') {
656+
return (
657+
<FormItem key={type} name={[f.name, 'abtest']} label="分流Key">
658+
<Input placeholder={`route-abtest-${serviceName}-${f.name}`} />
659+
</FormItem>
660+
);
661+
}
662+
return null;
663+
}}
664+
</FormItem>
665+
</div>
666+
))}
667+
<Button id={`test-add-route-${serviceName}`} onClick={() => addRoute()}>
668+
新增路由
669+
</Button>
670+
</div>
671+
)}
672+
</FormList>
673+
</div>
674+
))}
675+
</>
676+
)}
677+
</FormList>
678+
</Form>
679+
);
680+
};
681+
682+
const { container, getByPlaceholderText } = render(<TestView />);
683+
684+
// 第一个 route
685+
expect((getByPlaceholderText('route-type-0-0') as HTMLInputElement).value).toBe('weight');
686+
expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50');
687+
// 切换到 abtest
688+
const typeInput0 = getByPlaceholderText('route-type-0-0') as HTMLInputElement;
689+
fireEvent.change(typeInput0, { target: { value: 'abtest' } });
690+
await mockTimeout();
691+
expect((getByPlaceholderText('route-abtest-0-0') as HTMLInputElement).value).toBe('cid');
692+
expect(container.querySelector('[placeholder="route-weight-0-0"]')).toBeFalsy();
693+
// 切换回 weight
694+
fireEvent.change(typeInput0, { target: { value: 'weight' } });
695+
await mockTimeout();
696+
expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50');
697+
698+
// 第二个 route
699+
expect((getByPlaceholderText('route-type-0-1') as HTMLInputElement).value).toBe('abtest');
700+
expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid');
701+
// 切换到 weight
702+
const typeInput1 = getByPlaceholderText('route-type-0-1') as HTMLInputElement;
703+
fireEvent.change(typeInput1, { target: { value: 'weight' } });
704+
await mockTimeout();
705+
expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('30');
706+
expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy();
707+
// 切换回 abtest
708+
fireEvent.change(typeInput1, { target: { value: 'abtest' } });
709+
await mockTimeout();
710+
expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid');
711+
712+
// 添加新的 route(没有初始数据)
713+
const addRouteBtn = container.querySelector('#test-add-route-0');
714+
fireEvent.click(addRouteBtn);
715+
await mockTimeout();
716+
const newTypeInput = getByPlaceholderText('route-type-0-2') as HTMLInputElement;
717+
expect(newTypeInput).toBeTruthy();
718+
// 新添加的项,type 初始应该为空
719+
expect(newTypeInput.value).toBe('');
720+
721+
// 设置新 route 的 type 为 weight
722+
fireEvent.change(newTypeInput, { target: { value: 'weight' } });
723+
await mockTimeout();
724+
725+
const newWeightInput = getByPlaceholderText('route-weight-0-2') as HTMLInputElement;
726+
expect(newWeightInput).toBeTruthy();
727+
// 新添加的项,weight 初始应该为空
728+
expect(newWeightInput.value).toBe('');
729+
730+
// 切换到 abtest
731+
fireEvent.change(newTypeInput, { target: { value: 'abtest' } });
732+
await mockTimeout();
733+
const newAbtestInput = getByPlaceholderText('route-abtest-0-2') as HTMLInputElement;
734+
expect(newAbtestInput).toBeTruthy();
735+
expect(newAbtestInput.value).toBe('');
736+
// 设置值
737+
fireEvent.change(newAbtestInput, { target: { value: 'new-key' } });
738+
await mockTimeout();
739+
expect(newAbtestInput.value).toBe('new-key');
740+
741+
// 切换回 weight
742+
fireEvent.change(newTypeInput, { target: { value: 'weight' } });
743+
expect(container.querySelector('[placeholder="route-abtest-0-2"]')).toBeFalsy();
744+
await mockTimeout();
745+
// weight 重置为空
746+
const weightInputAgain = getByPlaceholderText('route-weight-0-2') as HTMLInputElement;
747+
expect(weightInputAgain.value).toBe('');
748+
749+
// 切换回 abtest
750+
fireEvent.change(newTypeInput, { target: { value: 'abtest' } });
751+
await mockTimeout();
752+
// abtest 重置为空
753+
const abtestInputAgain = getByPlaceholderText('route-abtest-0-2') as HTMLInputElement;
754+
expect(abtestInputAgain.value).toBe('');
755+
});
608756
});

packages/components/form/_example/form-field-linkage.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
2-
import { Form, Radio, Button } from 'tdesign-react';
2+
import { Button, Form, Radio } from 'tdesign-react';
33

44
const { FormItem } = Form;
55

66
export default function FormExample() {
77
const [form] = Form.useForm();
8-
const setMessage = () => {
8+
9+
const applyColdPreset = () => {
910
form.setFieldsValue({
1011
type: 'cold',
1112
ice: '1',
@@ -15,20 +16,31 @@ export default function FormExample() {
1516
return (
1617
<Form form={form} colon labelWidth={100}>
1718
<FormItem label="类型" name="type" initialData="hot">
18-
<Radio.Group>
19-
<Radio value="hot">热饮</Radio>
20-
<Radio value="cold">冷饮</Radio>
19+
<Radio.Group variant="default-filled">
20+
<Radio.Button value="hot">热饮</Radio.Button>
21+
<Radio.Button value="cold">冷饮</Radio.Button>
2122
</Radio.Group>
2223
</FormItem>
2324
<FormItem shouldUpdate={(prev, next) => prev.type !== next.type}>
2425
{({ getFieldValue }) => {
25-
if (getFieldValue('type') === 'cold') {
26+
const type = getFieldValue('type');
27+
if (type === 'cold') {
28+
return (
29+
<FormItem label="冰量" key={type} name="ice">
30+
<Radio.Group>
31+
<Radio value="normal">正常冰</Radio>
32+
<Radio value="less">少冰</Radio>
33+
<Radio value="none">去冰</Radio>
34+
</Radio.Group>
35+
</FormItem>
36+
);
37+
}
38+
if (type === 'hot') {
2639
return (
27-
<FormItem label="冰量" key="ice" name="ice">
40+
<FormItem label="温度" key={type} name="heat">
2841
<Radio.Group>
29-
<Radio value="0">正常冰</Radio>
30-
<Radio value="1">少冰</Radio>
31-
<Radio value="2">去冰</Radio>
42+
<Radio value="high"></Radio>
43+
<Radio value="warm">常温</Radio>
3244
</Radio.Group>
3345
</FormItem>
3446
);
@@ -38,7 +50,7 @@ export default function FormExample() {
3850
</FormItem>
3951

4052
<FormItem style={{ marginLeft: 100 }}>
41-
<Button onClick={setMessage}>选择冷饮-少冰</Button>
53+
<Button onClick={applyColdPreset}>选择冷饮-少冰</Button>
4254
</FormItem>
4355
</Form>
4456
);

packages/components/form/form.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
### 字段联动的表单
1616

17-
在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 `shouldUpdate` 修改 `FormItem` 的更新逻辑。
17+
在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 `shouldUpdate` 修改 `FormItem` 的更新逻辑,同时要给具体的 `FormItem` 添加不同的 `key`
1818

1919
{{ form-field-linkage }}
2020

packages/components/form/hooks/useFormItemInitialData.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ export default function useFormItemInitialData(name: NamePath, fullPath: NamePat
7676
return initialData;
7777
}
7878

79-
if (name && formListInitialData?.length) {
80-
const defaultInitialData = get(formListInitialData, name);
79+
if (Array.isArray(name) && formListInitialData?.length) {
80+
let defaultInitialData;
81+
const [index, ...relativePath] = name;
82+
if (formListInitialData[index]) {
83+
defaultInitialData = get(formListInitialData[index], relativePath);
84+
}
8185
if (typeof defaultInitialData !== 'undefined') return defaultInitialData;
8286
}
8387

0 commit comments

Comments
 (0)