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
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
根目录创建配置文件craco.config.js:
const CracoLessPlugin = require("craco-less");
// 需要安装两个插件:`yarn add craco-less` `yarn add babel-plugin-import` 从而实现样式的按需引入
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
},
},
},
],
babel: {
//支持装饰器
plugins: [
[
"import",
{
libraryName: "antd",
libraryDirectory: "es",
style: true, //设置为true即是less 这里用的是css,如果是scss,则需要设置为false,否则不能正常生效
},
],
],
},
};
2. 使用Antd
1. Antd 使用第一课 Form 表单
-
引入
import { Form, Input, Button } from 'antd'; -
使用
标签,每一个表单项使用 <Form.Item> -
Item 标签中需要填写
name= ""这个字段将作为表单提交事件的属性名 -
item 可以增加 rules 属性,例如:
rules={[{ required: true, message: '请输入密码' }]} -
表单的 submit 写法不同于 html 原生,antd 的 type是用于指定 组件样式的,是一个字面量类型
declare const ButtonTypes: ["default", "primary", "ghost", "dashed", "link", "text"]; export declare type ButtonType = typeof ButtonTypes[number];需要使用 htmlType 属性来指定为 submit
-
最后 表单的 提交事件也不再是 onSubmit,而是onFinish
-
表单事件注册的函数也不同于,原生html,接受的不再是react的event事件,而是封装好的 item指定名字的对象
最终代码对比:
//使用antd
import { useAuth } from 'context/auth-context';
import { Form, Input, Button } from 'antd';
const LoginScreen = () => {
const { login, user } = useAuth()
//根据 Form.Item的name决定的这个values是什么
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
//不使用antd
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)使用一个对象填充表单
使用该钩子可能会导致一个异常:
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 表格的使用:
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
例如:
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;
}
//扩展TableProps,增加users
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
3. Select 与 Input
input输入框的用法与原来大致相同,Select 的用法有差别
<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
4. Dropdown 与 Menu
<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 用于显示一段文字
显示一行错误信息:
{error ? <Typography.Text type='danger'>{error.message}</Typography.Text> : null}
6. Button
Button是AntD中最常用的组件之一,通过Type可以设定各种用途
//将子组件作为link
<Button type="link"></Button>
//使用emotion包装的AntD Button
export const LongButton = styled(Button)`
width: 100%;
`
//设置 htmlType loading 等props
<LongButton loading={isLoading} htmlType='submit' type='primary' >注册</LongButton>
在AntD中 Button 共有6中type
declare const ButtonTypes: ["default", "primary", "ghost", "dashed", "link", "text"];
export declare type ButtonType = typeof ButtonTypes[number];
我们在原生表单中,往往使用type=‘submit’来表示提交按钮,在AntD组件中 需要使用:htmlType,来指定该动作
当我们使用Button 作为link 包裹组件时,子组件有时候并不能居中排版,我们可以使用flex的行内样式来设置
<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:
export const ButtonNoPadding = styled(Button)`
padding: 0;
`
常用props:
- type 设置样式
- htmlType 用于在表单中设置为提交
- loading 显示加载
7. Popover
Popover 与 Dropdown很像,但是又有不同之处:
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()
//筛选pin为true的项目
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设置显示的内容:
8. List,List.Item,List.Item.Meta
<List>
{
pinnedProjects?.map(project => (
<List.Item key={project.id}>
<List.Item.Meta title={project.name} />
</List.Item>
))
}
</List>
用于显示一个简单的列表
9. Modal 模态弹窗
const confirmDelete = () => {
Modal.confirm({
title: '确认删除',
content: '确认删除该项目吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteKanban({ id: kanban.id })
}
})
}
除了在方法里调用弹窗,还可以作为一种布局使用,例如一个编辑任务的模态窗口布局:
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 显示加载动画效果
<Spin size='large' />