arch-design.md 36 KB

总:架构的结构

  1. 架构的定义和范围
  2. 技术栈
  3. 数据结构
  4. 服务的接口
  5. 状态管理设计及数据流设计
  6. 目录结构
  7. 核心工具接口
  8. 开发规范(api/component/container/css)
  9. 前端 URL 规划
  10. 路由、页面划分、子组件、常量
  11. 环境变量设计
  12. constants 设计
  13. 开发环境设计
  14. API 处理设计
  15. network request: 网络请求设计
  16. 消息提示处理设计
  17. 错误处理设计
  18. 样式管理设计
  19. 行为统计设计(百度统计)
  20. 日志设计(本地加密日志与线上日志)
  21. 故障诊断设计
  22. 弹出层设计
  23. 组件复用设计
  24. 定时器设计
  25. 人脸识别工具设计 (新加)
  26. 缓存和 PWA 设计
  27. JS 代码混淆设计
  28. Electron / browser 检测与开发运行设计
  29. Electron 打包设计
  30. websocket 设计
  31. 摄像头处理设计
  32. 时间同步设计
  33. Layout 设计
  34. 限流设计
  35. 编辑器设计
  36. 防作弊设计
  37. 局部技术难点
  38. 技术特性验证
  39. 升级策略
  40. 选做功能
  41. Others

Scope: 架构的定义和范围

设计是为了将困难解决在设计阶段,同时为预估工期提高准确度。

适合有点复杂度,追求高质量的项目。

设计目标:

  1. 混淆。(可以增强,但是 2 周以后被破解了呢?)动态更新防破解,组合式的手段。
  2. 可维护性。变高。
  3. 遗留问题,得到处理。
  4. 体验优化。提升速度,消灭页面瑕疵。
  5. 扩展性增强。

为保证设计不快速过时,设计文档仅记录不太变动的核心数据结构和流程,部分重要细节。 某些部分记录下来,也仅仅是为了帮助初次开发,后期如果过时了,可以写明已过时不再维护了。

组件设计;仅为辅助设计的脚手架,后续不维护? 开发环境设计(基础设施、文件目录、readme):不是设计的核心内容,确实将设计实现的重要入口

Tech stack: 技术栈

预计采用的技术栈和工具链如下

  1. Vue 3.2
  2. and-design-vue 3.x or naiveUI
  3. pinia 2
  4. vue router 4.x
  5. typescript 4.5
  6. eslint 8
  7. prettier 2
  8. vite 2.x
  9. javascript-obfuscator 2

Data structure: 数据结构

// 详见:student-client.d.ts
type Store = {
  /** 当前用户 */
  user: {
    id: number;
    /** 身份证号 */
    identityNumber: string;
    /** 学号 */
    studentCodeList: string[];
    // ...
  };
  // ...
};

/** 得到当前客户端版本 1.9.* */
type GetCurrentClientVersion = () => string;
type IsSupportedClientVersion = () => boolean;

Service: 服务的接口

写明层级的目标,设计层级的协调功能(接口)。

tools layer: 工具类,可以脱离项目的函数
api layer: 通信类,处理和服务端数据的交互
service layer: 核心服务类,处理项目的生命周期
data binding layer: 提供给 UI 展示的类
user event handler layer: 提供处理用户触发事件的类

State and data flow: 状态管理设计及数据流设计

单向数据流与复式记账法的比较。

数据的处理原则要遵循以下原则:

  1. 集中统一
  2. 命名清晰
  3. 类型与内容一致
  4. 不包含重复数据
  5. 上层清理数据,比如网络获取 JSON,里面的数据类型不对,要清理以后交给下层

组件的分类(适合复杂业务):

  1. container component: fetch data
  2. presentational component: display data

以下待整理

不涉及到 store 的状态,直接通过 api 请求,当做 component state 来使用。 好处:减少状态共享。

当一个状态被创建出,要明确它的 owner/scope/lifecycle,以及可能的值。 当一个状态被使用时,要明确它可能的值,做好防御性编程。

vue component wrapper. wrapper 里面放权限之类的异步获取数据。comp 里面放业务逻辑和同步的 props。 或者用 vue router 的 before guard

疑问:究竟是在 action 里面还是 container 里面请求数据?依据是什么?

store actions -> state/storage -> page state: page refresh? => sessionStorage (退出登录后要清除部分字段)

