0%

介绍

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

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请求。

不要向子组件传递form对象了,用useFormInstance代替之

在Ant Design的Form组件中,之前我们常常会向子组件传递form对象来进行表单操作。但是从Ant Design 4.0开始,推荐使用useFormInstance来获取当前表单实例,这样可以避免不必要的性能开销。

多个Form之间协同操作

使用FormProvider可以让多个Form之间协同操作,比如在一个父组件中管理多个子组件的表单状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Form, FormProvider, useForm } from 'antd';
import ChildComponent1 from './ChildComponent1';
import ChildComponent2 from './ChildComponent2';
export default function ParentComponent() {
const [form1] = useForm();
const [form2] = useForm();

return (
<FormProvider forms={{ form1, form2 }}>
<ChildComponent1 />
<ChildComponent2 />
</FormProvider>
);
}

动态表单

Form和modal的配合使用

动态显示Form组件

使用state变量或者shouldUpdate函数来控制Form组件的显示和隐藏。两者有何区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
state && (
<Form>
{/* 表单内容 */}
</Form>
)
}

const shouldUpdate = (prevValues, currentValues) => {
return prevValues.someField !== currentValues.someField;
};
<Form shouldUpdate={shouldUpdate}>
{/* 表单内容 */}
</Form>

Form样式漫谈

三种布局方式

Ant Design的Form组件提供了三种布局方式:水平布局、垂直布局和行内布局。

label和组件的位置

大部分情况label在前,组件在后,比较特殊的是Radio和checkbox组件,它们的label在后面。把标签写在label中时,label会在组件的前面。对应下面username和password的输入框。把标签写在组件中时,label会在组件的后面。对应下面的remember和agreement复选框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Form.Item label="用户名" name="username">
<Input />
</Form.Item>

<Form.Item label="密码" name="password">
<Input.Password />
</Form.Item>

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

<Form.Item name="agreement" valuePropName="checked">
<Radio>我已阅读并同意<a href="#">用户协议</a></Radio>
</Form.Item>

Form.Item中label和组件的宽度

在Ant Design的Form.Item中,label和组件的宽度可以通过labelColwrapperCol属性来控制。。

1
2
3
4
5
6
7
8
<Form.Item
label="用户名"
name="username"
labelCol={{ span: 8 }} // label占8列
wrapperCol={{ span: 16 }} // 组件占16列
>
<Input />
</Form.Item>