在react16.8版本中新增了一个hook特性,暂且称它为函数组件,react文档传送门。关于它最直白的介绍就是:它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 并且在antd Design V4版本中也多次使用到这个特性,比如弹出层中使用表单,文档的例子是这样的=》新特性使用案例传送门。刚开始使用的时候我发现他好像没有类组件好使,特别是他没有类组件中state、props的实时数据更新,导致了我中途把函数组件换成了类组件来实现我得需求,但是当我认真查看文档后发现是我不会用,其实函数组件可以完成类组件绝大多数的功能,甚至可以说全部功能(有些我没发现,话不敢说太满!)。百度下类组件和函数组件的区别,有一篇点击量比较多的=》类组件和函数组件的区别。那今天我在他们的基础上在加以案例来详细说明下函数组件的用法。
比较代码=》函数组件和类组件,可以点击进去在codesandbox在线测试这两个的区别。
做演示代码里我模拟生成周计划表,在选择完日期后,自动生成对应天数的计划表。
类组件代码如下:
import React from "react";
import { Modal, Form, Button, Row, Col, DatePicker, Table } from "antd";
import moment from "moment";
const { log } = console;
const addDate = (date, days) => {
const d = new Date(date);
d.setDate(d.getDate() + days);
let mm = d.getMonth() + 1;
mm = mm < 10 ? "0" + mm : mm;
let dd = d.getDate();
dd = dd < 10 ? "0" + dd : dd;
return d.getFullYear() + "-" + mm + "-" + dd;
};
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 }
};
const config = {
rules: [{ required: true, message: "必填项不得为空!" }]
};
let endTime = "";
let startTime = "";
// 新增周计划表单组
class CollectionCreateForm extends React.Component {
formRef = React.createRef(); //表单实例初始化
constructor(props) {
super(props);
this.state = {
daysLong: 0, // 天数
weeksContent: []
};
}
onFinish = () => {
// const values = this.formRef.current.getFieldsValue();
this.formRef.current
.validateFields()
.then(values => {
this.props.onCreate(values);
})
.catch(errorInfo => {
log(errorInfo);
});
};
render() {
const getWeeksContent = () => {
let weeksContent = [];
const week = ["日", "一", "二", "三", "四", "五", "六", "日"];
const daysLong =
Number(endTime.slice(9, 11)) - Number(startTime.slice(9, 11)) + 1;
this.setState({ daysLong });
let weekDay = new Date(startTime).getDay(); // 获取星期
for (let index = 0; index < daysLong; index++) {
const element = {
weeks: "周" + week[weekDay],
content: "请输入当天工作内容,不超过40个字,未安排可不填写",
contents: "请输入周汇总,不超过140个字"
};
weekDay++;
weeksContent.push(element);
}
log(weeksContent);
this.setState({ weeksContent });
};
// 开始时间选择器(监控记录日期变换)
const handleStartDateChange = (value, dateString) => {
startTime = dateString;
if (endTime) getWeeksContent();
};
// 结束时间选择器(监控记录日期变换)
const handleEndDateChange = (value, dateString) => {
endTime = dateString;
if (startTime) getWeeksContent();
};
// 结束时间可选范围
const handleEndDisabledDate = current => {
let start = "";
if (endTime) {
start = addDate(
endTime,
Number(
"-" +
(new Date(endTime).getDay() === 0
? 6
: new Date(endTime).getDay() - 1)
)
);
}
// 判断结束日期是否跨月
if (Number(endTime.slice(5, 7)) !== Number(start.slice(5, 7))) {
start = endTime.slice(0, 8) + "01";
}
if (endTime === undefined) {
return null;
} else if (endTime !== "") {
return (
current > moment(endTime).endOf("day") ||
current < moment(start).startOf("day")
);
} else {
return null;
}
};
// 开始时间可选范围
const handleStartDisabledDate = current => {
let end = "";
if (startTime) {
end = addDate(
startTime,
7 -
(new Date(startTime).getDay() === 0
? 7
: new Date(startTime).getDay())
);
}
// 判断结束日期是否跨月
if (Number(startTime.slice(5, 7)) !== Number(end.slice(5, 7))) {
end =
startTime.slice(0, 8) +
new Date(startTime.slice(0, 3), startTime.slice(5, 7), 0).getDate();
}
if (startTime === undefined) {
return null;
} else if (startTime !== "") {
return (
current < moment(startTime) || current > moment(end).endOf("day")
);
} else {
return null;
}
};
let columns = [
{ title: "星期", dataIndex: "weeks", align: "center", width: 60 },
{
title: "工作内容",
dataIndex: "content",
align: "center"
},
{
title: "周总结",
dataIndex: "contents",
align: "center",
render: (value, row, index) => {
const obj = {
children: value,
props: { rowSpan: this.state.daysLong }
};
if (index !== 0) {
obj.props.colSpan = 0;
}
return obj;
}
}
];
return (
制定计划细则
);
}
}
export default class CollectionsPage extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false
};
}
render() {
const onCreate = values => {
endTime = "";
startTime = "";
values.startTime = values["startTime"].format("YYYY-MM-DD");
values.endTime = values["endTime"].format("YYYY-MM-DD");
console.log("Received values of form: ", values);
this.setState({ visible: false });
};
return (
{this.state.visible ? ( // 重置组件,移除未保存的内容
{
this.setState({ visible: false });
endTime = "";
startTime = "";
}}
/>
) : null}
);
}
}
功能啥的大家直接在codesandbox里操作,至于数据的更新就是使用state和props来实现的,也不用多说,都用烂了的东西。
函数组件代码如下
import React, { useState } from "react";
import { Modal, Form, Table, Button, Row, Col, DatePicker } from "antd";
import moment from "moment";
const { log } = console;
const addDate = (date, days) => {
const d = new Date(date);
d.setDate(d.getDate() + days);
let mm = d.getMonth() + 1;
mm = mm < 10 ? "0" + mm : mm;
let dd = d.getDate();
dd = dd < 10 ? "0" + dd : dd;
return d.getFullYear() + "-" + mm + "-" + dd;
};
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 }
};
const config = {
rules: [{ required: true, message: "必填项不得为空!" }]
};
// 新增周计划表单组
const CollectionCreateForm = ({ visible, onCreate, onCancel }) => {
const [daysLong, setDaysLong] = useState(0);
const [weeksContent, setWeeksContent] = useState([]);
let endTime = "";
let startTime = "";
const [form] = Form.useForm();
const getWeeksContent = () => {
let weeksContent = [];
const week = ["日", "一", "二", "三", "四", "五", "六", "日"];
const days =
Number(endTime.slice(9, 11)) - Number(startTime.slice(9, 11)) + 1;
setDaysLong(days);
let weekDay = new Date(startTime).getDay(); // 获取星期
for (let index = 0; index < days; index++) {
const element = {
weeks: "周" + week[weekDay],
content: "请输入当天工作内容,不超过40个字,未安排可不填写",
contents: "请输入周汇总,不超过140个字"
};
weekDay++;
weeksContent.push(element);
}
log(weeksContent);
setWeeksContent(weeksContent);
};
// 开始时间选择器(监控记录日期变换)
const handleStartDateChange = (value, dateString) => {
startTime = dateString;
if (endTime) getWeeksContent();
};
// 结束时间选择器(监控记录日期变换)
const handleEndDateChange = (value, dateString) => {
endTime = dateString;
if (startTime) getWeeksContent();
};
// 结束时间可选范围
const handleEndDisabledDate = current => {
let start = "";
if (endTime) {
start = addDate(
endTime,
Number(
"-" +
(new Date(endTime).getDay() === 0
? 6
: new Date(endTime).getDay() - 1)
)
);
}
// 判断结束日期是否跨月
if (Number(endTime.slice(5, 7)) !== Number(start.slice(5, 7))) {
start = endTime.slice(0, 8) + "01";
}
if (endTime === undefined) {
return null;
} else if (endTime !== "") {
return (
current > moment(endTime).endOf("day") ||
current < moment(start).startOf("day")
);
} else {
return null;
}
};
// 开始时间可选范围
const handleStartDisabledDate = current => {
let end = "";
if (startTime) {
end = addDate(
startTime,
7 -
(new Date(startTime).getDay() === 0
? 7
: new Date(startTime).getDay())
);
}
// 判断结束日期是否跨月
if (Number(startTime.slice(5, 7)) !== Number(end.slice(5, 7))) {
end =
startTime.slice(0, 8) +
new Date(startTime.slice(0, 3), startTime.slice(5, 7), 0).getDate();
}
if (startTime === undefined) {
return null;
} else if (startTime !== "") {
return current < moment(startTime) || current > moment(end).endOf("day");
} else {
return null;
}
};
let columns = [
{ title: "星期", dataIndex: "weeks", align: "center", width: 60 },
{
title: "工作内容",
dataIndex: "content",
align: "center",
editable: true
},
{
title: "周总结",
dataIndex: "contents",
align: "center",
editable: true,
render: (value, row, index) => {
const obj = {
children: value,
props: { rowSpan: daysLong }
};
if (index !== 0) {
obj.props.colSpan = 0;
}
return obj;
}
}
];
return (
<Modal
width={600}
visible={visible}
title="周计划表"
okText="确认"
cancelText="取消"
// centered
maskClosable={false}
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
onCreate(values);
})
.catch(info => {
console.log("Validate Failed:", info);
});
}}
>
<div
style={{
border: "rgba(229, 229, 229, 1) solid 1px",
padding: "12px 22px 12px 0px"
}}
>
<Form form={form} {...layout} name="form_in_modal">
<Row>
<Col span={12}>
<Form.Item label="开始时间" name="startTime" {...config}>
<DatePicker
format="YYYY-MM-DD"
onChange={handleStartDateChange.bind(this)}
disabledDate={handleEndDisabledDate.bind(this)}
style={{ width: "100%" }}
placeholder="请选择开始时间..."
allowClear
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="结束时间" name="endTime" {...config}>
<DatePicker
style={{ width: "100%" }}
placeholder="请选择结束时间..."
allowClear
format="YYYY-MM-DD"
onChange={handleEndDateChange.bind(this)}
disabledDate={handleStartDisabledDate.bind(this)}
/>
</Form.Item>
</Col>
</Row>
</Form>
</div>
<p className="text_293X1">制定计划细则</p>
<Table columns={columns} pagination={false} dataSource={weeksContent} />
</Modal>
);
};
const CollectionsPage = () => {
const [visible, setVisible] = useState(false);
const onCreate = values => {
values.startTime = values["startTime"].format("YYYY-MM-DD");
values.endTime = values["endTime"].format("YYYY-MM-DD");
// console.log("Received values of form: ", values);
setVisible(false);
};
return (
<span>
<Button
className="buttonRight"
onClick={() => {
setVisible(true);
}}
>
函数组件演示
</Button>
<CollectionCreateForm
visible={visible}
onCreate={onCreate}
onCancel={() => {
setVisible(false);
}}
/>
</span>
);
};
export default CollectionsPage;
从函数组件的实现中可以看出,函数组件中没有生命周期,没有state、props,那么注定了他的性能会比类组件的要好。在函数组件中使用useState来实现类组件中的state状态,函数组件演示代码中的daysLong,weeksContent两个变量就是类组件中this.state的daysLong,weeksContent一样的作用。那么要实现props的功能使用函数参数就可以实现了,参看函数组件227行里的visible,onCreate,onCancel就是想的与类组件里的props状态。
经过写demo测试hook还是很好用的,但是我不清楚如何首先函数组件的ref功能,就比如要调用子组件的内部方法就行相应要怎么办呢?等我去进阶下再来修改补充
总结:那么总的来说函数组件基本能做到类组件一样的功能,性能也有提升,做展示性组件的首选,简单功能需求时也是可以使用的,但是复杂需求时还是类组件首选,毕竟谁也不想万一先天缺失导致功能无法实现那带来的问题就会很大了。最后附上阮一峰老师的hook教学帖子-》阮一峰大神传送门
还有好多功能等我用例子测试完再来补充说明。