美文网首页React
基于react框架的项目组需要具备的知识统计

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

作者: 送你一堆小心心 | 来源:发表于2021-06-07 16:52 被阅读0次

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

    image.png

    1. 项目前期准备

    1.1 开发环境搭建

    1.2 开发流程

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

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

    1.2.3 开发阶段需要系统

    • 测试bug单 (如tapd,jira等
    • 开发设计与问题文档维护 (wiki,confluence,语雀,有道等
    • UI给稿平台(蓝湖等
    • 提测平台(公司内部提供
    • 日志排查(公司提供或者阿里云日志等
    • 接口平台 (有道,Middleman等)

    1.3 代码提交流程

    • 向组长申请(f / bg)分支,f为当天迭代内容分支,bg为bug紧急上线分支, 均在master代码上操作
    • 在本地分支开发自测,提到dev分支进行与后端联调
    • dev自测验证后,提测交付测试

    1.4 联调流程

    • 公司内部使用bff,请查看文章
    • 只是前后端联调,询问后端接口文档地址,确定参数信息即可。

    1.5 问题排查原则

    • 抓包定位问题模块
    • 寻求对应模块开发人员解决
    • 与组长确定是否为紧急bug,紧急bug当天修复上线,其余随迭代
    • 开发不可擅自接需求、bug,需经过组内产品或组长确认

    1.6 需求依赖系统

    • 测试bug单 (如tapd,jira等
    • 开发设计与问题文档维护 (wiki,confluence,语雀,有道等
    • UI给稿平台(蓝湖等
    • 提测平台(公司内部提供
    • 日志排查(公司提供或者阿里云日志等
    • 接口平台 (有道,Middleman等)

    2. 项目整体保障机制

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

    2.1 需求评审

    • 确认对接其他业务组的需求确认其他业务组已提前上线
    • 确认其他业务组需求对接的各岗位负责人
    • 确认风控规则内容
    • 确认业务监控内容
    • 确认核心业务流程内容

    2.2 技术评审

    • 梳理业务影响范围
    • 预评估开发周期
    • 针对历史活动数据如何兼容处理
    • 现有的技术框架能否实现业务需求
    • 是否有领域模型
    • 是否有第三方对接
    • 是否有跨部门对接协助
    • 需求冲突
    • 数据处理
    • 权益类逻辑评审
    • 确认性能要求指标
    • 数据是否需要脱敏

    2.3 UI评审

    • 确认UI设计调整范围:新旧设计稿对比
    • 确认UI切图是否已提供
    • 确认SVG图能否直接从蓝湖导出
    • 确认交互细节UI设计师是否特殊讲解说明
    • 确认产品是否有定稿,确保不再大范围改动

    2.4 用例评审

    • 评估用例覆盖业务范围

    2.5 开发

    • 协程泄露
    • 内存泄露
    • 安全性(用户态,防刷
    • 确认日志记录正常
    • 确认数据一致性(数据上报,传播链路
    • 接口幂等
    • 变量竞争
    • 数据库 ddl 与索引 *
    • Code Review :
      • 评估代码是否在各层职责范围内
      • 有无低级设计缺陷
      • 通用安全校验等是否遗漏
      • 是否存在相同可复用逻辑
      • 错误异常等处理是否合理
      • 代码可读性(命名明确,代码是否简单易懂
      • 集成外部服务设计合理性
      • 适配不同场景的设计合理性
      • c端接口是否加分布式锁
      • 数据库查询是否使用了索引、是否有join 表情况(一般情况下不建议join表,特殊数据统计可以join
      • 业务状态常量是否 抽离到常量文件统一维护
      • 代码是否有一定量的注释。复杂实现最好有一定的讲解内容
      • 缓存是否有被击穿的可能性。(empty值需要也需要设置empty的缓存
      • 高频查询接口是否有缓存
      • 第三方对接接口是否有足够的日志记录

    2.6 测试

    • 登录态处理
    • 接口核心数据校验,不信任外部输入,如 用户中心 ID , 手机号
    • 操作类是否需要加入人机防刷
    • 前端网络协议问题,统一https协议,特别像 图片地址
    • 前端兼容适配问题,如 px未转换成rem
    • 事件移除问题,如 监听事件未remove , 定时器setInterval
    • c端bff调用接口尽量并行处理
    • 代码逻辑是否做了容错处理,防止页面乱码(特别关注点:空指针)
    • 是否存在相同可复用逻辑
    • 代码可读性(命名明确,代码是否简单易懂)
    • react组件性能问题(props未改变情况下避免不必要的渲染hook组件合理使用useCallback,useMemo
    • 网页性能:图片压缩处理,超过500KB的图片需要压缩
    • 网页性能:列表页面图片量大的情况,图片需要做懒加载处理
    • 网页性能:能用样式实现的效果,尽量用样式,减少图片调用
    • alert,debug,console等调试代码删除
    • 浮点数计算是否有进行精度问题处理(特别关注金额相关精度问题)
    • 代码逻辑业务解耦

    2.7 验收

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

    • 提前一天定好会议室,会议在发版当天4点进行,历时1个小时。
    • 产品在后台创建含所有功能点的活动,发布,以及复制更换准入再次发布,验收至少两个活动。
    • 创建临时微信群聊,用于分享活动数据。
    • 参会人员扫码以'新用户' 、'被邀请用户'、'老用户' 三种身份玩转活动。
    • 全员参与完毕后,在后台数据明细中,检验数据是否正确。
    • 无异议,迭代需求无明显bug,测试准备发布。
    • 有体验优化点,产品记录,后续迭代优化。

    2.8 发布

    • 根据发布清单列表确认发布分支
    • 检查确认发布清单列表:
      需求名,分支名,sql,apollo,灰度租户,缓存处理,开发责任人,遗留问题(bug链接),发布依赖,备注
    • 产品确认遗留问题。

    2.9 发布后

    • 上线后关注 error log 日志
    • 上线后关注数据库负载
    • 上线后关注慢 sql 日志
    • 上线后关注 grafana 监控
    • 生产环境特性验收,已发布后同步团队
    • 出现问题立即回滚,回滚后关注功能是否恢复正常。

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

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

    3.1 应用AOP思想的好处

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

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

    4. 项目文档维护

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

    4.1 开发流程文档

    • code review清单
    • 提测流程

    4.2 前端文档

    • 环境搭建
    • 代码规范
    • 项目仓库 & 权限账户
    • 组件文档
    • 采坑记录
    • 代码设计(关键业务信息
    • 第三方对接
    • 与其他组对接
    • 知识笔记
    • 调研文档

    4.3 后端文档

    • 环境搭建
    • 项目仓库 & 权限账户
    • 代码规范
    • SQL注意事项
    • 日志规范
    • 采坑记录
    • 性能相关
    • 代码设计(模块设计 / 数据库表设计

    4.4 测试文档

    • 测试用例
    • 自动化测试
    • 发布流程
    • 采坑记录

    4.5 入职文档(新人

    4.6 离职交接文档(旧人

    4.7 质量记录

    • 事故记录
    • 质量保障机制

    4.8 其他业务相关文档记录

    5. 项目组件开发

    5.1 组件设计

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

    公共组件, 用于全局引用

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


    image.png

    共有五层内容:

    • constant.ts 用于存储常量, 例如: 图片的url地址,固定数据结构等

      image.png
    • index.tsx 组件核心代码


      image.png
    • README.md 组件文档!!!为什么加了重点号,因为随着项目的复杂化加深,开发人员的增多,组件需要维护以及真正用上,避免重复开发,我们需要有一个持续化更新的文档去查看以及校验,方便开发人员的使用。


      image.png
    • styled.tsx 用于编写样式,为了方便实现组件化,解决了css全局命名空间,避免样式冲突的问题,维护起来更加方便。在大型项目中,开发人员可以方便的组合组件。使用方法查看此篇文章:


      image.png
    • types.ts 用于ts定义

    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与小程序交互

    相关文章

      网友评论

        本文标题:基于react框架的项目组需要具备的知识统计

        本文链接:https://www.haomeiwen.com/subject/qmbleltx.html