0%

介绍

今天我们一起来学习一下Ant Design(简称AntD)中的AutoComplete组件。AutoComplete组件是一个非常实用的输入框组件,它可以根据用户输入的内容,动态地显示匹配的选项,提升用户体验。为什么要学习这个组件呢?因为我之前一直没有使用过它,而且总是将这个组件和Select组件搞混,一直以为他们干的是一件事,今天就测底解决这个疑惑。

首先上结论,看看AutoComplete和Select的区别:

  • AutoComplete:主要用于输入和选择,用户可以输入部分内容,组件会根据输入内容动态显示匹配的选项。
  • Select:主要用于选择,用户只能从下拉列表中选择一个选项,不能输入其他内容(除非定制Select的行为)。

也就是说Select组件是在一组固定的值中选择一个,而AutoComplete组件则是可以输入内容并从动态生成的选项中选择。AutoComplete的作用主要是做辅助输入。

如果大家不明白AutoComplete是如何工作的,可以尝试去百度、必应或者谷歌等搜索引擎随便搜点东西,当我们在搜索框中输入内容时,搜索引擎会根据我们输入的内容,动态地显示匹配的搜索建议,这就是AutoComplete的典型应用场景。

如何使用AutoComplete组件

下面是一个输入邮箱的例子,当用户输入邮箱前缀时,组件会动态显示匹配的邮箱地址后缀。使用AutoComplete组件非常简单,一般都是相应onSearch事件,然后根据输入的值动态生成选项。

在下面的代码中,我们定义了一个handleSearch函数,当用户输入内容时,这个函数会被调用。我们根据输入的值动态生成选项,并通过setOptions函数将这些选项传递给AutoComplete组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';
import { AutoComplete } from 'antd';
import type { AutoCompleteProps } from 'antd';

const App: React.FC = () => {
const [options, setOptions] = React.useState<AutoCompleteProps['options']>([]);

const handleSearch = (value: string) => {
setOptions(() => {
if (!value || value.includes('@')) {
return [];
}

return ['gmail.com', '163.com', 'qq.com'].map((domain) => ({
label: `${value}@${domain}`,
value: `${value}@${domain}`,
}));
});
};

return (
<AutoComplete
style={{ width: 200 }}
onSearch={handleSearch}
placeholder="input here"
options={options}
/>
);
};

export default App;

大小写无关的搜索

如果要实现大小写无关的搜索,可以响应filterOption方法。

1
2
3
4
5
6
7
8
9
10
<AutoComplete
style={{ width: 200 }}
onSearch={handleSearch}
placeholder="input here"
options={options}
filterOption={(inputValue, option) =>
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
}
/>

典型的应用场景

  1. 搜索建议,如搜索引擎的搜索框。
  2. 表单输入,比如要填写邮箱地址时,我们可以将常见的邮箱地址后缀(如@gmail.com, @yahoo.com等)作为选项,用户输入邮箱前缀时,组件会动态显示匹配的邮箱地址后缀。

总之,AutoComplete组件的核心作用是输入提示,在选项不固定的时候非常有用。

CSS属性选择器

下面是一个属性选择器的示例,选择具有属性title且其值为"Sports"的元素

1
const elements = document.querySelectorAll('[title="Sports"]');

JS中如何交换两个变量?

可以使用ES6的解构赋值来交换变量的值。

1
2
3
4
5
6
7
8
9
10
11
let a = 1;
let b = 2;

// 交换前
console.log('交换前:', a, b);

// 交换
[a, b] = [b, a];

// 交换后
console.log('交换后:', a, b);

React中如何使用条件渲染

所谓条件渲染是指满足某个条件时才渲染某个组件。比如当用户登录后,可以渲染用户的个人信息页面,否则渲染登录页面。

1
{isLoggedIn ? <Home /> : <Login />}

Ant Design中Form.Item的valuePropName属性

通常来说,Ant Design中的控件,比如InputSelect等,其值都是通过value属性来控制的,但是有一些控件没有value属性,比如SwitchCheckbox等。这时就需要使用valuePropName属性来指定控件的值属性。否则提交的时候无法取得对应的控件值。

