前端实习经验分享一:从学习React技术开始(以官方文档为基础,跟随师父了解公司项目业务逻辑),为公司封装组件进一步了解React最大特点——模块化。也感受到封装组件的重要性。下面我会通过一个案例总结封装组件的场景、过程、技巧。
(一) 优点
使结构清晰; 组件复用;拓展组件;
(二)场景
想要实现这样的一个需求一个页面的代码量已经降低了代码的可视性。建议把这个页面作为一个单独文件夹,主页面为index.tsx只需搭建基本结构,模态框内容比较多,可以封装在一个文件里。
(三) 过程
import { $Field } from "XXX/entity";
import { $PageType } from "XXX/enum"
import { Button, Form } from 'antd';
import React, { useImperativeHandle, useState } from 'react';
import styled from "styled-components"
//import ...
const DetailForm = (props: IDetailFormProps) => {
const { initialValues, pageType } = props;
const [form] = Form.useForm();
const [readonly, setReadonly] = useState(!!props.readonly);
const [formData, setFormData] = useState(initialValues);
const onFinish = values => {
props.onSave(values)
.then(flag => {
setReadonly(true)
})
};
const onFinishFailed = errorInfo => {
console.log('Failed:', errorInfo);
};
const onCancel = () => {
form.validateFields();
props.onCancel && props.onCancel()
}
useImperativeHandle(props.form, () => ({
setFieldsValue: (values) => {
form.setFieldsValue(values);
}
}));
const { fields } = props;
const executeScript = (script: string | undefined, defaultResult: any) => {
if (!script) {
return defaultResult;
}
const data = formData;
const pageType = props.pageType;
let result = defaultResult;
try {
eval(script);
} catch (err) {
console.error(err);
}
return result;
}
const getRules = (required: boolean, field: $Field) => {
const rules: any[] = [];
if(required) {
rules.push({ required, message: '必填' })
}
if(field.pattern) {
rules.push({ pattern: field.pattern, message: '正则校验不通过' })
}
return rules;
}
return (
<Wrapper>
<Form
initialValues={initialValues}
onValuesChange={(val, values) => setFormData(values)}
onFinishFailed={onFinishFailed}
hideRequiredMark={true}
onFinish={onFinish}
layout={"inline"}
form={form}
>
{
fields.map((field) => {
//设置显示,只读,必填校验
const { displayCondition, readonlyCondition, requiredCondition } = field;
const display = executeScript(displayCondition, true);
const readonlyR = executeScript(readonlyCondition, readonly);
const required = executeScript(requiredCondition, true);
if (!display) {
return null;
}
return (
<div key={field.name} style={{ width: `${(field.cols || 1) * 100 / 3}%` }}>
<Form.Item
rules={getRules((required && !readonlyR), field)}
key={field.name}
label={<LabelWrapper>{required && <span className={"required"}>*</span>}{field.label}</LabelWrapper>}
name={field.name}>
{renderWidget(field, readonlyR)}
</Form.Item>
</div>
)
})
}
<ButtonWrapper>
{readonly && <Button type="primary" onClick={() => setReadonly(false)}>编辑</Button>}
{!readonly && pageType !== $PageType.CREATE &&
<Button type="primary" ghost={true} onClick={() => {
setReadonly(true);
onCancel()
}
}>取消</Button>}
{!readonly && <Button type="primary" htmlType="submit">保存</Button>}
</ButtonWrapper>
</Form>
</Wrapper>
)
}
export default DetailForm;
const Wrapper = styled.div`// styled
& {
margin: 0;
padding: 16px 32px 0px 16px;
}
.ant-form-inline .ant-form-item {
width: 100%;
margin-right: 0;
margin-bottom: 12px;
> .ant-form-item-label {
> label::after {
display: none;
}
}
}
`;
const LabelWrapper = styled.div`// styled
& {
width: 108px;
padding-left: 12px;
margin-right: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: rgb(102, 102, 102);
.required {
color: #FF4D4F;
display: inline-block;
}
}
`;
const ButtonWrapper = styled.div`// styled
& {
width: 100%;
padding-left: 16px;
padding-right: 16px;
text-align: center;
> * {
margin-right: 12px;
}
}
`;
这里使用styled-components实现组件化样式,样式里可以进行js操作,组件名首字母必须大写,感兴趣的可以深入研究。
import { $Field } from "XXX/entity"
import { $PageType } from "XXX/enum"
export interface IDetailFormProps {
//具体方法,见上段代码里
onSave: (formData) => Promise<boolean>;
onCancel?: () => void;
fields: $Field[];
initialValues?: { [key: string]: any };
readonly?: boolean;
form?: (form) => void;
pageType: $PageType;
}
import { $WidgetType } from "XXX/enum"
export class $Field {
name: string;
label: string;
widgetType: $WidgetType;
placeholder?: string;
options?: Array<{ label: string, value: string, displayName?: string }>;
helpText?: string;
readonlyCondition?: string;
displayCondition?: string;
requiredCondition?: string;
cols?: number = 1;
displayName?: string;
pattern?: RegExp;
min?: number;
max?: number;
precision?: number;
minLength?: number;
maxLength?: number;
isMulti?: boolean;
objectType?: string;
condition?:{[key: string]: any};
onChange?:(val: any, otherProps?: any) => void;
//根据新的需求,可以拓展属性
}
import { $Field } from "XXX/entity"
import { $WidgetType } from "XXX/enum"
import {
Input,
InputNumber,
InputReadonly,
ObjectSelector,
ObjectSelectorReadonly,
Password,
Select,
SelectReadonly,
Switch,
SwitchReadonly,
TextArea,
} from "XXX/widgets"//这个文件存放已经封装的基本组件,这里就体现了组件的复用性
import React from "react";
export const renderWidget = (filed: $Field, readonly = false) => {
let Widget;
//根据组件设置的readonly属性,决定显示的组件类型
if (readonly) {
switch (filed.widgetType) {
case $WidgetType.SELECT:
Widget = SelectReadonly;
break;
case $WidgetType.OBJECT_SELECTOR:
Widget = ObjectSelectorReadonly;
break;
case $WidgetType.SWITCH:
Widget = SwitchReadonly;
break;
default:
Widget = InputReadonly;
break;
}
} else {
switch (filed.widgetType) {
case $WidgetType.NUMBER:
Widget = InputNumber;
break;
case $WidgetType.TEXTAREA:
Widget = TextArea;
break;
case $WidgetType.SELECT:
Widget = Select;
break;
case $WidgetType.OBJECT_SELECTOR:
Widget = ObjectSelector;
break;
case $WidgetType.PASSWORD:
Widget = Password;
break;
case $WidgetType.SWITCH:
Widget = Switch;
break;
default:
Widget = Input;
break;
}
}
return (
<Widget name={filed.name} field={filed} onChange={filed.onChange}/>
)
}
(四)技巧
1.灵活应用组件库
常用的Form组件不常用但好用的功能:如表单数据存储于上层组件、多表单联动等。多看API、了解FormInstance使用。
2.清晰的文档结构和枚举类,可以有效提高代码可读性。
3.及时总结收集优秀案例。
除了封装组件,组件通信也是必须理清和掌握的,有机会再复习总结一下。
最后希望大家多多批评多多指教。从开始懵懂无知到现在渐渐了解,未来充满期待。
寄语:不要畏惧问题,积极主动,认真负责