错误要直达页面元素呢?-> action return data 好处:页面清爽;数据集中处理 数据的共享与过期

page 上的数据满足以下几点: 1、对数据仅仅做展示 2、数据输入来源为 URL(path & query) 上个页面传递过来的数据(为保证刷新可用,最好通过 URL 做简单的数据传递) 本地存储的数据(一般不是和页面主动关联,是通过 URL 或其他事件导致要取本地存储) 组件生命周期 用户输入 原生事件(网络)

Folder structure: 目录结构

文件名/文件夹 组织模块用

强联系的文件放在一起 文件名有区分度,不要过分依赖文件路径名。否则会出现很多像是 index.js 这样的文件。 文件影响范围:不超出文件夹最好,image 或 package private

.
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── prebuild.mjs
├── prettier.config.js
├── public
│   └── favicon.ico
├── src
│   ├── App.vue
│   ├── api
│   │   ├── loginPage.ts # api 请求
│   ├── assets
│   │   └── logo.png
│   ├── components # 页面其中的组件,可能复用
│   │   ├── PageError404.vue
│   │   ├── QmButton.vue
│   │   ├── QmDialog.vue
│   ├── constants
│   │   └── constants.ts # 项目中的常量。url, name, code,
│   ├── devLogin.ts # 开发模式下自动登录
│   ├── devLoginParams.ts
│   ├── env.d.ts
│   ├── features
│   │   ├── Login
│   │   │   ├── Login.vue
│   │   │   ├── images
│   │   │   │   └── bg.png
│   │   │   └── use
│   │   │   └── \*.ts # 抽象页面的业务逻辑
│   ├── filters
│   │   └── index.ts
│   ├── main.ts
│   ├── plugins
│   │   ├── axiosApp.ts
│   │   ├── axiosIndex.ts
│   │   ├── axiosNoAuth.ts
│   │   ├── axiosNotice.ts
│   │   └── eventBus.ts
│   ├── router
│   │   └── index.ts
│   ├── setups
│   │   └── useTimers.ts
│   ├── store
│   │   └── store.ts
│   ├── styles
│   │   ├── cssvar.css
│   │   ├── global.css
│   │   ├── nprogress.css
│   │   └── tailwind.css
│   ├── types
│   │   ├── 3rd.d.ts
│   │   ├── global.d.ts
│   │   ├── index.ts
│   │   └── student-client.d.ts
│   └── utils
│   ├── renderJSON.ts
│   ├── ua.ts
│   └── utils.ts
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts

Environment Variables: 环境变量设计

PORTABLE_EXECUTABLE_FILE:electron 添加,运行时获取可执行文件的路径

VUE_APP_SKIP_CHECK_NATIVE:允许本地通过 web 来访问学生端服务

VUE_APP_CORE_HOST_URL:本地开发时,后台服务器的地址

VITE_SLS_STORE_NAME:阿里云日志服务地址

VUE_APP_CONFIG_FILE_SEVER_URL=https://ecs-static.qmth.com.cn

VUE_APP_GIT_REPO_VERSION=TO_BE_OVERRIDED # 显示版本号

Constants: constants 设计

export const REMOTE_APPS = [
  ["qq", "QQ"],
  ["teamviewer", "TeamViewer"],
  ["lookmypc", "LookMyPC"],
  ["xt", "协通"],
  ["winaw32", "Symantec PCAnywhere"],
  ["pcaquickconnect", "Symantec PCAnywhere"],
  ["sessioncontroller", "Symantec PCAnywhere"],
  [/sunloginclient/gi, "向日葵"],
  [/sunloginremote/gi, "向日葵"],
  [/选择免安装运行,截图识别/gi, "向日葵"],
  ["wemeetapp", "腾讯会议"],
  ["wechat", "微信"],
] as const;

API status code: API 处理设计

api 的 statusCode:

  1. 200:正常
  2. 401:无权限
  3. 403:无权限
  4. 503:后台限流,要求重试

api 请求是否显示错误:

noErrorMessage: boolean (某些情况下希望网络请求静默处理)

当 statusCode 为非正常时,优先显示 desc 字段,如果没有,则显示“未定义异常:......”

“websocket 重连失败” => "服务器连接失败(websocket)"

network request: 网络请求设计

网络请求可以限流、重试、取消。