看下面的代码,这是一个简单的登录框,包含用户名、密码、记住我和提交按钮,你会发现,无论记住我是否选中,当点击提交按钮时,打印的结果中的remember都是false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Login: React.FC = () => {
const handleSubmit = async (values: LoginParams) => {
console.log('登录信息:', values);
};
return (
<Form
name="login"
initialValues={{ remember: true }}
onFinish={handleSubmit}
>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password placeholder="密码" />
</Form.Item>
<Form.Item name="remember" noStyle>
<Checkbox>记住我</Checkbox>
</Form.Item>
<Form.Item>
<Button htmlType="submit">登录</Button>
</Form.Item>
</Form>
);
};

原因就是Checkbox不是使用value来记录它的值,它使用的是checked(html底层如此,详情看这里)

解决办法,只需要给Form.Item添加valuePropName="checked"属性即可,代码如下,这时我们再点击提交按钮,打印的结果中remember的值就会根据复选框的选中状态而变化了。

1
2
3
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>记住我</Checkbox>
</Form.Item>

还有哪些控件需要使用valuePropName属性呢?比如RateSlider等控件也需要使用valuePropName来指定值属性。

  • Switch
  • Rate
  • Slider
  • Transfer

Form.Item的工作原理

使用Ant Design中的Form.Item包裹组件,且在Form.Item中指定name属性后,Form.Item会自动将组件的值和Form进行绑定。也就是说,组件的值会自动同步到Form中,Form的值也会自动同步到组件中。

  • 不能使用组件的onChange收集value,应该使用Form级别的onValuesChange来收集表单的值变化。

  • 不能使用defaultValue属性来设置初始值,应该使用initialValues属性来设置。

  • 不能在Form.Item中直接使用value属性来设置组件的值,应该使用form.setFieldsValue来设置组件的值。

  • 使用htmlType='submit'的按钮来提交表单,点击此按钮会触发表单的onFinish事件。onFinish函数中的values参数会接收到表单的所有值。

Form.Item如何寻找内部控件

本以为Form.Item会自动寻找第一个子元素作为控件。但是第三个列子说明不是这样的。

这样不可以,username的值始终是undefined

1
2
3
4
<Form.Item name="username">
<div>This is title</div>
<Input placeholder="用户名" />
</Form.Item>

这样可以:

1
2
3
4
5
<Form.Item name="username">
<div>
<Input placeholder="用户名" />
</div>
</Form.Item>

这样也可以

1
2
3
4
5
6
<Form.Item name="username">
<div>
<div>This is title</div>
<Input placeholder="用户名" />
</div>
</Form.Item>

介绍

我们在使用Ant Design中的Table组件时,经常会遇到文本过长导致内容溢出的情况,今天我们来看看如何使用Ellipsis text来解决这个问题,所谓Ellipsis text,就是当文本内容超出指定长度时,显示省略号,为了功能的完整性,还需要添加一个鼠标悬停显示完整文本的功能,效果如下。

ellipsis-text

CSS中的Ellipsis

其实Ellipsis text是CSS中的一个功能,比如下面定义了一个ellipsis-text样式,当文本长度超过200px时,会自动显示省略号。

1
2
3
4
5
6
.ellipsis-text {
width: 200px; /* 文本宽度200像素 */
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 末尾显示... */
}

下面来应用这个样式,其中title属性用来显示tooltip,其值和p标签的内容完全相同。

1
2
3
<p class="single-line" title="This is a very long line of text">
This is a very long line of text
</p>

Ant Design Table组件中的Ellipsis

Ant Design的Table组件中,Column组件提供了一个ellipsis属性来实现这个功能,非常的方便,只要在需要的地方添加ellipsis属性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Table } from 'antd';

const columns = [
// ...
{
title: 'Description',
dataIndex: 'description',
ellipsis: true, // 这里开启了ellipsis功能
},
];

const data = [
{
key: '1',
name: 'John Brown',
description: 'This is a very long description that will be truncated',
},
{
key: '2',
name: 'Jim Green',
description: 'This is another very long description that will be truncated',
},
];

const App = () => (
<Table columns={columns} dataSource={data} />
);

