React

基于react框架的项目组需要具备的知识统计

2021-06-07  本文已影响0人  送你一堆小心心

通过基础篇的入门,对于react基本语法和插件都可以灵活运用,这篇文章说说实战中进阶的知识点吧~

image.png

1. 项目前期准备

1.1 开发环境搭建

1.2 开发流程

推荐以敏捷开发方法进行软件开发

需求评审 -> 技术评审 -> UI评审 -> 用例评审 -> 开发拆解需求 -> 截止日期评审 -> 开发 -> 测试 -> 验收 -> 发布

1.2.3 开发阶段需要系统

1.3 代码提交流程

1.4 联调流程

1.5 问题排查原则

1.6 需求依赖系统

2. 项目整体保障机制

以下为各个阶段需要确保的内容

2.1 需求评审

2.2 技术评审

2.3 UI评审

2.4 用例评审

2.5 开发

2.6 测试

2.7 验收

发布当天组织会议全员集中验收
参与人员:迭代相关的产品、测试、前端,后端,ui设计师, 产品主导,携带电脑。
!!!如有第三方参与,需加上第三方人员一同参会

2.8 发布

2.9 发布后

3. AOP编程思路 (面向切面编程)

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。

3.1 应用AOP思想的好处

AOP的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模
块。

3.2 应用例子 - 通过修饰器实现编程思想

4. 项目文档维护

持久化的项目必定需要标志的文档记录,基本必备如下:

4.1 开发流程文档

4.2 前端文档

4.3 后端文档

4.4 测试文档