给进行中的网络请求覆盖全局的遮罩层

网络请求可设置遮罩层,防止请求过程中用户的误操作、重复操作等等。有 mask,拒绝键盘事件。

globalMaskCount === 0 ====> no mask
log: inc/dec globalMaskCount, reason

loading / error 状态不进 store

但是可以通过请求的状态机来处理?

network reactive design(待细化)

const { data, isLoading, isFinished, error } = useAxios('/api/posts') 好处/难处:

  1. v-loading="isLoading"
  2. data 模板中直接用?因为有 loading 开关
  3. 请求有依赖?不好搞?
  4. 错误处理不好搞?
  5. 重复请求?

network loading state from api and reactive?

get reactive network result?

form inputs / confirm / throttle? / error => network params => res/loading => display result 多个 loading 状态转换?

Error handle: 错误处理设计

错误的边界是用户,无论怎么处理,错误的不可避免的,一定要通知到用户,或不给用户错误的结果(先成功后失败)。

错误分为两种,一种是给用户看的错误,一种是给程序排错或恢复的错误。
错误应该抛出 Error 对象,不能 throw 字符串等类型。

用户查看类错误

  1. 错误以弹窗形式提醒用户。
  2. 错误信息应有识别度,方便用户反馈。
  3. 错误应隐藏敏感的技术信息,比如使用的技术种类。
  4. 重试时应该不报错,默默重试,但要记日志。

程序排错类错误包括:

  1. JS 错误,需预警,保证及时处理
  2. 网络错误,记日志
  3. 环境错误,提示用户,记日志
  4. 兜底的错误记录

网络的错误处理:

  1. 补充后台的错误码。如果后台没有统一的,则只能模糊处理。
  2. 未知错误(待讨论):a、显示错误的详细信息 b、错误监控 c、不显示详细信息(不能定位错误)

潜在错误位置

  1. 在 vue 组件中,某些方法可能会在 vue 组件被销毁后执行。
  2. 错误内部处理被特殊压制?定制错误处理,axios config ?
  3. 发生错误后,流程逻辑是否正常。

Promise 的错误处理实践

  1. 将长串的流程拆解成多个 promise,利用 promise 来处理错误嵌套?
  2. 将 promise 嵌套超过两层的抽象出来变成 async 函数
  3. early return ?? 抽到流程的第一层;throw 不被处理从底层抛到高层;中间层一般不处理底层的错误,而且可能自己 throw guardError;
  4. 错误内部处理?顶层不知道用户友好的错误 => 如果追求这个体验,那么有大量的消息在中间层传递。大部分软件是不追求这个的。

reference

  1. JavaScript 错误处理完全指南

Logging: 日志设计(本地加密日志与线上日志)

同时记录到本地日志,以及网络日志,网络日志部分可以做监控提醒。

本地日志要采用加密算法,同时在系统处于登录界面时,将本地日志上传。

日志的主要内容为:

  1. 可识别用户
  2. 环境信息(硬件、网络、OS、process)
  3. 核心流程的操作
  4. 错误信息

日志分等级记录

  1. debug,收集个别考生的全日志信息?如何设置它?(ctrl 5 秒内连按 5 次,启动 debug 日志级别)
  2. log,全流程日志。
  3. warn,疑似错误信息。(low network speed)
  4. error,错误信息。(硬件错误,文件不存在)

logger => (destination: baidu / console / file / aliyun) (type: error / warning / operation) (easy / format / single / conf)

打印日志的注意事项

  1. 打印一个 JS error 对象? ===> new Error('err').stack (IE10+)
  2. Promise 的异常通过 Promise.reason 来获取

Page URLs: 前端 URL 规划

前端 URL 规划

  1. https://ccnu.exam-cloud.cn/oe-web/index.html
  2. https://ccnu.exam-cloud.cn/oe-web/{js, css, img, fonts, service-worker.js}/*
  3. https://ccnu.exam-cloud.cn/admin/index.html
  4. https://ccnu.exam-cloud.cn/admin/{js, css, img, fonts, service-worker.js}/*
  5. https://ccnu.exam-cloud.cn/photo-upload/index.html
  6. https://ccnu.exam-cloud.cn/photo-upload/{js, css, img, fonts, service-worker.js}/*
  7. https://ccnu.exam-cloud.cn/oe-wap/index.html
  8. https://ccnu.exam-cloud.cn/oe-wap/{js, css, img, fonts, service-worker.js}/*
  9. https://ccnu.exam-cloud.cn/api/*