就这么简单,但是这个功能有些限制,对于纯文本内容来说还可以,但是如果你的列有自定义渲染器的话,这个功能可能会失效,比如我想将description列渲染成link button,在点击的时候显示一个弹框。这时候仅添加ellipsis属性就不够了,我们需要自定义一个组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react';
import { Tooltip } from 'antd';
type EllipsisTextProps = {
children: React.ReactNode;
maxLength?: number;
}
export default function EllipsisText(props: EllipsisTextProps) {
const { children, maxLength = 30 } = props;
const text = typeof children === 'string' ? children : String(children);
if (text.length <= maxLength) {
return <span>{text}</span>;
}
return (
<Tooltip title={text}>
<span
style={{
display: 'inline-block',
maxWidth: `${maxLength}ch`,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{text}
</span>
</Tooltip>
)
}

上面我们自定义了一个EllipsisText组件,在这个组件中首先判断传入的文本是否超过指定长度(30),如果没有超过,那么直接渲染文本,如果超过了,则使用Tooltip组件包裹,并且应用了CSS的ellipsis样式。

使用方法如下,在Table组件的Column中使用自定义的EllipsisText组件。在这里我们将EllipsisText组件放在了一个link button中,这样用户点击按钮时可以触发一些操作,比如显示一个弹框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
title: '描述',
dataIndex: 'description',
key: 'description',
search: false,
ellipsis: true,
render: renderDescription,
},

const renderDescription = (text: ReactNode) => {
return (
<Button type='link' onClick={onClickHandler}>
<EllipsisText>{text}</EllipsisText>
</Button>
);
};

今天就到这里了,祝大家编程愉快,我们下期再见!

8月大杂烩

使用类选择器时,如何指定多个类名?

比如我们想要选择的元素有三个类名classA, classB, classC,那么选择器可以这样写:

1
document.querySelector('.classA.classB.classC')

只要将三个类名连接起来就行,中间不需要加空格。

为什么我的请求在Chrome开发者工具中看不到?

今天和同事调试代码时发现一个奇怪的现象,有一个api调用,在浏览器开发者工具中的network面板中竟然看不到它的response,研究了半天也没找到原因,后来发现是因为调用api之后,使用了window.location.href = path - 这句代码会导致页面进行一个强制刷新,所以api调用的记录也被刷掉了,response面板中也看不到。

如果要修复这个问题,可以使用useNavigate代替,代码如下:

1
2
3
4
5
import { useNavigate } from 'react-router-dom';

const navigate = useNavigate();
const path = xxx;
navigate(path, {replace: true});

使用JSON.parse时一定要注意异常处理

一个好的习惯是,始终用try-catch包裹JSON.parse,以防止解析失败导致的错误。例如:

1
2
3
4
5
6
try {
const data = JSON.parse(jsonString);
// 处理解析后的数据
} catch (error) {
console.error('JSON解析失败:', error);
}

JSON.parse非常容易出错,当传入的参数是空字符串, undefined或不合法的JSON字符串时,都会抛出异常。因此,始终使用try-catch来捕获这些异常是一个好的习惯。

1
2
3
JSON.parse(undefined); // Uncaught SyntaxError: "undefined" is not valid JSON
JSON.parse(''); // Uncaught SyntaxError: Unexpected end of JSON input
JSON.parse(null); // null

要点:

  1. 永远不要传递undefinednull空字符串JSON.parse
  2. 如果要解析空字符串,应该用JSON.parse('""')来代替。
  3. 虽然null不会直接导致异常,但解析结果是null,这可能导致后续代码出错,因为我们总要访问解析结果中的字段。

如何对指定目录运行单元测试?

随着项目的进行,测试用例会越来越多,跑一次完整的测试需要的时间也水涨船高,如果我们只修改了某一个功能,那么可以只对这个功能进行单元测试。

下面的命令只对user目录运行单元测试,这个命令有个前提,那就是你的测试用例都放到src/__tests__/目录下。

1
npm run test -- src/__tests__/pages/user

如果你的测试用例是跟随业务功能代码的结构放置的,那么可以使用以下命令:

1
npm run test -- src/pages/user

也就是说,无论使用哪种方式,都要保证指定的目录下有对应的测试用例。

Ant Design Table中如何隐藏某一列?

1
2
3
4
5
6
7
8
const columns = [
// ...
{
title: '作者',
dataIndex: 'author',
hidden: someCondition, // someCondition = true时隐藏该列
},
];

‘Button’ refers to a value, but is being used as a type here. Did you mean ‘typeof Button’?

组件代码如下,就这么简单的一个组件,竟然能出现上面的问题。真是服了!

1
2
3
4
5
import { Button } from 'antd';

export default function Users() {
return <Button>提交</Button>;
}

仔细检查了半天也没有发现问题所在,后来一看是文件名不对!React组件文件的扩展名应该是jsx或者tsx,而不是js或者ts。而我这个组件的扩展名恰恰是.ts,将它改为.tsx后问题就解决了。

限制输入框文本长度

有两种方式,一种是在Form.Item中的rules中添加max属性,另一种是在Input组件中添加maxLength属性。

在Form.Item中添加max属性

这种方式的好处是,如果超出长度限制,会自动显示提示信息,但是用户仍然可以继续输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
{
max: 10,
message: `Max length is 10`,
}
]}
>
{inputNode}
</Form.Item>

