React学习——封装组件

前端实习经验分享一:从学习React技术开始(以官方文档为基础,跟随师父了解公司项目业务逻辑),为公司封装组件进一步了解React最大特点——模块化。也感受到封装组件的重要性。下面我会通过一个案例总结封装组件的场景、过程、技巧。

封装组件

(一) 优点

使结构清晰; 组件复用;拓展组件;

(二)场景

  • 多个复杂组件具有层层嵌套关联时,为了增加代码可视性考虑封装组件。
    比如:这样一个经典的列表页,由查询表单Form、操作按钮Button,展示列表Table组合。点击Table右侧的操作列按钮Button,会打开一个模态框Modal,Modal里有一个步骤条Steps。

想要实现这样的一个需求一个页面的代码量已经降低了代码的可视性。建议把这个页面作为一个单独文件夹,主页面为index.tsx只需搭建基本结构,模态框内容比较多,可以封装在一个文件里。

  • 一个组件被多次引用时,为了增加组件的复用性考虑封装组件。
    比如:同上一个系统必然会有其主题样式,为了达到主题样式统一,查询表单Form和操作按钮Button可以封装成一个组件,相同类型的列表页都可以复用。

(三) 过程

  1. 基于常用组件(AntDesign)封装结构,先处理UI
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操作,组件名首字母必须大写,感兴趣的可以深入研究。

  1. 定义组件的属性,具体方法在组件类里
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;
}
  1. 组件遍历的数据
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;
    //根据新的需求,可以拓展属性
}
  1. 表单项显示的组件
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.及时总结收集优秀案例。

除了封装组件,组件通信也是必须理清和掌握的,有机会再复习总结一下。

最后希望大家多多批评多多指教。从开始懵懂无知到现在渐渐了解,未来充满期待。
寄语:不要畏惧问题,积极主动,认真负责

你可能感兴趣的:(typescript,react,前端)