Router: 路由、页面划分、常量

学生端是属于严格受控的页面跳转,既考虑刷新,也考虑缓存。
要禁用链接的拖动或通过按 Ctrl 点击链接多开窗口。

页面划分

  1. Login
  2. MainLayout
  3. OnlineExamContainer
  4. ....
  5. ModifyPassword
  6. ExamingHome
  7. ExamEnd

Core utils interface: 核心工具接口

clientVersion.ts 管理可用客户端版本。
api: getCurrentClientVersion isSupportedClientVersion

updateManager.ts 应用(非客户端)是否应该更新了
api: isNewWebAppAvailable isNewBackendAvailable getNewBackendDesc

native.ts 管理原生及系统命令,详细说明 path 和相对 path
api: isElectron execCmd readFile getRemoteApps getVirtualCams

runtimeMonitor.ts 检测当前运行的环境是否合法
检测是否打开了控制台;检测是否使用了高版本的 chrome(feature 检测) 检测 onResize;截屏

todolist:

  1. md5 文件校验工具方法
  2. auth 认证
  3. 工具类的测试

Layout 设计

仅有一个 MainLayout 供复用,在 router 里面设置 layout。

  • MainLayout:
  • SideBar
  • Header
    • MainContent
    • MainContent 不包含统一的 margin,但为了方便,提供全局的 class 来设置 MainContent 的 margin.

Styles design: 样式管理设计

全局的样式包含以下功能:

  1. reset style
  2. 引入组件库的 style(看能否按需引入)
  3. 全局的字体
  4. 全局的 css variables(颜色、字体大小)
  5. 全局的 class。title-text。primary-text。secondary-text。comment-text。

组件内部的样式应采用 scoped,将样式限制在组件内起作用。

Notice handle: 消息提示处理设计

提示的种类:成功、警告、失败、进度

提示不能过于频繁,要对重复的消息提示做限流处理。

部分流程性质的消息提示,可直接更新内容,延长显示时间。比如“正在上传文件”,“上传成功”。

Cache and PWA: 缓存的设计

检查各个服务器资源的缓存设置:

  1. 后台服务器的 HTML/JS/CSS/image
  2. 阿里云上的 studentClient.json / backgroundImage.

service worker cache html/js/css/image 保证首页快速可用。 保证在运行过程中,可以更新到最新版本的 js。

缓存分为应用层和网络层。 应用层的缓存主要是将 store 缓存在 sessionStorage。

都要注意缓存失效的问题,比如用户点击退出回到登录界面,通过权限获取的缓存就应该失效。

PWA 最主要的功能是 cache cache 要判断:

  1. 对于更新不敏感的资源体验最好
  2. 注意缓存失效的策略
  3. 名字作为缓存的名称
  4. 页面 html 作为缓存的入口,这里是不确定的,但它包含的资源可以通过名称来跟随 html 是否缓存
  5. 网络请求的缓存。post 的不应该缓存。get 可以缓存,刷新并且网络是通畅的情况,请求新的网络请求来了,那么可以替换。
  6. 手动清缓存。做一个按钮,让用户手动点击,unregister service worker。

service worker ; image cache; audio cache/prefetch

dev settings: 开发环境设计

再使用了 vite 来作为开发服务器后,还有几个可以提升开发效率的功能需要研究。

http/ws proxy
开发模式下:
设置一个服务器 ip 地址,即满足所有的 https/wss 转发。
设置学校域名
设置是否为测试环境。最好能 preval,减少代码信息泄露。参考 preval.marco / ts-transformer-preval

JS obfuscation: JS 混淆设计

tools

  1. rollup.js
  2. js-obfuscator

    {
    rotateStringArray: true,
    // 防止打开devtools,打开后程序会冻结
    // debugProtection: true,
    // debugProtectionInterval: true,
    // // 防止代码格式化
    // selfDefending: true,
    // // 要测试sourcemap是否还有用,以下sourcemap配置不起作用
    // sourceMap: true,
    // sourceMapBaseUrl: "http://localhost",
    // sourceMapFileName: "exam",
    // sourceMapMode: "separate",
    splitStrings: true,
    // stringArrayEncoding: ["none", "base64", "rc4"],
    }
    