在Input组件中添加maxLength属性

这种方式的好处是,用户输入超过长度限制时,会被直接阻止输入。弊端是没有提示信息。

1
<Input maxLength={10} />

如果既要限制输入长度,又要提供提示信息,可以结合使用两种方式,在实际使用中,可以将MaxLength提取成公共变量,这样可以避免重复代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const MaxLength = 10;

<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
max: MaxLength,
message: `Max length is ${MaxLength}`,
}
]}
>
<Input maxLength={MaxLength} />
</Form.Item>

使用默认参数代替||运算

不知道大家是否见到过如下这样的代码。

1
2
3
4
function foo(userList: string[]) {
const users = userList || [];
return users.map(user => user.toUpperCase());
}

这段代码中使用|| []进行了一种防御性编程,因为传入的userList可能是undefined。使用|| []以后,就可以避免在undefined上调用map函数出错,其实我们可以使用javascript的默认参数来实现这个功能。

1
2
3
function foo(userList: string[] = []) {
return userList.map(user => user.toUpperCase());
}

需要注意的是,默认参数只能处理undefined, 对于null或其他假值(如0''等)是无法替代的。因此在使用默认参数时,需要确保传入的参数是undefined。否则还是用||比较保险一些。

介绍

git是每天都要用的工具,在使用过程中,难免遇到一些问题,姑且罗列一下供日后参考

Fatal: reference is not a tree: xxxx

这里xxx对应一个commit id,这是昨天遇到的一个问题,因为同事的代码有问题需要大家研究一下,但是这些代码还在develop分支上,所以我就使用下面的命令来对指定的commit进行checkout操作,于是就出现了上面的问题。

1
git checkout -b my-branch <commit_id>

经过一番研究,发现原因是没有更新代码,因为我是在自己的分支上运行这个命令的,此时我的分支并没有包含这个commit。解决方法就是先切换到develop分支,然后执行git pull命令更新代码,最后再运行上面这个命令就行了。

介绍

最近在做Code review的时候,发现很多使用Array.prototype.push的代码,都可以使用Array.prototype.map来重构。使用map可以使代码更简洁、更易读。

示例

下面是一个使用push来返回User对象的例子,逻辑很简单,根据传入的userList,返回一个简化的User对象列表。函数内部首先定义了一个空数组result,然后使用forEach遍历userList,如果满足条件,就将简化的User对象推入result数组中。最后返回这个result数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
const buildUserList = (userList: User[]) => {
let result: SimplifiedUser[] = [];
userList.forEach((user: User) => {
if (user.accountCode === '' || user.accountCode === null) {
result.push({
department: user.department,
userCode: user.username,
userName: user.fullName
});
}
});
return result;
}

这种写法很常见,也算中规中矩吧,但是有一些弊端:

  1. 需要额外维护一个结果数组result
  2. 数据的过滤和转换混合在了一起。
  3. 属于命令式编程的写法。

现在我们使用map来改写一下,对于第一个if判断,我们可以使用filter来提取满足条件的用户,然后使用map来转换成简化的User对象。这样就可以直接返回一个新的数组,而不需要手动创建和推入元素。