4.5 入职文档(新人

4.6 离职交接文档(旧人

4.7 质量记录

4.8 其他业务相关文档记录

5. 项目组件开发

5.1 组件设计

在一个项目中,好的组件封装决定一个迭代的工作效率,复用就是重中之重。
组件封装一般分为两个部分:业务组件 and 公共组件 。

公共组件, 用于全局引用

先看下我最近项目优化后的组件目录


image.png

共有五层内容:

image.png

业务组件

我们一般放置的目录结构 - 具体层级参考上述内容


image.png

5.2 项目中的功能组件

5.2.1 表格

export default class MainTable extends Component<Props> {
    state = {};
    render() {
        const {
            list,
            columns,
            total,
            page,
            pageSize,
            onShowSizeChange,
            rowKey,
            rowSelection,
            scroll = {}
        } = this.props;
        let tabProps = { columns, dataSource: list, rowKey, rowSelection, pagination: false, scroll };
        return (
            <div className={`${styles.table_wrap} ${this.props.className || ''}`}>
                <Table {...tabProps} pagination={false} />
                <div className={`${styles.page_wrap} tl-list flex-end`}>
                    <Pagination
                        showTotal={total => `共计 ${total} 条`}
                        showSizeChanger
                        showQuickJumper
                        defaultCurrent={page}
                        current={page}
                        pageSize={pageSize}
                        total={total}
                        onChange={onShowSizeChange}
                    />
                </div>
            </div>
        );
    }
}

5.2.2 表单

<Form {...formItemLayoutSignUp}>
                {items.map((item, index) => {
                    const { viewVisible, itemLayout, label, name, options, FormComponent, isElement } = item as any;
                    if (viewVisible !== undefined && !viewVisible) return null;
                    if (isElement && React.isValidElement(FormComponent)) return FormComponent;
                    return (
                        <Form.Item key={index} label={label} {...itemLayout}>
                            {form.getFieldDecorator(name as never, options)(FormComponent)}
                        </Form.Item>
                    );
                })}
            </Form>

5.2.3 提示框

import { Modal } from 'antd';
import React from 'react';
import './style.less';
import styled from 'styled-components';
import { countPrizeNum, numFormat } from '@/pages/Component/ActivityCreate/Prize/constant';

interface Props {
    [propName: string]: any;
}
interface State {
    value: any;
    [propName: string]: any;
}
class AmountTip extends React.Component<Props, State> {
    reportConfirm = () => {
        this.props.confirm();
    };
    cancel = () => {
        this.props.handleCancel();
    };
    render() {
        const { visable, drawParams } = this.props;
        const dataSource = drawParams.awards || [];
        const { award_quota_rule, hasRed = false } = drawParams;
        return (
            <ModalMain
                centered
                title="温馨提示"
                visible={visable}
                className="amount-num"
                onCancel={this.cancel}
                onOk={this.reportConfirm}
                width={400}
            >

            </ModalMain>
        );
    }
}
const ModalMain = styled(Modal)``;
export default AmountTip;

5.2.4 缩略图

import React from 'react';
import { QuestionCircleFilled } from '@ant-design/icons';
import { ThumbnailConfig } from './constant';
import { ThumbnailWrap, ContengWrap } from './styled';
import { Tooltip } from 'antd';
import { ThumbnailProps } from './types';

const ThumbnailContent = (props: ThumbnailProps) => {
    const { src, width, height, content } = ThumbnailConfig[props.type];

    return (
        <ContengWrap widthW={width} heightH={height}>
            <h3>效果展示</h3>
            <p>{content}</p>
            <img src={src} alt=" " />
        </ContengWrap>
    );
};
const Thumbnail = (props: ThumbnailProps) => {
    return (
        <ThumbnailWrap>
            <Tooltip title={() => ThumbnailContent(props)} color={'#fff'}>
                <QuestionCircleFilled className="mark" />
            </Tooltip>
        </ThumbnailWrap>
    );
};
export default Thumbnail;

5.2.5 上传图片

import { YkProjectSelect } from '@yunke/yunked';
import { queryHandOut } from '@/services/oss';
import Loading from '../component/loading';
import XLSX from 'xlsx';
import { getToken as getSubjectId } from '@yunke/yunked/lib/esm/utils/app';
import { message } from 'antd';
import * as R from 'ramda';
import qs from 'qs';
import utils from '@/utils/utils';
const coreUtil = require('@yunke/core/util').default;
//上传图片公用方法封装
export const uploadImg = (file: File, maxSize?: any) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.addEventListener('load', function () {});
    return new Promise((resolve, reject) => {
        let max = utils.getByteSize(maxSize || '2mb');
        if (maxSize && file.size > max) {
            reject('文件大小超过限制');
            return message.warn(`文件大小超过限制: ${maxSize}`);
        }
        Loading.init();
        let maxMb = Math.ceil(max / (1 << 20)); //转换为最大Mb 并向上取整
        queryHandOut('', {}, maxMb)
            .then(res => {
                if (!res) {
                    message.error('上传失败');
                    return;
                }
                // 开始直传
                const { accessid, host, policy, signature, callback, dir } = res.data;
                // 验证格式
                if (!/(jpg|jpeg|png|gif|mp4)/.test(file.type)) {
                    message.error('格式不支持,只支持jpg、jpeg、png、gif、mp4格式文件上传');
                    return;
                }
                // 准备数据
                const data = new FormData();
                data.append('key', `${dir}${randomName() + ('封面图' || ['.jpg'])[0]}`);
                data.append('policy', policy);
                data.append('OSSAccessKeyId', accessid);
                data.append('success_action_status', '200');
                data.append('signature', signature);
                data.append('callback', callback);
                data.append('file', file);
                // 上传数据
                const xhr = new XMLHttpRequest();
                xhr.open('POST', `https:${host}`);
                xhr.onload = e => {
                    try {
                        if (xhr.status === 200) {
                            let data = JSON.parse(xhr.response).data;
                            data.url = data.url.replace(/http:\/\//, 'https://');
                            resolve(data);
                        } else {
                            message.error('文件或尺寸过大,请重新上传');
                            reject(xhr);
                        }
                    } catch (e) {
                        reject(e);
                        message.error('文件或尺寸过大,请重新上传');
                    }
                };
                xhr.onerror = e => {
                    reject(e);
                    message.error('文件或尺寸过大,请重新上传');
                };
                xhr.send(data);
            })
            .finally(() => {
                Loading.destroy();
            });
    });
    function randomName() {
        var str = '';
        var arr = [
            '0',
            '1',
            '2',
            '3',
            '4',
            '5',
            '6',
            '7',
            '8',
            '9',
            'a',
            'b',
            'c',
            'd',
            'e',
            'f',
            'g',
            'h',
            'i',
            'j',
            'k',
            'l',
            'm',
            'n',
            'o',
            'p',
            'q',
            'r',
            's',
            't',
            'u',
            'v',
            'w',
            'x',
            'y',
            'z'
        ];

        for (let i = 0; i < 32; i++) {
            str += arr[Math.floor(Math.random() * arr.length)];
        }

        return str;
    }
};

5.2.6 文本编辑器

import 'braft-editor/dist/index.css';
import React from 'react';
import BraftEditor from 'braft-editor';
import { ContentUtils } from 'braft-utils';
import UploadImg from '@/component/editor/upload';
interface Props {
    type: string; //编辑 详情 新增
    onChange: (editorVal: any) => void;
    val?: string; //编辑详情时传入
    controls?: Array<any>;
    imageControls?: Array<any>;
    [propName: string]: any;
}
export default class Editor extends React.Component<Props> {
    state = {
        editorVal: BraftEditor.createEditorState(null)
    };
    isInit = false; //是否初始化完成
    render() {
        const extendControls: any = [
            {
                key: 'antd-uploader',
                type: 'component',
                component: <UploadImg onChange={this.editorUpload} />
            }
        ];
        let { val, type, cover_url, imageControls = [], controls = [] } = this.props;
        let value = this.state.editorVal;
        if (type !== 'new' && !this.isInit && val) {
            this.isInit = true;
            let data = val.includes('p') ? val: `<p>${val}</p>`
            value = BraftEditor.createEditorState(data);
        }
        if (type === 'new' && !this.isInit && cover_url) {
            this.isInit = true;
            let src = encodeURI(cover_url);
            let img = `<p><p><img src='${src}' style="max-width: 100%;"/></p>${val}</p>`;
            value = BraftEditor.createEditorState(img);
        }
        return (
            <BraftEditor
                style={type === 'look' ? { color: '#ddd' } : {padding: 0}}
                readOnly={type === 'look'}
                value={value}
                defaultValue='<p></p>'
                onChange={this.editorChange}
                extendControls={extendControls}
                imageResizable={false}
                imageControls={imageControls}
                controls={controls}
            />
        );
    }
    //富文本编辑器图片上传
    editorUpload = url => {
        this.setState(
            {
                editorVal: ContentUtils.insertMedias(this.state.editorVal, [
                    {
                        type: 'IMAGE',
                        url
                    }
                ])
            },
            () => {}
        );
    };
    //富文本编辑器内容改变
    editorChange = editorVal => {
        this.setState({ editorVal });
        this.props.onChange(editorVal);
    };
}


import React from 'react';
import { PictureFilled } from '@ant-design/icons';
import { Upload } from 'antd';
import { uploadImg } from '@/utils';
interface Props {
    maxSize?: string;
    onChange: (url: string) => void;
    [propName: string]: any;
}
export default class ImageUploader extends React.Component<Props> {
    customRequest = config => {};
    /**
     * 上传中、完成、失败都会调用
     * 坑的一批,如果beforeUpload返回了一个Promise,file.originFileObj instanceof File === true
     * 如果beforeUpload返回了一个false,file instanceof File === true
     */
    onChangeWrap: any = ({ file, fileList, event }) => {
        const { maxSize, onChange } = this.props;
        uploadImg(file, maxSize)
            .then((res: any) => {
                onChange(res.url);
            })
            .catch(() => {});
    };

    render() {
        return (
            <Upload
                accept=".jpg,.png,.jpeg"
                showUploadList={false}
                beforeUpload={() => {
                    return false;
                }}
                onChange={this.onChangeWrap}
            >
                <button type="button" className="control-item button upload-button" data-title="插入图片">
                    <PictureFilled />
                </button>
            </Upload>
        );
    }
}

5.2.7 日历

5.2.8 svg

import React from 'react';
import { SvgDiv } from './style';
const RadianSVG = (props: any) => {
    const { themeColor, width = '30px' } = props;
    const svgStr = `<?xml version="1.0" encoding="UTF-8"?>
    <svg style="margin: 0 auto; display: block;" width="375px" height="237px" viewBox="0 0 375 237" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <!-- Generator: Sketch 61 (89581) - https://sketch.com -->
        <title>Path 3</title>
        <desc>Created with Sketch.</desc>
        <defs>
            <path d="M0,0.0370471208 C64.9638617,16.3320105 129.468751,24.4794922 193.514668,24.4794922 C257.560585,24.4794922 318.055696,16.3320105 375,0.0370471208 L375,236.056883 L0,236.056883 L0,0.0370471208 Z" id="path-1"></path>
        </defs>
        <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
            <g id="Path-3">
                <mask id="mask-2" fill="white">
                    <use xlink:href="#path-1"></use>
                </mask>
                <use id="Mask" fill="${themeColor}" xlink:href="#path-1"></use>
                <path d="M0,8.5 C135.12239,41.6333333 260.197584,38.8 375.225582,-1.0658141e-14 C375.075194,-6.66666667 375.075194,-15.4666667 375.225582,-26.4 L0,-26.4 L0,8.5 Z" id="Path-75" fill-opacity="0.2" fill="#FFFFFF" mask="url(#mask-2)"></path>
            </g>
        </g>
    </svg>`;
    return (
        <SvgDiv width={width} dangerouslySetInnerHTML={{ __html: svgStr }} />
    );
};

export default RadianSVG

6. 项目应用技术

7. React技术

8. h5与小程序交互

上一篇 下一篇

猜你喜欢

热点阅读