消极影响:

查错会很困难,会变慢。

Todolist:

// sessionStorage design // store 防破解?仅保存 user, QECSConfig 信息

前端加密解密设计

前后端协商加密解密,避免接口敏感信息被暴露。

  1. 前后端协商 N(比如 5)种加密解密方式
  2. 后端选择加密的组合方式,1 到 2 种进行连续加密
  3. 前端在需要加密的接口使用后端规定的加密方式进行连续加密,参数要加上时间戳
  4. 后端验证通过后,返回的 response 也是用之前加密的方式加密过,前端再进行相应的解密
  5. 加密方式被禁用后,仅影响开考接口?
  6. 后端对加密方案的选择:间隔一段时间才换加密组合

Prevent cheating: 防作弊设计

  1. 检测伪装 Chrome 版本号的行为 - 检测 Chrome 高能力特性

tech challenges: 局部技术难点

技术难题:

  1. iframe 的摄像头等数据共享和通信
  2. 学生作答编辑器
  3. 音频播放器
  4. 摄像头各种错误捕捉和提示
  5. webpack / babel 提前执行 js,减少代码长度。确认开发环境不检测 native 的代码。
  6. websocket as a library
  7. network with / without auth
  8. performance
  9. event design: 作为解耦核心流程的手段,用来反调试

Tech verify: 技术特性验证

  • Electron 1.7.16 支持 Proxy / CSS var 等。
  • naive ui 核心组件在 Electron 的适配
  • 开考、交卷接口由于接口超时被重复调用,会发生什么?
  • query 的最佳实践?
  1. 如果有不需要链接被分享、收藏、日志或被直接跳转进来,prefer state。typed state?
  2. 传确定的简单数据。类型转换怎么做?
  • select 初始值类型的研究
  • form 字段 null/undefined 的最佳实践

query params: 1. select,未选择,默认为 undefined? 默认为 undefined,则可不传递到后台. clearable => undefined 2. input,未填写,默认为 undefined,查询不传递。如果后台有需要,可以通过 watch 将‘’转 undefined 3. 在 axios paramsSerializer 中处理 query parameter,

https://github.com/axios/axios/issues/1139 https://www.npmjs.com/package/qs paramsSerializer QS defaults: null => '' ; undefined => omit ; change arrayFormat 'comma'

Upgrade strategy: 升级策略

新增加 API 支持新前端,后端维护多套 API,一次升级。
前端保持多套,新旧共存。
nginx 通过域名来分发新旧前端代码。
如此,可支持前端灰度升级。

Optional feature: 选做功能

  1. Electron crash 日志捕获及上传
  2. 不适用的宿主环境日志
  3. Electron 启动速度
  4. 非生产环境,右上角显示 tip,并可以打开 console
  5. Electron 加固(替换网络层,用 C 来发网络请求,探索可行性)
  6. 先得到抓拍的 png 照片,再通过 canvas 得到 jpeg 格式的照片。js image manipulation

待整理

======测试规范===== 工具类充分测试 服务类模拟测试 前端单元测试:dom 是否如期变化,api 是否发出

script 抽出来 business logic code? 不好抽取,因为大部分和 UI 相关。将 UI 相关的类抽取成不依赖 UI 的属性集合比如 ImageLike.

======测试规范 end=====

======校验规范===== 校验字段的 UI 显示 必须校验 校验消息提示 Form rules validator 校验的问题在于:代码太多,重复太多,干扰正常逻辑 ======校验规范 end=====

======Mock 的研究====== api mock? 1. 基于 api 规范。 2. 随机生成数据。

easy mock 线上版。 easy mock 线下版。可以迁移。 基本满足了需求。

mock 的优缺点? 缺点: 1. 内网更新比较麻烦 2. 有转换成本

======Mock 的研究 end======

Others

======设计的遗留问题和感悟=====

前端架构的责任: 1. 架构是方向性的。提出目标和方向,无论底层技术和框架如何发展,目标和方向都不会变,甚至能更好的利用到变化。 2. 架构是管理变化的。当可能的变化点来临,架构能够处理。 3. 架构是划分权力和责任的。即不同的人员负责不同的模块和层级,互相有大致明确的依赖和边界。 4. 架构是负责增长的。 5. 架构是深刻理解了底层技术和业务需求,搭建出的架构既稳固又灵活。 _. 方向性:易使用、易维护、高性能、高兼容、易调试、日志完善、安全性、文档、code review。。。前端工程化 _. 日志完善 _. 作用域可控 _. 接口规范 _. 数据类型 => IDE 编程 _. 测试

