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

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

image.png

1. 项目前期准备

1.1 开发环境搭建

  • 安装git https://git-scm.com/downloads
  • 安装node https://nodejs.org/zh-cn/
  • 安装nvm https://github.com/nvm-sh/nvm
  • 安装postman https://www.postman.com/
  • 安装抓包工具 教程指引 -- whistle安卓手机端抓包方法 - (jianshu.com)
  • 安装vscode https://code.visualstudio.com/

    vscode 必备插件

    • Gitlens - 最强git增强插件
    • Prettier – 代码格式化工具
    • Code Spell Checker – 变量拼写检查
    • git-commit-plugin – Git提交信息格式化
    • Document This – 注释生成
    • Todo Tree – 重要注释高亮
    • Eslint – 代码风格约束
    • Turbo Console Log - console日志输出
  • 配置常用hosts(根据自身项目而定

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 应用例子 - 通过修饰器实现编程思想

  • 读懂ES7中javascript修饰器 - SegmentFault 思否
    上面的文章通俗易懂,适合初学者理解

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 {
    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 (
            
`共计 ${total} 条`} showSizeChanger showQuickJumper defaultCurrent={page} current={page} pageSize={pageSize} total={total} onChange={onShowSizeChange} />
); } }

5.2.2 表单

{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.getFieldDecorator(name as never, options)(FormComponent)} ); })}

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 {
    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 (
            

            
        );
    }
}
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 (
        
            

效果展示

{content}

); }; const Thumbnail = (props: ThumbnailProps) => { return ( ThumbnailContent(props)} color={'#fff'}> ); }; 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;
    imageControls?: Array;
    [propName: string]: any;
}
export default class Editor extends React.Component {
    state = {
        editorVal: BraftEditor.createEditorState(null)
    };
    isInit = false; //是否初始化完成
    render() {
        const extendControls: any = [
            {
                key: 'antd-uploader',
                type: 'component',
                component: 
            }
        ];
        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: `

${val}

` value = BraftEditor.createEditorState(data); } if (type === 'new' && !this.isInit && cover_url) { this.isInit = true; let src = encodeURI(cover_url); let img = `

${val}

`; value = BraftEditor.createEditorState(img); } return ( ); } //富文本编辑器图片上传 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 { 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 ( { return false; }} onChange={this.onChangeWrap} > ); } }

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 = `
    
        
        Path 3
        Created with Sketch.
        
            
        
        
            
                
                    
                
                
                
            
        
    `;
    return (
        
    );
};

export default RadianSVG

6. 项目应用技术

7. React技术

8. h5与小程序交互

你可能感兴趣的:(基于react框架的项目组需要具备的知识统计)