在这篇文章中,我将讨论如何在 Vue 应用中使用 SOLID 原则。
SOLID 包括以下观点:
接下来我们看看如何在 Vue 实战中避免这些原则,我们从一个 TODO LIST 项目中去体会这些观点。
用 vue cli 初始化一个 Vue 项目。
vue create todo-app我们用 vue2.6.10 + typescript3.4.3 构建我们的应用。如果你对 typescript 不熟悉,可以查看typescriptlang。
安装完成之后,将目录中的组件都删除掉,然后我们的 src 目录如下图所示:

App.vue:

views/Home.vue:

准备工作就绪,接下来正式进入正题。
首先我们将 views/Home.vue 组件改成如下代码,通过API获取一个任务列表并展示出来:

基本上所有的功能我们都在 views/Home.vue 中完成了。单一职责原则规定的是一个类应该有且仅有一个引起变化的原因,否则应该被拆分。接下来我们看看有多少个理由可以改变我们的组件:
如上面所述,我们轻易地找到3条理由去修改这个 views/Home.vue。 当这个应用的功能越来越丰富时,真正的问题将开始:代码越来越多直到我们都不知道自己写了什么(这就意味着该组件失去了控制)。
通过将上述可能存在的变动提取到不同的函数、类或者组件中,我们就可以避免违反单一职责原则。接下来进行重构:
第一步,将我们的请求函数放到新的API文件中(新建 src\api\api.ts):

第二步,我们将 header 组件提取成一个新的函数组件 components/Header.vue:

最后一步,我们提取 TODO LIST 组件 components/TodoList.vue:

然后修改 views/Home.vue:

重构之后,可以看到我们的 Home 组件更加的单一和可读。
让我们把目光聚焦在 components/TodoList.vue。这个组件展示了一系列待做任务卡片。如果需求变更,我们要改变这些卡片或者将它们用表格的形式展示,会发生什么?答案是我们必须修改这个组件(它看起来非常死板)。开闭原则规定“当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。”现在我们来重构 TodoList 组件,达到避免这种窘境!
我们通过插槽来改变这个情况:

然后将我们的卡片独立成一个组件 components/TodoCard.vue:

最后更新我们的 Home 组件:

这样一来,我们就能够轻易地通过其它组件来修改任务显示。
这节聚焦在 API 部分。首先我们将 api.ts 重新命名并将它放到一个独立的文件 api/BaseApi.ts:

如你所见, BaseApi 类有一个 fetch 方法需要一个参数 url。因为一些原因,我们决定使用 axios 库。
yarn add axios然后创建一个 BaseApi 的子类 AxiosApi( api/AxiosApi.ts):

如果我们要将 views/Home.vue 的 BaseApi(父类)类用 AxiosApi(子类)替换掉:

这样做已经破坏了我们的应用,因为我们没有遵从里氏原则:“子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。”
如你所见,我们将对象作为参数传递给 AxiosApi 类的 fetch()方法,但是 BaseApi 类改为使用字符串。在这种情况下,我们不能毫不费力地用父类替换子类。我们只需要做下面的改变即可:

现在我们就可以随意无缝切换 BaseApi 或 AxiosApi。更深入一些,我们可以创建一个 Api 类来控制子类。

我们将任务可视为卡片。让我们在 components/TodoRaw.vue 添加一个列表:

然后用列表替换掉卡片:

如你所见,我们在 TodoCard.vue 和 TodoRow.vue 中将整个 todo 对象作为 prop 发送,但是我们仅使用此对象的一部分。 userId 在两个组件中都没用到, id 仅在 TodoCard.vue 中使用。我们这就违反了接口隔离原则“组件不应该依赖没有使用到的属性和方法”。
有两种方式可以解决这个问题:
使用函数式组件来重构可视化组件, views/Home.vue:

components/TodoCard.vue:

components/TodoRow.vue:

依赖倒置原则的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
高层模块和低层模块是什么意思?
我们在 types 中为 Api 类创建一个新的接口:

接着更新我们所有的 api 类和 views/Home.vue: 更新 api/api.ts:

api/AxiosApi.ts:

api/BaseApi.ts:

views/Home.vue:

现在,低级类 Api 和高级 views/Home.vue 类依赖同一个接口 IApi。
原始依赖关系的方向已经颠倒了:低级 Api 现在依赖于高级抽象。