Appearance
设计篇-需求功能设计与数据存储方案设计
前言
前面章节主要是介绍相关内容与环境搭建,也许你还未感受到“快乐”,本章节将通过主要介绍简历平台的需求功能,从交互层面到功能实现层面进行讲解,同时会给小伙伴们介绍项目的数据存储方案。阅读完此章节你会对项目应用有一定的了解。
数据存储方案设计
此项目应用中,最为重要的莫过于数据,试想你进行了长时间的编辑,一不小心刷新或者关闭应用,导致数据丢失,此时内心是不是很崩溃;由于不小心将应用关闭,你是否期望在重新打开应用之后,尽可能恢复你上次的数据信息?于是对于数据的存储是本应用需要考虑的关键点。
实时性数据存储-Redux
对于数据的存储,我们有诸多选择,例如 storage API
、indexDB
,或选择 Redux
,得益于 Electron 内置了 Node 模块,自然而然地,我们还可以通过读写文件的方式进行存储。
我们来看看简历平台的数据特点:字段属性多、操作频繁、数据实时响应
假设采用 localStorage、sessionStorage、jsonFile 等方式来持久性存储用户简历信息,那需要解决的一个重要问题是:数据如何进行实时响应 ?
业界内较为流行的方案是:采用 Redux,可能会有很多小伙伴会问,还有许多可选方案,如: Recoil、Dva、Mobx 等,为什么采用 Redux ?从成本、生态、热度等综合考虑,Redux 更甚一筹,并且 Redux 成名多年,生态圈完善,业内口碑不错,所以最后采用了 Redux 状态管理容器。
说到 Redux,不得不提一下 React 数据流。在 react 中,有 props 和 state,当我们想从父组件给子组件传递数据的时候,可通过 props 进行数据传递,如果我们想在组件内部自行管理状态,那可以选择使用 state。react 它是单向数据流的形式,它不存在数据向上回溯的技能,你要么就是向下分发,要么就是自己内部管理。
当两个兄弟组件需要进行数据共享,交换双方的数据,唯一的解决方案就是:提升 state,将原本兄弟组件的 state 提升到共有的父组件中管理,由父组件向下传递数据。子组件进行处理,通过回调函数回传修改 state,这样的 state 一定程度上是响应式的。
这会存在什么问题?你会发现如果你想共享数据,你得把所有需要共享的 state 集中放到所有组件顶层,然后分发给所有组件。我们肯定不会这么做,所以我们采用 Redux 进行数据状态管理。
持久性数据存储——本地文件
通过 Redux 进行数据管理,它解决了数据实时响应的问题,但也有局限,那就是当应用刷新/应用关闭之后,数据会丢失。而应用最关键的一环就是数据持久化,如何将应用数据进行持久化存储是一大问题点,为此我们有许多解决方案:
- 数据库软件的方案,如 Mysql、MongoDB
- 浏览器相关的解决方案,如 LocalStorage、indexDB
- 存入文件之中,以文件形式保存于本地
采用数据库方式相对成本较高,你需要装一系列软件,然后自己写后台,写 sql 语句;采用浏览器本地缓存可能会出现大小限制,并且只能作用于浏览器;采用文件形式进行数据持久化存储,好像是较为不错的选择,得益于 Electron 内置了 Node 模块
,我们在 Render Process 渲染进程中不仅能使用浏览器相关 API,也能使用 Node 内置的诸如 fs、child_process 等模块。
对于非注重实时性且需要进行本地存档的数据,采用文件的形式存储于本地之中,既能解决刷新丢失 redux 数据问题,又能达到“历史数据存档”的效果,并且文件的移植性也不赖,为此采用本地文件存储貌似是较好的选择。
我们做个约定
约定一:优化 Redux 的使用
虽然我们通过 Redux 进行数据管理,但不得不说,在使用 Redux 的过程中,代码编写较为繁琐,通常我们在写 redux 时,需要定义 const.js
、action.js
、reducer.js
相关文件,为了修改一个 state 值,我们需要编写这么一段代码:(👇 下面是一个例子)
javascript
// const.js
const SET_THEME = 'SET_THEME';
javascript
// action.js
export function changeStudent(data) {
return {
type: SET_THEME,
data: data,
};
}
javascript
// reducer.js
function userReducer(state, action) {
switch (action.type) {
case SET_THEME:
return Object.assign({}, state, {
theme: action.data
})
}
当我们的 state 属性特别多时,我们需要在 reducer 、action 中一直写这种相似
代码,简直就是 CV 操作,只需要 copy 一份,修改一下名称,对我个人而言,分散管理 const、action、reducer 一套流程,需要不断的跳跃思路。而且文件数量会变多。
于是我就在想,能否有个库,能让我写状态管理轻松一些,我个人是很喜欢类似像 dva 那样,在一个 model 文件中写所有的 action、state、effect、reducers 等,但本小册实战项目又不复杂,谈不上使用 dva 框架的层次,所以最后采用 rc-redux-model 第三方库进行辅助。
rc-redux-model 出发点在于解决繁琐重复的工作,store 文件分散,state 类型和赋值错误的问题,提供了一个写状态管理较为[舒服]的书写方式。
- 为了解决
store 文件分散
,参考借鉴了 dva 写状态管理的方式,一个 model 中写所有的 action、state、reducers - 为了解决
繁琐重复的工作
,提供默认的 action,用户不需要自己写修改 state 的 action,只需要调用默认提供的[model.namespace/setStore]
即可,从而将一些重复的代码从 model 文件中剔除 - 为了解决
state 类型和赋值错误
,在每次修改 state 值时,都会进行检测,检测不通过则报错提示
我们做第一个约定:对于 Redux 的操作,都以 rc-redux-model 进行打辅助。当然小伙伴们不用担心,它是无侵入式的,支持你原先 redux 的写法,更多实际操作代码,可以看 业务篇-如何写好我们的 Redux 与 jsonFile。
约定二:数据流方式
我们组件接收数据时,往往有两种选择, 一种是父组件通过 Props 传递,一种是直接从 Redux 中取。
我们先来讨论第一种:父组件通过 Props 传递
这种设计会导致:无限套娃,在定位一个问题或者修改一个数据源,可能需要层层往上进行排查,费时费力,并容易导致 Bug。
很明显这种数据流方式在我们这并不是最佳的形式。
第二种:坚守“禁止套娃,Redux 至上”的理念,所有组件直连 Redux。
相比之下,这种数据流方式貌似比层层套娃更加优雅,并在代码阅读和数据源追溯上更加直观。
当然可能也会出现父子组件 Props 传递数据的方式,阿宽会根据某个数据值的使用场景和使用频率,考虑是否将其放入 Redux 进行维护。
我们做第二个约定:此应用中,大多数情况下,组件直连 Redux,直接取数据,一些场景下会通过 Props 进行传递。
功能需求设计
功能一:数据录入的需求设计和功能实现
应用最核心的主流程莫过于:简历信息的录入 -> 数据信息的展示 -> 简历导出 PDF
数据的输入,有两种类型,一种是简单数据的录入,另一种是复杂数据的录入,我们均通过弹窗交互形式,对数据进行录入,接下来阿宽为大家仔细讲解。
简单数据的录入
如下图所示,我们先搞明白一点,什么是简单数据的输入?如姓名、电话、邮箱等字段,不需要经过任何数据校验处理。用户输入什么就展示什么。
会有小伙伴有所疑问,不需要对电话、邮箱等进行验证吗?如果输错了怎么办?我们回归问题本质,以 Word 文档制作简历,在制作过程中,我们输入个人相关信息时,内容输错了会怎么办?自己修改!所以在这里做正则校验是否是很好的交互体验?从用户角度出发,在使用过程我输入内容还要被各种校验,我很难开心起来;其次
我认为这是重要信息,不应该输错
,最大程度上我能给到的帮助是:在导出 PDF 时,给用户一个二次确定的弹窗,让用户再次确认信息。
对于简单数据的输入,我们只需要在 input 输入框中获取用户输入的内容,进行存储,在内容展示上,取数据进行填充即可。
复杂数据的录入
在这里哪些是复杂数据呢?项目经验、工作经验、在校经验等,我们很容易就能想到,这些肯定不是一个简单数据类型,他们是一个数组,数组中的每一项对应一次“非凡”的体验;
由于我们均通过弹窗交互形式,对数据进行录入,那么在交互层面上要保持统一,避免用户点击不同地方,进行不同的交互,从而存产生疑问,那么问题就成了:如何在弹窗内,进行复杂类型的数据录入。
想必大家脑海中第一时间想到的交互效果应该如下图所示:👇
这种交互效果固然能实现功能,但有一个体验问题:我们的弹窗宽高是固定的,在有效的区域之内进行多数据的增改删操作,需要鼠标滚轮不停的工作(可能我追加一条数据需要鼠标滚到弹窗底进行编辑)于是我在市面上进行调研,最终确定以下面这种交互形式,进行复杂数据的录入。
上图是最终实现的效果,我们将其看成是一个类似“文档笔记”的小模块,可以进行新增、编辑、删除等操作,细品细品,这种交互效果的数据录入是否比第一种更友好呢?
这种效果至关重要的莫过于对整个流程的把控,下面是拆解过后的操作流程图:
- 添加条目
- 删除条目
- 切换条目
以上是复杂数据的输入,只有在点击保存条目之后,该数据才能写入 Redux 中,之后进行内容的展示。下面是流程图对应的最终效果图
功能二:简历数据如何进行展示
上边讲到 React 数据流,如果你理解了,那么理解 Redux 如何实现数据实时响应的工作流就简单了。实现原理是在根组件,通过调用 react-redux
提供的 Provider 组件,将整颗 Store 树放在顶层。
以数据驱动方式
进行页面更新,我们所有组件进行监听 Store 中的数据,当监听的值发生改变,我们的视图也会更着改变,所以将简历数据存于 Redux,模版组件通过监听简历数据,从而达到视图的更新,进而展示我们的数据。
功能三:简历导出成 PDF
主流程三大环节:数据录入、数据展示、简历导出,我们已经完成 2/3,距离最终的完整实现仅差一步之遥,如何将我们网页中写好的简历模版导出成 PDF,决定着简历平台应用的生死,毕竟不能为我们所用,那么这个平台将没有任何的使用价值。
具体实现将在下面的实战过程中,带着大家一步步前行,小伙伴们也可以在评论区中提出实现方案 👏
功能四:持久化数据存储
上边已经提到对于持久性数据该如何存储,在此项目中,需要给用户提供数据存储路径的配置项。
思考一下,什么样的数据是需要进行持久化存储?PDF 文件?我想最重要的是数据,通过创建本地文件,将用户信息以文件形式存于本地系统中。具体实现将在下面的实战过程中,带着大家一步步前行~
功能五:主题换肤模块
主流程走通之后,我们的应用程序可以说处于可用状态,但功能相对单一,我们可以添加一些新颖的功能点,丰富整个应用。
对于这块感兴趣的小伙伴,可以前往👉 业务篇-首页主题换肤功能实现进行阅读,这里也留个思考,小伙伴们可以想想如何实现此功能呢?欢迎在评论区留言 👏
功能六:简历模版模块
我期望,将来会有许多模版,而不是大家都用同一份模版去制作简历,于是基于简历制作之上,我们开辟一个模块:姑且称为简历模版列表。
由于我们都窗口宽度有限,在预览效果上极度不佳,通过支持侧边栏的收起,让交互效果更为友好。
总结
本章节主要以多角度去切入思考整个应用的需求功能点,从视觉效果、交互效果上精心打磨,并且以主要核心功能为主,在实现核心主流程之后的进一步功能丰富,以及在功能实现上的一些思考,尽可能让小伙伴们能感同身受。
该章节主要还介绍了 React 数据流以及为什么要使用 Redux,使用 Redux 导致我们的开发体验上有什么不舒适的地方?采用什么方式进行解决。通过对比实时性数据与非实时性数据,采用什么存储方式会相对优雅。最后与小伙伴们做两个约定,再总结一下:
- react 它是单向数据流的形式,当兄弟组件进行数据通信时,只能提升 state ,由共有的父组件进行管理 state,Props 方式下发状态,通过回调修改 state 从而达到实现数据响应
- 由于 redux 流水线过于繁琐,故而通过 rc-redux-model 进行简化,它允许我们只在一个 model 文件中写所有的东西,我们可忽略写 action、reducer,同时不用记许多的 action,只需要记住官方提供的
setStore
就能修改任意的 state - 得益于 Electron 内置了 Node 模块,我们还可以通过读写文件的方式,对非实时性且重要的数据进行本地存储,从而达到数据持久化保存
- 约定一:对于 Redux 的操作,都以 rc-redux-model 进行打辅助
- 约定二::此应用中,大多数情况下,组件直连 Redux,直接取数据
如果对本章节存在疑问,欢迎在评论区留言。