1
2
3
4
5
6
7
8
9
const buildUserList = (userList: User[]) => {
return userList
.filter((user: User) => user.accountCode === '' || user.accountCode === null)
.map((user: User) => ({
department: user.department,
userCode: user.username,
userName: user.fullName
}));
};

现在代码简洁了不少,但是filter中的条件有点长,我们可以将其提取成一个函数来提高可读性。

1
2
3
4
5
6
7
8
9
10
11
12
13
const isUserWithoutAccountCode = (user: User) => {
return user.accountCode === '' || user.accountCode === null;
};

const buildUserList = (userList: User[]) => {
return userList
.filter(isUserWithoutAccountCode)
.map((user: User) => ({
department: user.department,
userCode: user.username,
userName: user.fullName
}));
};

甚至map中的转换逻辑也可以提取成一个函数,这样代码会更清晰。

1
2
3
4
5
const simplifyUser = (user: User): SimplifiedUser => ({
department: user.department,
userCode: user.username,
userName: user.fullName
});

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const isUserWithoutAccountCode = (user: User) => {
return user.accountCode === '' || user.accountCode === null;
};

const simplifyUser = (user: User): SimplifiedUser => ({
department: user.department,
userCode: user.username,
userName: user.fullName
});

const buildUserList = (userList: User[]) => {
return userList
.filter(isUserWithoutAccountCode)
.map(simplifyUser);
};

重构后的代码有如下好处:

  1. 逻辑清晰,简洁易懂,读起来更加顺畅。
  2. 数据的过滤和转换分离,职责单一,方便测试和维护。
  3. 使用了函数式编程的风格,符合现代JavaScript的编程习惯。

总结

什么是好代码?大概就是这个样子吧,为什么有些人的代码读起来通俗易懂,而有些人的代码读起来却像天书?这就是差距。我喜欢将其成为代码气质,我们一定要培养出自己的代码气质。

昨天组里聚餐,喝了几瓶啤酒,总算睡了一个好觉,这几天隔壁的猫天天叫,我每晚只能睡四五个小时,白天困得不行,已经好几天没有提PR了,下周要加油了!

介绍

今天写代码的时候遇到如下错误:

1
Uncaught ReferenceError: rawData.some is not a function

为了便于理解,我截了一个图:
ts-xxx-is-not-a-function
这个错误也算是typescript中比较常见的错误了,以前曾多次遇到,但是从未认真思考过,今天就来仔细分析一下它,如果对一个错误了解的不够深,就会导致你一直惧怕它,一直想回避它,所以每次遇到这个错误的时候,第一反应就是要去网上搜索解决办法,而忽略了问题产生的根源。

错误分析

这个错误分为两个部分,第一部分阐明了错误的原因,请看第一个红框中的内容:

1
Uncaught ReferenceError: rawData.some is not a function

这里的rawData是一个变量名,some是一个函数名,我们都知道some是js中数组对象上的一个方法,它的作用是判断数组中是否有满足条件的元素,如果有则返回true,否则返回false。这个错误的意思是:rawData不是一个数组,所以不能调用some方法。

从这个错误的调用栈可以看到,都是框架底层的代码,并没有应用级别的代码,而框架级别的代码是不可能出现类型错误的,所以这个错误一定是应用级别的代码引起的,继续往下看。

来到第二个红框中的内容:这是错误的第二个部分,它反映的是错误发生的位置,这里面Users就是业务代码对应的组件了,而Users上面一行是Table组件,也就是Ant Design中的表格组件。这说明我们的Users组件在使用Table组件时,导致了这个错误。

到这里,基本可以推断出错误的原因了,那就是在一个需要数组的地方传入了一个非数组的变量,而框架内部代码在调用some方法时,发现这个变量不是数组,所以抛出了这个错误。

于是来到Users组件的代码,看看是哪里传入了非数组的变量。Table组件用到数组变量的地方基本就是两个,一个是columns,一个是dataSource。讲过检查,发现是dataSource传入了一个非数组的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Table from 'antd/es/table';
import { useEffect, useState } from 'react';
import { User } from './User';
import { fetchUsers } from './UserAPI';

export default function Users() {
const [tableData, setTableData] = useState<User[]>([]);

const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
// ...
];

useEffect(() => {
fetchUsers().then((res) => {
setTableData(res.data); // res.data不是数组!
});
}, []);

