if 我是前端 Leader, 前端业务开发做不做设计?

《if 我是前端 Leader 系列》已经好久没更新了,我这两三年都去哪了?

图片[1]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

Twitter

有可能掉进了一个黑洞。不是 Byte Dance,现在国内大小公司都卷,整体行业的已经被带偏了,还有向其他行业蔓延的趋势… 真是好的不学

那我现在怎么又开始写文章了?

因为现在我不卷工作了,公司也开始的考勤打卡,我觉得挺好了,一切按规矩办事,到点就弹射下班。

工作只是生活的一部分而已,工作的目的本来就是为了生活过得更好不是吗?这才应该是正常的人生形态,你说是不是? 2023 年了,梦也该醒了

图片[2]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

老贼

另外,我这边也想挪坑了,Base 珠海、远程也可以,有坑位推荐的可以私信我,感激不尽。

回到正题,做业务前端开发要不要做设计呢?我觉得大部分情况不需要,简单的增删改查业务,没有必要浪费时间去做这些,只要在产品侧描述清楚就行了。

如果业务比较复杂、涉及到多人分工和共识建立、而且项目预留的充裕的时间给开发者做预研和设计,那么做一下设计还是有必要的。

那怎么做呢?本文就介绍一下我在这方面的探索,希望能给读者提供一些借鉴。

0. 为什么需要设计?

图片[3]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

一次做好

1. 画好业务流程图

设计的第一步是梳理业务。这个不是产品的责任吗?产品会提供 PRD、原型、用户故事等需求输入,但是下游的开发、测试还需要进一步消化,因为职责的不同,立场和关注点也是有差异的。

因此,笔者设计了一套适合前端的业务流程图绘制规范。关注点在于:

1.1 图例和要点

图片[4]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

示例

图例:

图片[5]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

图例

要点:

通过流程图可以提供什么信息?

无法提供什么信息?

→ 这部分由概要设计来弥补

1.2 案例

统一使用 draw.io 来绘制流程图。将流程图保存在项目根目录下的 docs 下,跟随代码一起存储和更新。

推荐 VSCode draw.io 插件

案例 1: 营销拼团

图片[6]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

拼团.jpg

要点:

案例 2: 优惠券

图片[7]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

优惠券

要点:

案例 3: 活动预约

图片[8]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

活动预约流程图

2. 做好概要设计

业务流程图可以梳理待开发的业务流程、业务主体状态、依赖关系等等。这里并没有包含太多前端技术设计细节,概要设计就是为了弥补这块的空白。

我在 if 我是前端团队 Leader,怎么做好概要设计 讲过类似的话题,可以结合一起看吧。

2.1 页面/模块拆分

根据业务需求以及产品原型对业务域内的页面进行拆分。页面拆分是前端设计中最简单的一个环节,主要涉及:

页面通信协议设计。

目录规划。原则是按业务聚合而不是职能聚合。我们推荐将同一个业务域下的组件、API、模型、页面都聚在一起,而不是按照功能分散在程序多处。

# ❌ 按职能聚合
/components
  /a
  /b
  /c
/pages
  /page-a
  /page-c
/api
/utils

# ✅ 按业务域聚合
/modules
   domain-a/     # 业务模块
     components/
       /a
       /b
     page-a.tsx
     api.ts
     utils.ts
   domain-b/     # 业务模块
     components/
       /c
     page-b.tsx
     api.ts
routes.ts # 通用注册路由,引用业务域的页面

输出案例:

# 优惠券

## 页面设计

所属分包: member

页面路径命名paramsdatabackMessage

2.2 数据模型拆分和设计

数据模型用于放置业务逻辑和业务状态。

2.2.1 业务状态机/业务主体生命周期

通过上面的业务流程图,我们可以发现很多业务可以抽象为有限状态机,而前端页面无非在不同的状态下,支持不同的呈现和操作。

例如拼团详情页状态机:

图片[9]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

状态机

我们可以从上图抽象出三个状态(等待拼团、拼团过期、拼团成功、拼团取消),以及挂靠在不同状态下的不同动作。

最简单的实现是用一个状态枚举来表达它:

enum GroupStatus {
Pending = '等待',
OutDated = '过期',
Success = '成功',
Cancelled = '取消',
}

在视图层,我们可以给这些状态区分不同的呈现:

status === GroupStatus.Pending ? (




) : status === GroupStatus.OutDated || status === GroupStatus.Cancelled ? (



) : status === GroupStatus.Success ? (




) : null

如果不同状态下视图有较大差异,可以将每个状态抽离成单独的组件。