设计一个项目时:

  1. 先垂直切分,分为几层

架构:关注生命周期,切割生命周期,关注模块,关注模块间的通信 工具:将常用操作收敛在一起 业务:具体细节

几种设计方式:

  1. obj.method 耦合,同步
  2. postEvent & obj 松耦合,异步,但程序更抽象,必须把模块设计好
  3. store & emit 共享数据,模块耦合程度更低

看得远,才能做设计。 如何看得远?经验,想象力,推理能力。

几种程序的架构方式:

  1. app start/register module
  2. plugin callback
  3. component nest

前端架构:减少重复劳动。库、组件库、流程制定、高难度工具。

先开发逻辑?还是同时开发样式? 先设计通用组件?还是后面提取?

程序设计能力和过程是估工期的重要因素。

架构与收纳: 方便获取(就近) 方便记忆(分类摆放) 容易还原(将获取模式固定,避免系统状态杂乱) 流程抽象

设计程序时: 1. 分解流程(操作人的责任范围即为一个流程) 2. 制定接口(接口即为双方开发、调试时查找问题的边界) 3. 保留日志(接收的输入,返回的输出) 4. 错误处理

看源代码: 1. 模块关系 2. 模块加载 3. 模块初始化 4. 模块动态关系

架构:

  1. 架构的核心工作是写出核心生命周期。由核心模块去实现核心生命周期。每个核心模块又有自己的生命周期。
  2. 如何分出模块?模块划分的标准:包含一段核心流程。可以独立更新。保持接口服务的稳定性。复用是附加好处。
  3. 如何一眼看出两个模块之间的关系。交互的数据量大小。

框架的目的: 1. 整体可控,整体流程和接口 2. 给模块保证接口,内部可发挥,可 hack,不影响整体的质量

结构良好的程序: 1. 总组件控制流程 2. 子组件控制细节 3. 总组件和子组件通过接口来沟通 结构不好的程序: 1. 总组件只是个入口 2. 细节和跳转逻辑都在每一个子组件中

有没有可能总组件无法控制流程? 如果父子、子子组件耦合过高,那么很难梳理出清晰的总流程。

MVC 分层与组织的树状图? 组织结构 CEO 下面总监。总监下面经理。经理下面员工。 总监层? 每一个层级的职责。目标、进度、指挥、协调。

编程时脑袋里应该存放什么信息(超越本能,练习专业能力):

  1. 流程逻辑
  2. 当前打开的文件,之前打开的文件
  3. 预期结果是什么

究竟是前端编织业务,还是后台自行沟通?

组件:

  1. 组件本身的 scope,作为一个对象,需要注册。
  2. 组件的本身包含:模板、样式、逻辑。模板是内部的,也可以接受输入。由于 CSS 的特性,会受全局 CSS 的影响。JS 是内部的,接受外部传入的 props。
  3. 模板接受外部的为 slot。
  4. CSS 接受外部的 style 和 class。都是只接受当前层?其他改变样式的通过 props 传入。
  5. JS 通过外部传入。包括事件回调。接口包括变量和事件。
  6. 组件封装,主要是在业务层作用。以实现业务为主,根据业务特点来确定通用性,但不追求复用。
  7. 组件复用主要是框架层。
  8. 组件间通信。props 和 event。props 的耦合性更强,耦合并非坏处,逻辑上该紧耦合就紧耦合。
  9. vuex。全局状态的分发。状态的更新方法和传递。