return <Table dataSource={tableData} columns={columns}></Table>;
}

由于疏忽,fetchUsers返回的res.data并不是一个数组,而是一个对象,所以在传入Table组件时,导致了这个错误。
解决这个问题很简单,只需要修改fetchUsers函数,确保它返回一个数组即可。

至此,问题完美解决,更重要的是,我们对于这个错误的理解也更加深入了。以后再遇到类似的错误时,就不会再感到困惑了。

今天就到这里了,我们明天再见。

介绍

为什么我的Modal还没打开,API就开始调用了?不知道大家在使用Ant Design的Modal组件时,是否遇到过这个问题?在某些情况下,我们希望在Modal打开后才开始执行某些操作,比如发起API请求。但是实际的情况时,Modal还没有打开,API请求就已经发起了。今天我们就来揭开这个问题的谜底。

一个例子

先定义一个Modal组件,用来编辑用户信息。这个Modal组件包含如下属性:

  • isVisible属性,用来控制Modal是否可见。
  • userId属性,用来指定要编辑的用户ID。
  • onOk属性,用来提交表单数据。
  • onCancel属性,用来取消编辑操作。
1
2
3
4
5
6
type UserEditModalProps = {
isVisible: boolean;
userId: number;
onOk: () => void;
onCancel: () => void;
};

接下来,我们定义Modal组件的内容,我们需要在Modal组件中添加一个Form组件,来展示待编辑的用户信息。

组件加载时,我们需要根据传入的userId属性,调用后端api获取用户详情,并使用form.setFieldsValue方法将获取到的用户信息填充到Form中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export default function UserEditModal(props: UserEditModalProps) {
const {isVisible, userId, onOk, onCancel} = props;
const [form] = useForm();

useEffect(() => {
if (userId) {
fetch(`/api/user/detail/${userId}`).then(async (res) => {
const data = await res.json();
form.setFieldsValue(data);
});
}
}, [userId]);

return (
<Modal
open={isVisible}
title="Edit user"
onOk={onOk}
onCancel={onCancel}>
<Form form={form}>
<Form.Item label='User name' name='userName'>
<Input placeholder='Enter user name' />
</Form.Item>
<Form.Item label='Email' name='email'>
<Input placeholder='Enter email' />
</Form.Item>
<Form.Item label='Phone' name='phone'>
<Input placeholder='Enter phone number' />
</Form.Item>
</Form>
</Modal>
);
}

调用Modal组件

现在我们可以在其他组件中调用这个Modal组件了。调用的方式很简单:

  1. UserEditModal组件放在父组件中(通常放到组件的末尾)。
  2. 在父组件中定义一个状态变量isEditModalVisible,用来控制Modal的显示和隐藏。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
const [isEditModalVisible, setIsEditModalVisible] = useState(false);

return (
<>
{/* 其他组件内容 */}
<UserEditModal
isVisible={isEditModalVisible}
userId={selectedUser?.id}
onOk={onEditOK}
onCancel={onEditCancel}/>
</>
);

当我们需要显示Modal时,只需要将isEditModalVisible设置为true即可。

1
2
3
4
const onEditUser = (userId: number) => {
setSelectedUser(users.find(user => user.id === userId));
setIsEditModalVisible(true);
};

当我们需要隐藏Modal时,只需要将isEditModalVisible设置为false即可。

1
2
3
const onEditCancel = () => {
setIsEditModalVisible(false);
};

运行程序

此时,我们运行程序,你会发现,即使Modal还没有打开,API请求已经发起了。这是因为useEffect钩子在组件加载时就会执行,而不管Modal是否可见。

为了解决这个问题,我们需要在useEffect中添加一个条件判断,只有当Modal可见时才发起API请求。

1
2
3
4
5
useEffect(() => {
if (isVisible && userId) {
fetch(...);
}
}, [isVisible, userId]);

其实上面的代码是双保险,只有当Modal可见且userId存在时,才会发起API请求。这样就可以确保在Modal打开后才开始执行相关操作。

总结

使用Ant Design中的Modal组件时,一定要在组件可见时在发起API请求。通过在useEffect中添加条件判断,我们可以确保在Modal打开后才开始执行相关操作。从而避免不必要的API请求。