模型层对应的行为触发时,也可以对状态进行断言检查(assert, 或者转换守卫 guard):

class GroupModel {
status: GroupStatus
// ...

/**
* 取消拼团
*/
cancel() {
// 状态检查
this.assertStatus(GroupStatus.Pending, '取消拼团')
await this.repo.cancel(this.id)

// 状态流转
this.status = GroupStatus.Cancelled
}

/**
* 状态检查
*/
assertStatus(status: GroupStatus, message: string) {
if (this.status !== status) {
throw new Error(`程序异常:只能在 ${status} 状态下,才能 ${message}`)
}
}
}

当然,对于复杂的页面,状态不会像上述的那么单一, 比如:

就拿发起拼团这个例子来说:

图片[10]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

多个嵌套状态,可以由多个状态变量来控制。

多个嵌套状态,可以由多个状态变量来控制。

如上所示,一个复杂业务流程会涉及很多子状态,在设计阶段我们需要将 不同的主体的状态 识别出来。后期就围绕着这些状态进行开发。

好在我们在梳理业务流程图时,已经将相关规则梳理清楚了。识别这些状态并不难。更重要的是,这是一种业务建模思维的转变。

如果你想要深入学习和理解状态机, 或者在项目中严谨应用状态机,不妨试一下更专业的 XState。

状态机学习资料:

2.2.2 模型设计

模型(Model) 是一个核心对象,它承载了核心的业务逻辑。模型类中应该包含哪些内容呢?

图片[11]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

模型内容

事件。事件是模块解耦、实现扩展的一种重要手段。通常模型会抛出下列事件:

模型生命周期。使用依赖注入框架之后,需要关心这个问题,决定单例还是非单例?原则是如果你的模型需要在整个应用生命周期中存在,则使用单例,例如登录、会员信息这些。大部分场景都应该使用非单例,跟随页面释放而释放。

2.2.3 输出案例

以登录 SDK 为例:

业务数据:

衍生数据:这些信息都从会话信息中提取出来

行为:

事件:

模型生命周期:单例

2.3 视图设计:组件拆分和设计

组件的拆分和设计是前端设计的重头戏,合理拆分组件,可以提高代码复用率和后期的可维护性。关于如何拆分和设计组件见 组件设计指南 、以及 React 组件设计实践相关文章

图片[12]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

组件设计

案例:

NoticeBar 滚动公告栏

图片[13]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

NoticeBar 原型

属性

属性说明类型默认值

mode

通知栏模式,可选值为'closeable'/ 'link'

string

''

text

通知文本内容

string

''

color

通知文本颜色

string

#f60

background

滚动条背景

string

#fff7cc

leftIcon

左侧图标名称或图片链接

string

rightIcon

右侧图标名称或图片链接

string

delay

动画延迟时间 (s)

number

string

speed

滚动速率 (px/s)

number

string

scrollable

是否开启滚动播放,内容长度溢出时默认开启

boolean

wrap

是否开启文本换行,只在禁用滚动时生效

boolean

false

事件

事件名说明

onClick

单击通告栏时触发

onClose

关闭通告栏时触发

插槽

名称说明

children

通知栏内显示内容

leftIcon

自定义左边图标内容

rightIcon

自定义右侧图标内容

2.4 扩展点设计

如果你开发的是 SDK (即面向其他开发者),那就需要考虑扩展性问题,你的程序需要考虑各种场景的使用,比如对于 ToB 行业, 需要考虑二开、项目交付时,对你的程序进行各种粒度的定制。我在 2B or not 2B: 多业态下的前端大泥球 这篇文章也讨论过相关的背景。。

扩展点实现方式:

案例:

登录 SDK 扩展点

## 暴露的扩展点