从需求推导框架的设计,最后到项目的架构: 用户需求: 1. 更快的响应 2. 更细致的体验 3. 更好的使用机器的特性,如下 4. 屏幕大小的适配 5. GPS,摄像头,通知 6. 由于数据的物理距离的亲密性,在设备内计算是必须的 环境变化: 1. 硬件更强,CPU 运算更快 2. 网络更好,带宽,延时都有进步 3. 需要考虑电池 4. 移动端的屏幕 5. 浏览器 API 更强大 6. 前端开发工具链更发达 通用框架的需求: 1. 降低前端开发的复杂性,如下 2. 渲染 => 将数据变成 HTML。模板。DOM 性能 3. 将渲染的流程优化。 a. 即模板不变,只是根据数据动态改变 HTML,和传递用户的输入。 b. 将逻辑集中到数据操作。因为数据逻辑更加易于测试和推导。 c. 如何监控数据?deep? native value? Array value? 4. 组件式开发。例子:UI 库,ant-design。可定制的部分?样式、行为。 a. 如何支持组件?核心问题。查看 Vue 的源码。 b. 从模板来的组件树。编译原理?? 5. 数据的流转。store,single source of truth. 数据的共享和隔离。不可变数据? 6. router。适合 web 的一种将应用模块化的方式。用来组织程序,分包,并给用户在程序中定位的能力。

原理:假如自己来写一个前端框架应该怎么写? 如何组织一个应用程序?分层。模块化。将应用程序模块化。

项目开始编码前问几个问题: 1. 有几个 module? 2. 是否需要划分 module?划分 module 的原则是,项目足够大,需要划分 module 来降低复杂度。未来有可能划分吗? 3. 划分模块后,store/routes 和 modules 放在一起

view 的具体划分 components 纯视图组件,可共用。比如 view libs,iview 之类的。 views 和 routes 相关的组件。带状态,最起码和 URL 相关。 组件划分的需求(终极目标降低复杂度): 1. 可被复用。如果很通用,非业务组件,就追求这一点。业务组件很难被复用。 2. 业务组件的目标。利于维护,需求来自于业务划分。 利于维护可细化为: 1. 分离相互可以独立的功能,每一个模块可以独立完成一个功能,即因为有了这个模块存在,项目的复杂度降低了。(树形划分功能,并将功能之间的关系连接) 2. 接口清晰。即功能之间的关系最小化。

数据类型: 全局公用一份,比如 user,privilege 衍生数据,通过 computed 来拿?getter 数据逻辑: 不同 module 的数据不共享,由各自的 module 去维护更新。如果 module 之间的数据不一致,可以设置全局标志位,或由页面刷新来保证一致性。 Modal 的数据有包含它的组件(Main)提供,Main 提供数据处理完的回调。 在 Modal 里完成对数据的校验和提交。 增加和修改的功能抽离为一个组件:所属的代码较多,和主功能牵扯较少。 组件与 store 数据之间的关系: 1. 尽可能减少使用 store 数据。优先使用 props 传递,这样组件更加独立。 2. 数据集中使用 3. 尽量少修改数据 4. 做好事件回调,将组件内发生的事情传递出去。

设计的原则(主要从人的大脑理解能力出发):

  1. 顺序。一个系统切分为多个流程。流程又切分为子流程。流程主要是顺序执行的,较少判断跳转和循环,杜绝随意的流程间的连接。
  2. 集中。具有相同性质的事务,应该集中处理。

设计一个系统就好像给一个团队分工,系统分工不必关心具体人的能力和性格,但是需要知道底层工具组件的能力。 甚至一个系统最终是给不同的人使用的,那么这些角色的操作流程、效率、配合和权限也要考虑在系统中。

系统的演化:

  1. 混为一团。
  2. 识别出主要模块,模块间连接没有规范,物理上还在一起。
  3. 模块间通信规范,物理上没分离。
  4. 模块物理分离。

https://www.infoq.cn/article/AJ0S3IDEHyusNms0bTf1 深度解读当代前端架构演进与趋势 MVC: 将 view 和 model 分开,这两个都是比较复杂的部分,且可以独立变化,并且各自的细粒度组件可以复用。controller 是粘合剂。 经典 MVC 时代,视图没有标准(图形操作系统还没有诞生),视图的输入输出比较底层,比如接收键盘输入,这个时候 controller 就做了大量非本质性工作。 controller update model. controller update view. event & reference 都被使用了。

Application Model: 某些状态是属于 view 的,但又不能放 view 里面,因为会影响 view 的通用性,同时这些状态又是 model 派生出来的,但理论上 model 与 view 是无关的,所以 model 来维护这些状态很奇怪。 比如超过某个数字,把 label 变成红色。 这时就创造出 AM 这个概念了。AM 来维护 view 的状态。

以上 MVC,

