Antd 使用学习
Antd 使用学习1. 配置antd 配合 craco2. 使用Antd1. Antd 使用第一课 Form 表单扩展知识:2. Table 表格的使用:3. Select 与 Input4. Dropdown 与 Menu5. Typegraphy.Text 用于显示一段文字6. Button7. Popover8. List,List.Item,List.Item.Meta9. Modal 模态弹窗10. Spin 显示加载动画效果
1. 配置antd 配合 craco
- 安装craco
yarn add @craco/craco
,并修改 package.json
文件
- 安装craco-less
yarn add craco-less
- 安装bable-plugin-import
yarn add babel-plugin-import
用于按需映入css or less
修改package.json
1 2 3 4 5 6
| "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" },
|
根目录创建配置文件craco.config.js:
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 CracoLessPlugin = require("craco-less");
module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { "@primary-color": "#1DA57A" }, javascriptEnabled: true, }, }, }, }, ], babel: { plugins: [ [ "import", { libraryName: "antd", libraryDirectory: "es", style: true, }, ], ], }, };
|
2. 使用Antd
引入 import { Form, Input, Button } from 'antd';
使用
Item 标签中需要填写 name= ""
这个字段将作为表单提交事件的属性名
item 可以增加 rules 属性,例如:rules={[{ required: true, message: '请输入密码' }]}
表单的 submit 写法不同于 html 原生,antd 的 type是用于指定 组件样式的,是一个字面量类型
1 2
| declare const ButtonTypes: ["default", "primary", "ghost", "dashed", "link", "text"]; export declare type ButtonType = typeof ButtonTypes[number];
|
需要使用 htmlType 属性来指定为 submit
最后 表单的 提交事件也不再是 onSubmit,而是onFinish
表单事件注册的函数也不同于,原生html,接受的不再是react的event事件,而是封装好的 item指定名字的对象
最终代码对比:
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
| import { useAuth } from 'context/auth-context'; import { Form, Input, Button } from 'antd';
const LoginScreen = () => { const { login, user } = useAuth() const handleSubmit = (values: { username: string, password: string }) => { const { username, password } = values login({ username, password }) }
return ( <Form onFinish={handleSubmit} > {user ? <div>登录成功,用户名为:{user?.name}</div> : null} <Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}> <Input placeholder='用户名' type="text" id="username" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}> <Input placeholder='密码' type="password" id="password" /> </Form.Item> <Form.Item> <Button htmlType='submit' type='primary'>登录</Button> </Form.Item> </Form> ) }
export default LoginScreen
|
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 { FormEvent } from 'react' import { useAuth } from 'context/auth-context';
const LoginScreen = () => { const { login, user } = useAuth() const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault() console.log('submit') const username = (e.currentTarget.elements[0] as HTMLInputElement).value const password = (e.currentTarget.elements[1] as HTMLInputElement).value login({ username, password }) } return ( <form onSubmit={handleSubmit}> {user ? <div>登录成功,用户名为:{user?.name}</div> : null} <div> <label htmlFor="username">Username</label> <input type="text" id="username" /> </div> <div> <label htmlFor="password">Password</label> <input type="password" id="password" /> </div> <button type='submit'>登录</button> </form> ) }
export default LoginScreen
|
可以看出 antd 省去了我们很多的操作。
知识点,Form.Item 代理了其子组件中的 value onChange函数,并用自己的 name 为这个内容命名,使其成为了受控组件,这个我之后会单开一个文章详细介绍。
扩展知识:
const [form] = Form.useForm()
,可以将这个钩子的返回值赋值给 Form 的 form,用于控制Form表单的行为;
form.resetFields()
清空表单
form.setFieldsValue(editingProject)
使用一个对象填充表单
使用该钩子可能会导致一个异常:
1
| react_devtools_backend.js:3973 Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?
|
一般来说无视即可,如果觉得是在讨厌,可以在Form的父组件 传入 forceRender={true}
2. Table 表格的使用:
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 34 35 36 37 38 39 40 41
| import { Table } from 'antd'; import React, { PropsWithChildren } from 'react' import { User } from './search-panel';
type Project = { id: string, name: string, personId: string, } type ListProps = { list: Project[]; users: User[] } const List: React.FC<PropsWithChildren<ListProps>> = ({ list, users }) => { return ( <Table //加载状态 loading={false} //数据行的key rowKey="id" // 是否分页 pagination={false} //数据源 dataSource={list} //渲染的列 columns={[ { title: '项目名称', dataIndex: 'name', sorter: (a, b) => a.name.localeCompare(b.name) }, { title: '项目负责人', render(value, project) { return <span key={project.id}> {users.find(user => user.id === project.personId)?.name || "未知"}</span> } }]} /> ) }
export default List
|
这里由于负责人我们拿到的只有id,需要我们从user列表中去自行计算,所以直接传入render函数;
dataIndex,表示的是我们使用的这个key中的数据去渲染这一行;
sorter 用于排序,传入一个排序算法;
如果我们的组件是对Table的封装,我们可以透传table的props,也就是让我们组件扩展Table的Props
例如:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import { Table, TableProps } from 'antd'; import dayjs from 'dayjs'; import React, { PropsWithChildren } from 'react' import { User } from './search-panel';
type Project = { id: string, name: string, personId: string, pin: boolean, organization: string, created: number; }
type ListProps = { users: User[] } & TableProps<Project>
const List: React.FC<PropsWithChildren<ListProps>> = ({ users, ...props }) => { return ( <Table //透传 antd的props {...props} //每一行的key rowKey="id" // 是否分页 pagination={false} //渲染的列 columns={[ { title: '项目名称', dataIndex: 'name', sorter: (a, b) => a.name.localeCompare(b.name) }, { title: '部门', dataIndex: 'organization' }, { title: '项目负责人', render(_, project) { return <span key={project.id}> {users.find(user => user.id === project.personId)?.name || "未知"}</span> } }, { title: '创建时间', render(_, project) { return <span> {project.created ? dayjs(project.created).format("YYYY-MM-DD") : '未知时间'} </span> }, },]} /> ) }
export default List
|
这样上层使用我们的封装组件时,可以方便的传入Table原有的Props
input输入框的用法与原来大致相同,Select 的用法有差别
1 2 3 4 5 6 7 8 9 10 11 12
| <Select value={param.personId} onChange={(value) => setParam({ ...param, personId: value })}>
<Select.Option value="">负责人</Select.Option>
{users.map((user: User) => ( <Select.Option key={user.id} value={user.id}> {user.name} </Select.Option> ))} </Select>
|
- 回调事件与Form类似 直接可以拿到value
- 子项目需使用
<Select.Option>
- value属性用于指定当前显示的option
1 2 3 4 5 6 7 8 9
| <Dropdown overlay={( <Menu> <Menu.Item key='logout'> <a onClick={() => logout()}>退出</a> </Menu.Item> </Menu> )}> <a onClick={e => e.preventDefault()}>Hi,{user?.name}</a> </Dropdown>
|
Dropdown 必须包含一个 overlay 的props,其中返回的是JSX组件,一般使用 Menu,当鼠标悬停于 Dropdown 组件时,会显示其中的overlay中的组件。
Dropdown 的子组件,就是当前显示的组件样式;
5. Typegraphy.Text 用于显示一段文字
显示一行错误信息:
1
| {error ? <Typography.Text type='danger'>{error.message}</Typography.Text> : null}
|
Button是AntD中最常用的组件之一,通过Type可以设定各种用途
1 2 3 4 5 6 7 8
| <Button type="link"></Button>
export const LongButton = styled(Button)` width: 100%; `
<LongButton loading={isLoading} htmlType='submit' type='primary' >注册</LongButton>
|
在AntD中 Button 共有6中type
1 2
| declare const ButtonTypes: ["default", "primary", "ghost", "dashed", "link", "text"]; export declare type ButtonType = typeof ButtonTypes[number];
|
我们在原生表单中,往往使用type=‘submit’
来表示提交按钮,在AntD组件中 需要使用:htmlType,来指定该动作
当我们使用Button 作为link 包裹组件时,子组件有时候并不能居中排版,我们可以使用flex的行内样式来设置
1 2 3
| <Button type="link" onClick={resetRoute} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <SoftwareLogo width={'18rem'} color={'rgb(38,132,255)'} /> </Button>
|
如果使用了emotion,可以使用自定义样式的emotion组件来封装,例如一个没有padding的Button:
1 2 3
| export const ButtonNoPadding = styled(Button)` padding: 0; `
|
常用props:
- type 设置样式
- htmlType 用于在表单中设置为提交
- loading 显示加载
7. Popover
Popover 与 Dropdown很像,但是又有不同之处:
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 34 35 36 37 38 39
| import styled from '@emotion/styled' import { Button, Divider, List, Popover, Typography } from 'antd' import React from 'react' import { useProjects } from 'utils/project' import { ButtonNoPadding } from './lib'
export const ProjectPopover = ({ setProjectModalOpen }: { setProjectModalOpen: (isOpen: boolean) => void }) => { const { data: projects, isLoading } = useProjects() const pinnedProjects = projects?.filter(project => project.pin)
const content = <Container> <Typography.Text type='secondary'>收藏项目</Typography.Text> <List> { pinnedProjects?.map(project => ( <List.Item key={project.id}> <List.Item.Meta title={project.name} /> </List.Item> )) } </List> <Divider /> <ButtonNoPadding type='link' onClick={() => setProjectModalOpen(true)}>创建项目</ButtonNoPadding> </Container> return ( <Popover placement='bottom' content={content} > <span>项目</span> </Popover> ) }
const Container = styled.div` min-width: 30rem; `
|
Popover 通过 placement控制显示的位置,通过content设置显示的内容:
1 2 3 4 5 6 7 8 9
| <List> { pinnedProjects?.map(project => ( <List.Item key={project.id}> <List.Item.Meta title={project.name} /> </List.Item> )) } </List>
|
用于显示一个简单的列表
9. Modal 模态弹窗
1 2 3 4 5 6 7 8 9 10 11
| const confirmDelete = () => { Modal.confirm({ title: '确认删除', content: '确认删除该项目吗?', okText: '确认', cancelText: '取消', onOk: () => { deleteKanban({ id: kanban.id }) } }) }
|
除了在方法里调用弹窗,还可以作为一种布局使用,例如一个编辑任务的模态窗口布局:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| import { Button, Input, Modal } from 'antd' import Form from 'antd/lib/form' import { TaskTypeSelect } from 'components/task-type-select' import { UserSelect } from 'components/user-select' import { useEffect } from 'react' import { useDeleteTask, useEditTask } from 'utils/task' import { useTaskModal, useTasksQueryKey } from './util'
const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }
export const TaskModal = () => { const [form] = Form.useForm() const { editingTaskId, editingTask, close } = useTaskModal() const { mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey())
const { mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey())
const onCancel = () => { close() form.resetFields() }
const onOk = async () => { await editTask({ ...editingTask, ...form.getFieldsValue() }) onCancel() } useEffect(() => { form.setFieldsValue(editingTask) }, [form, editingTask])
const confirmDelete = () => { Modal.confirm({ title: '确认删除', content: '确认删除该任务吗?', okText: '确认', cancelText: '取消', onOk: () => { deleteTask({ id: Number(editingTaskId) }) close() } }) } return ( <Modal forceRender={true} onCancel={close} onOk={onOk} okText='确认' cancelText='取消' confirmLoading={editLoading} visible={!!editingTaskId} title='编辑任务' > <Form {...layout} initialValues={editingTask} form={form} > <Form.Item label="任务名" name={'name'} rules={[{ required: true, message: '请输入任务名' }]} > <Input /> </Form.Item> <Form.Item label="经办人" name={'processorId'} > <UserSelect style={{ width: '100%' }} defaultOptionName='经办人' /> </Form.Item> <Form.Item label="类型" name={'typeId'} rules={[{ required: true, message: '请选择类型' }]} > <TaskTypeSelect style={{ width: '100%' }} /> </Form.Item>
</Form> <div style={{ 'textAlign': 'right' }}> <Button onClick={() => confirmDelete()} type={'primary'} danger > 删除 </Button> </div> </Modal> ) }
|
模态窗口布局组件一般放在需要用的页面的根节点,通过visible
字段来进行控制显隐。
常用属性:
- onCancel={close} 取消回调
- onOk={onOk} 确认回调
- okText=’确认’ 确认按钮的文字
- cancelText=’取消’ 取消按钮的文字
- confirmLoading={editLoading} 确认的loading
- visible={!!editingTaskId} 控制显隐
- title=’编辑任务’ 标题
10. Spin 显示加载动画效果