react-ant-design-component-autocomplete
介绍
今天我们一起来学习一下Ant Design(简称AntD)中的AutoComplete组件。AutoComplete组件是一个非常实用的输入框组件,它可以根据用户输入的内容,动态地显示匹配的选项,提升用户体验。为什么要学习这个组件呢?因为我之前一直没有使用过它,而且总是将这个组件和Select
组件搞混,一直以为他们干的是一件事,今天就测底解决这个疑惑。
首先上结论,看看AutoComplete和Select的区别:
- AutoComplete:主要用于输入和选择,用户可以输入部分内容,组件会根据输入内容动态显示匹配的选项。
- Select:主要用于选择,用户只能从下拉列表中选择一个选项,不能输入其他内容(除非定制Select的行为)。
也就是说Select组件是在一组固定的值中选择一个,而AutoComplete组件则是可以输入内容并从动态生成的选项中选择。AutoComplete的作用主要是做辅助输入。
如果大家不明白AutoComplete是如何工作的,可以尝试去百度、必应或者谷歌等搜索引擎随便搜点东西,当我们在搜索框中输入内容时,搜索引擎会根据我们输入的内容,动态地显示匹配的搜索建议,这就是AutoComplete的典型应用场景。
如何使用AutoComplete组件
下面是一个输入邮箱的例子,当用户输入邮箱前缀时,组件会动态显示匹配的邮箱地址后缀。使用AutoComplete组件非常简单,一般都是相应onSearch事件,然后根据输入的值动态生成选项。
在下面的代码中,我们定义了一个handleSearch
函数,当用户输入内容时,这个函数会被调用。我们根据输入的值动态生成选项,并通过setOptions
函数将这些选项传递给AutoComplete组件。
1 | import React from 'react'; |
大小写无关的搜索
如果要实现大小写无关的搜索,可以响应filterOption
方法。
1 | <AutoComplete |
典型的应用场景
- 搜索建议,如搜索引擎的搜索框。
- 表单输入,比如要填写邮箱地址时,我们可以将常见的邮箱地址后缀(如@gmail.com, @yahoo.com等)作为选项,用户输入邮箱前缀时,组件会动态显示匹配的邮箱地址后缀。
总之,AutoComplete组件的核心作用是输入提示,在选项不固定的时候非常有用。
misc-2025-09
CSS属性选择器
下面是一个属性选择器的示例,选择具有属性title
且其值为"Sports"
的元素
1 | const elements = document.querySelectorAll('[title="Sports"]'); |
JS中如何交换两个变量?
可以使用ES6的解构赋值来交换变量的值。
1 | let a = 1; |
React中如何使用条件渲染
所谓条件渲染是指满足某个条件时才渲染某个组件。比如当用户登录后,可以渲染用户的个人信息页面,否则渲染登录页面。
1 | {isLoggedIn ? <Home /> : <Login />} |
Ant Design中Form.Item的valuePropName属性
通常来说,Ant Design中的控件,比如Input
、Select
等,其值都是通过value
属性来控制的,但是有一些控件没有value
属性,比如Switch
、Checkbox
等。这时就需要使用valuePropName
属性来指定控件的值属性。否则提交的时候无法取得对应的控件值。
看下面的代码,这是一个简单的登录框,包含用户名、密码、记住我和提交按钮,你会发现,无论记住我是否选中,当点击提交按钮时,打印的结果中的remember
都是false
。
1 | const Login: React.FC = () => { |
原因就是Checkbox
不是使用value
来记录它的值,它使用的是checked
(html底层如此,详情看这里)
解决办法,只需要给Form.Item添加valuePropName="checked"
属性即可,代码如下,这时我们再点击提交按钮,打印的结果中remember
的值就会根据复选框的选中状态而变化了。
1 | <Form.Item name="remember" valuePropName="checked" noStyle> |
还有哪些控件需要使用valuePropName
属性呢?比如Rate
、Slider
等控件也需要使用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 | <Form.Item name="username"> |
这样可以:
1 | <Form.Item name="username"> |
这样也可以
1 | <Form.Item name="username"> |
react-ant-design-customize-component-ellipsis-text
介绍
我们在使用Ant Design中的Table组件时,经常会遇到文本过长导致内容溢出的情况,今天我们来看看如何使用Ellipsis text来解决这个问题,所谓Ellipsis text,就是当文本内容超出指定长度时,显示省略号,为了功能的完整性,还需要添加一个鼠标悬停显示完整文本的功能,效果如下。
CSS中的Ellipsis
其实Ellipsis text是CSS中的一个功能,比如下面定义了一个ellipsis-text样式,当文本长度超过200px时,会自动显示省略号。
1 | .ellipsis-text { |
下面来应用这个样式,其中title
属性用来显示tooltip,其值和p标签的内容完全相同。
1 | <p class="single-line" title="This is a very long line of text"> |
Ant Design Table组件中的Ellipsis
Ant Design的Table组件中,Column组件提供了一个ellipsis
属性来实现这个功能,非常的方便,只要在需要的地方添加ellipsis
属性即可。
1 | import { Table } from 'antd'; |
就这么简单,但是这个功能有些限制,对于纯文本内容来说还可以,但是如果你的列有自定义渲染器的话,这个功能可能会失效,比如我想将description列渲染成link button,在点击的时候显示一个弹框。这时候仅添加ellipsis属性就不够了,我们需要自定义一个组件。
1 | import React from 'react'; |
上面我们自定义了一个EllipsisText组件,在这个组件中首先判断传入的文本是否超过指定长度(30),如果没有超过,那么直接渲染文本,如果超过了,则使用Tooltip
组件包裹,并且应用了CSS的ellipsis样式。
使用方法如下,在Table组件的Column中使用自定义的EllipsisText组件。在这里我们将EllipsisText组件放在了一个link button中,这样用户点击按钮时可以触发一些操作,比如显示一个弹框。
1 | { |
今天就到这里了,祝大家编程愉快,我们下期再见!
misc-2025-08
8月大杂烩
使用类选择器时,如何指定多个类名?
比如我们想要选择的元素有三个类名classA, classB, classC,那么选择器可以这样写:
1 | document.querySelector('.classA.classB.classC') |
只要将三个类名连接起来就行,中间不需要加空格。
为什么我的请求在Chrome开发者工具中看不到?
今天和同事调试代码时发现一个奇怪的现象,有一个api调用,在浏览器开发者工具中的network面板中竟然看不到它的response,研究了半天也没找到原因,后来发现是因为调用api之后,使用了window.location.href = path
- 这句代码会导致页面进行一个强制刷新,所以api调用的记录也被刷掉了,response面板中也看不到。
如果要修复这个问题,可以使用useNavigate
代替,代码如下:
1 | import { useNavigate } from 'react-router-dom'; |
使用JSON.parse时一定要注意异常处理
一个好的习惯是,始终用try-catch
包裹JSON.parse
,以防止解析失败导致的错误。例如:
1 | try { |
JSON.parse非常容易出错,当传入的参数是空字符串, undefined
或不合法的JSON字符串时,都会抛出异常。因此,始终使用try-catch
来捕获这些异常是一个好的习惯。
1 | JSON.parse(undefined); // Uncaught SyntaxError: "undefined" is not valid JSON |
要点:
- 永远不要传递
undefined
、null
或空字符串
给JSON.parse
。 - 如果要解析空字符串,应该用
JSON.parse('""')
来代替。 - 虽然
null
不会直接导致异常,但解析结果是null
,这可能导致后续代码出错,因为我们总要访问解析结果中的字段。
如何对指定目录运行单元测试?
随着项目的进行,测试用例会越来越多,跑一次完整的测试需要的时间也水涨船高,如果我们只修改了某一个功能,那么可以只对这个功能进行单元测试。
下面的命令只对user
目录运行单元测试,这个命令有个前提,那就是你的测试用例都放到src/__tests__/
目录下。
1 | npm run test -- src/__tests__/pages/user |
如果你的测试用例是跟随业务功能代码的结构放置的,那么可以使用以下命令:
1 | npm run test -- src/pages/user |
也就是说,无论使用哪种方式,都要保证指定的目录下有对应的测试用例。
Ant Design Table中如何隐藏某一列?
1 | const columns = [ |
‘Button’ refers to a value, but is being used as a type here. Did you mean ‘typeof Button’?
组件代码如下,就这么简单的一个组件,竟然能出现上面的问题。真是服了!
1 | import { Button } from 'antd'; |
仔细检查了半天也没有发现问题所在,后来一看是文件名不对!React组件文件的扩展名应该是jsx
或者tsx
,而不是js
或者ts
。而我这个组件的扩展名恰恰是.ts
,将它改为.tsx
后问题就解决了。
限制输入框文本长度
有两种方式,一种是在Form.Item中的rules中添加max
属性,另一种是在Input组件中添加maxLength
属性。
在Form.Item中添加max属性
这种方式的好处是,如果超出长度限制,会自动显示提示信息,但是用户仍然可以继续输入。
1 | <Form.Item |
在Input组件中添加maxLength属性
这种方式的好处是,用户输入超过长度限制时,会被直接阻止输入。弊端是没有提示信息。
1 | <Input maxLength={10} /> |
如果既要限制输入长度,又要提供提示信息,可以结合使用两种方式,在实际使用中,可以将MaxLength
提取成公共变量,这样可以避免重复代码:
1 | const MaxLength = 10; |
javascript-refactor-use-default-argument-instead-of-or
使用默认参数代替||运算
不知道大家是否见到过如下这样的代码。
1 | function foo(userList: string[]) { |
这段代码中使用|| []
进行了一种防御性编程,因为传入的userList
可能是undefined
。使用|| []
以后,就可以避免在undefined
上调用map
函数出错,其实我们可以使用javascript的默认参数来实现这个功能。
1 | function foo(userList: string[] = []) { |
需要注意的是,默认参数只能处理undefined
, 对于null
或其他假值(如0
、''
等)是无法替代的。因此在使用默认参数时,需要确保传入的参数是undefined
。否则还是用||
比较保险一些。
git_troubleshooting
介绍
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
命令更新代码,最后再运行上面这个命令就行了。
javascript-refactor-use-map-isteadof-push
介绍
最近在做Code review的时候,发现很多使用Array.prototype.push
的代码,都可以使用Array.prototype.map
来重构。使用map
可以使代码更简洁、更易读。
示例
下面是一个使用push
来返回User对象的例子,逻辑很简单,根据传入的userList,返回一个简化的User对象列表。函数内部首先定义了一个空数组result
,然后使用forEach
遍历userList
,如果满足条件,就将简化的User对象推入result
数组中。最后返回这个result
数组。
1 | const buildUserList = (userList: User[]) => { |
这种写法很常见,也算中规中矩吧,但是有一些弊端:
- 需要额外维护一个结果数组
result
。 - 数据的过滤和转换混合在了一起。
- 属于命令式编程的写法。
现在我们使用map来改写一下,对于第一个if判断,我们可以使用filter
来提取满足条件的用户,然后使用map
来转换成简化的User对象。这样就可以直接返回一个新的数组,而不需要手动创建和推入元素。
1 | const buildUserList = (userList: User[]) => { |
现在代码简洁了不少,但是filter
中的条件有点长,我们可以将其提取成一个函数来提高可读性。
1 | const isUserWithoutAccountCode = (user: User) => { |
甚至map
中的转换逻辑也可以提取成一个函数,这样代码会更清晰。
1 | const simplifyUser = (user: User): SimplifiedUser => ({ |
完整的代码如下:
1 | const isUserWithoutAccountCode = (user: User) => { |
重构后的代码有如下好处:
- 逻辑清晰,简洁易懂,读起来更加顺畅。
- 数据的过滤和转换分离,职责单一,方便测试和维护。
- 使用了函数式编程的风格,符合现代JavaScript的编程习惯。
总结
什么是好代码?大概就是这个样子吧,为什么有些人的代码读起来通俗易懂,而有些人的代码读起来却像天书?这就是差距。我喜欢将其成为代码气质
,我们一定要培养出自己的代码气质。
昨天组里聚餐,喝了几瓶啤酒,总算睡了一个好觉,这几天隔壁的猫天天叫,我每晚只能睡四五个小时,白天困得不行,已经好几天没有提PR了,下周要加油了!
typescript-xxx-is-not-a-function
介绍
今天写代码的时候遇到如下错误:
1 | Uncaught ReferenceError: rawData.some 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 | import Table from 'antd/es/table'; |
由于疏忽,fetchUsers返回的res.data
并不是一个数组,而是一个对象,所以在传入Table
组件时,导致了这个错误。
解决这个问题很简单,只需要修改fetchUsers
函数,确保它返回一个数组即可。
至此,问题完美解决,更重要的是,我们对于这个错误的理解也更加深入了。以后再遇到类似的错误时,就不会再感到困惑了。
今天就到这里了,我们明天再见。
react-ant-design-modal-case1
介绍
为什么我的Modal还没打开,API就开始调用了?不知道大家在使用Ant Design的Modal组件时,是否遇到过这个问题?在某些情况下,我们希望在Modal打开后才开始执行某些操作,比如发起API请求。但是实际的情况时,Modal还没有打开,API请求就已经发起了。今天我们就来揭开这个问题的谜底。
一个例子
先定义一个Modal组件,用来编辑用户信息。这个Modal组件包含如下属性:
isVisible
属性,用来控制Modal是否可见。userId
属性,用来指定要编辑的用户ID。onOk
属性,用来提交表单数据。onCancel
属性,用来取消编辑操作。
1 | type UserEditModalProps = { |
接下来,我们定义Modal组件的内容,我们需要在Modal组件中添加一个Form组件,来展示待编辑的用户信息。
组件加载时,我们需要根据传入的userId
属性,调用后端api获取用户详情,并使用form.setFieldsValue
方法将获取到的用户信息填充到Form中。
1 | export default function UserEditModal(props: UserEditModalProps) { |
调用Modal组件
现在我们可以在其他组件中调用这个Modal组件了。调用的方式很简单:
- 将
UserEditModal
组件放在父组件中(通常放到组件的末尾)。 - 在父组件中定义一个状态变量
isEditModalVisible
,用来控制Modal的显示和隐藏。
代码如下:
1 | const [isEditModalVisible, setIsEditModalVisible] = useState(false); |
当我们需要显示Modal时,只需要将isEditModalVisible
设置为true
即可。
1 | const onEditUser = (userId: number) => { |
当我们需要隐藏Modal时,只需要将isEditModalVisible
设置为false
即可。
1 | const onEditCancel = () => { |
运行程序
此时,我们运行程序,你会发现,即使Modal还没有打开,API请求已经发起了。这是因为useEffect
钩子在组件加载时就会执行,而不管Modal是否可见。
为了解决这个问题,我们需要在useEffect
中添加一个条件判断,只有当Modal可见时才发起API请求。
1 | useEffect(() => { |
其实上面的代码是双保险,只有当Modal可见且userId
存在时,才会发起API请求。这样就可以确保在Modal打开后才开始执行相关操作。
总结
使用Ant Design中的Modal组件时,一定要在组件可见时在发起API请求。通过在useEffect
中添加条件判断,我们可以确保在Modal打开后才开始执行相关操作。从而避免不必要的API请求。