现代个人操作系统大大简化了输入输出,PC OS 一开始就为个人设计,所以 GUI 是重中之重,view 的功能强大了,却同时简化了,大部分 controller 要做的底层部分被简化掉了,变成了 view 内置的逻辑。

MVVM: data-binding Model 在哪里? view 是 template & style?

MVVM => 重点在于 VM 是 data binding view,data 改变则 view 自动改变

MVVM => data(model) -> transform(view model) -> view MVP MVC (UI no auto update)

前端的变化:业务流程优化!以往 web 前端弱小的时候,流程是在服务端控制的多页面(业务窗口)完成的,现在前端强大了,虽然可能还是多页面,但已经是由前端控制的流程。 即由多业务窗口多次提交,变成统一业务窗口了。

改变开发的思维模式: 首先设想页面要展现什么数据(暂时不考虑组件) 假设数据已得到(并且要设计数据模型) 数据会如何变化

总是在第一次采用最直觉的写法完成任务,之后再优化性能和体验 比如对于易过期的数据,总是获取而不考虑缓存

关于 null 的思考 boolean select with null? and filter null? component: state: null; // default api pass state null/false/true? api: if pass null to api 是可以考虑的 search is null? search 时 null 的话,不传。 edit 时,null 必须要传到后台 (将 enabled 修改为 null) component prop forSearch? no search { enabled} 当传递到 api 时,去掉 null

前端开发的几大主题:

  1. 校验
  2. 异常处理
  3. 数据流
  4. 安全
  5. 图形化
  6. 数据模型

一个应用如果只考虑正确情况,那它的架构和状态应该是怎么样? 此为一个简化版本,应用的是第一性原理。 然后考虑一个应用如果考虑网络请求,网络错误,日志,性能,动画,复用,错误提示,校验?

======设计的遗留问题和感悟=====

======脚手架设计====== Vue 框架 avue 评估

混用 avue? theme? 样式覆盖 网络调用 vuex avue 的 vue 使用的是过时版本,它的长期发展不明确,还是自己维护组件比较靠谱。 q-crud: 增删改查 q-table: q-page: 脚手架快速启动: login page 4xx/500 page router store 权限/角色

只使用 avue 的 表单+表格+新增,分页,网络请求,错误处理

脚手架初始化公共功能 前端公共模块 提炼公共接口标准 提炼公共设计模式

vue 权限管理: 加入脚手架 scrollbar; visible/hide/width restore compatibility document.title i18n

CRUD: paging, save page state? 最低要求,一个丰富的 sample 页面,增删改查分页上传。最高要求:配置化(自动生成 route, page,被集成,但不进 git);或者通用页面,接收参数。

======componet 设计===== 一个设计良好的组件:

  1. 能简单的完成基本任务。
  2. API 符合直觉。名称。
  3. 不影响外部状态(样式)。
  4. 错误要抛出,出错至少要对开发友好。
  5. 清晰完整的使用文档。
  6. 可平滑升级。
  7. 如果是组件库,那么组件要有相似性,api、样式。
  8. 样式 scoped 很难被外部覆盖,所以最好基于 class 的。

component 的接口:

  1. 外部传进的 props,包含数据和 callback
  2. component globals, $store, $http, window
  3. 内部状态?只是影响组件内部逻辑是最好的。
  4. 内部状态由网络获取的部分?与整体的 store 没有关系。
  5. 直接发起请求影响服务器?可能影响 store 的数据一致性,那么应该有 store 去统一操作。
  6. mapstate from store. component 的依赖是个多重来源的,并不由 parent component 完全控制。
  7. 与 graphql 的关系

根据项目的大小划分 component 的责任: 责任列表:

  1. 显示数据
  2. 监听用户操作
  3. 获取数据
  4. 处理数据

中小项目可以将以上合在一起。大型项目中的 component 会很大很复杂。 需要将 component 的功能简化,变成只 render。 但 component 需要有办法声明数据依赖,并将对数据的影响传递出去。

container component: fetch data presentational component: display data

controlled component: 由上层获取数据,数据有变化,也通过 onchange 事件通知上层去处理,或者上层传递 props 函数去处理。

container component 的子组件层级里面再包含 container component 也是允许的,只要子组件是自己管理它获取的状态就可以。

css 共享和隔离? global css, component css

component 的职责:有无网络请求?有。

======componet 设计 end=====

======脚手架设计 end======