| 名称                                              | 说明                                                           | 单例 |
| ------------------------------------------------- | -------------------------------------------------------------- | ---- |
| 'DI.login.SUPPORT_QUICK_PHONE_AUTH': boolean;     | 是否支持快捷手机号码授权, 默认 true                            |
| 'DI.login.SUPPORT_QUICK_USER_INFO_AUTH': boolean; | 是否支持快捷用户授权,默认 true                                |
| 'DI.login.QUICK_PHONE_AUTH_TEXT': string;         | 手机号码快捷登录文案, 默认为手机号码快捷登录                  |
| 'DI.login.QUICK_USER_INFO_TEXT': string;          | 快捷用户信息获取, 默认为 允许授权                              |
| 'DI.login.ROUTE_PROTOCOL_DETAIL': string;         | 服务协议详情页面, 默认为 protocolDetail(命名路由)              |
| 'DI.login.MAX_RELOGIN_COUNT': number;             | 最大重新登录次数, 默认为 10                                    |
| 'DI.login.VERIFY_TIMEOUT': number;                | 发送验证码超时时间, 默认 60 秒                                 |
| 'DI.login.LOGIN_API': string;                     | 登录接口路径, 默认 /login_v3/login_v3                         |
| 'DI.login.USER_RULE_API': string;                 | 用户服务协议列表接口路径, 默认 /wk-base/c/agreement/queryList |
| 'DI.login.REGISTER_API': string;                  | 注册用户接口路径, 默认 /cs/auth/user/register/v3              |
| 'DI.login.UPDATE_USER_API': string;               | 更新用户信息接口路径, 默认 /cs/auth/vip/user/update_user      |
| 'DI.login.SEND_PHONE_VERIFICATION_API': string;   | 发送验证码接口路径, 默认 /cs/auth/user/send_register_code     |
| 'DI.login.PLATFORM': PlatformType;                | 当前平台                                                       |
| 'DI.login.Implement': ImplementProtocol;          | 平台适配实现                                                   | yes  |
| 'DI.login.LoginRepo': LoginRepo;                  | 登录接口实现                                                   | yes  |
| 'DI.login.LoginModel': LoginModel;                | 登录模型                                                       | yes  |
| 'DI.login.RegisterModel': RegisterModel;          | 注册模型                                                       | yes  |
| 'DI.login.PhoneVerifyModel': PhoneVerifyModel;    | 手机验证码模型                                                 |

<br>
<br>

## 暴露的事件

| 标识符                                                                | 描述                         |
| --------------------------------------------------------------------- | ---------------------------- |
| 'Event.login.onRecover': SessionInfo;                                 | 从缓存中恢复                 |
| 'Event.login.onBeforeLogin': undefined;                               | 登录前                       |
| 'Event.login.onSetup': SessionInfo;                                   | 首次登录完成                 |
| 'Event.login.onLogined': SessionInfo;                                 | 已鉴权,鉴权成功             |
| 'Event.login.onLoginFailed': Error;                                   | 鉴权失败                     |
| 'Event.login.onLoginComplete': { info?: SessionInfo; error?: Error }; | 登录完成,可能成功,可能失败 |
| 'Event.login.onRefreshed': SessionInfo;                               | 会话刷新成功                 |
| 'Event.login.onRefreshFailed': Error;                                 | 会话刷新失败                 |
| 'Event.login.onLogout': never;                                        | 退出登录                     |
| 'Event.login.onUpdateInfo': SessionInfo;                              | 更新信息成功                 |
| 'Event.login.onUpdatedUser': UserInfo;                                | 更新用户信息                 |
| 'Event.login.onUpdatedUserFailed': Error;                             | 更新用户信息失败             |
| 'Event.login.onBeforeRegister': RegisterOptions;                      | 注册前                       |
| 'Event.login.onRegistered': UserInfo;                                 | 注册成功                     |
| 'Event.login.onRegisterFailed': Error;                                | 鉴权失败                     |

4. 评审

当汽车到达一定的速度时,大部分的能耗用在了克服空气阻力(比如当到达 120km/h 时,大于 60%,随着速度的提升,这个比例会越来越高)。

这个适用于软件开发,随着团队规模的扩大,我们会花费大量时间用于“达成共识”。

这包括面对面的会议、电子邮件、即时消息、编写和阅读文档等各种形式。这是因为软件开发不仅仅是编写代码,更是需要理解业务需求、解决问题、协调任务、分享知识等。

软件开发中有很多工具 和方法论,可以帮助提升“达成共识”的效率,近些年最为出名的应该是 DDD 了,比如它强调引入领域专家来指导软件的设计、划分边界上下文、统一语言等等。

我们进行软件设计,也是出于此目的。因此一定要有评审,在这个过程中进行碰撞、纠错、最后达成共识。

总结

上文给做前端业务开发怎么做设计打了个样,主要脉络是:

图片[14]-if 我是前端 Leader, 前端业务开发做不做设计?-JieYingAI捷鹰AI

脉络

这些规范和观点可能并不完全适合你们的团队。为此,你们需要找出自身所面临的问题,然后采取行动,来构建出符合你们需求的设计规范。接着,在不断的迭代过程中,逐步完善和优化这些规范。

系列文章:

考虑挪坑中,Base 珠海、远程也可以,有坑位推荐的可以私信我,感激不尽。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
来说点什么吧!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容