本篇内容在国外的一篇博客的基础上修改的,基于Vue3 + JavaScript
实现,使用腾讯前端AlloyTeam
的代码规范对演示代码进行校验,Git
提交规范使用开源工具husky
来验证。本文涉及的代码均以上传到GitHub
和Gitee
中。文章中有不正确的地方,请大家不吝赐教,批评指正。
GitHub
访问地址Gitee
访问地址
1. SOLID原则
SOLID
原则是面向对象编程和面向对象设计的5大基本原则,分别代表的是:
S
:SRP
,单一职责原则O
:OCP
,开放封闭原则L
:LSP
,里氏替换原则I
:ISP
,接口隔离原则D
:DIP
,接口隔离原则
下面对5大基本原则做一个简单的描述,如果之前有了解,可以跳转到第2章,了解如何在Vue3
中一步步实践这5大原则。
1.1 单一职责原则
单一职责原则规定:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中,即定义有且仅有一个原因使类变更。
反例:A
类负责两个不同的职责:职责B
和职责C
。当由于职责B
需求发生了改变而修改A
类时,有可能会导致职责C
不能正常运行了,这就说明职责B
和职责C
被耦合在A
类。
1.2 开放封闭原则
开放封闭原则规定:一个实体应该对扩展开放,对修改是封闭的,也就说说,如果想对一个已完成或者已运行的实体添加新的功能,那么应该通过扩展的方式来实现对实体的变化,而不应该通过对现有的实体进行修改来实现变化。
1.3 里氏替换原则
里氏替换原则规定:一个对象出现的地方,都可以使用子类来替换,并且不会导致程序的错误,它的意义在于:子类可以扩展父类的功能,但是不能修改父类原有的功能。
1.4 接口隔离原则
接口隔离原则规定:客户端不应该被迫实现一些他们不需要的接口,而应该把胖接口中的方法分组,然后使用多个接口来替代它,确保每个接口服务于一个模块。
1.5 依赖倒置原则
依赖倒置原则规定:抽象不应该依赖于细节,而细节应该依赖于抽象,也就说,应该针对抽象(接口)编程,而不是针对实现细节编程。
依赖倒置原则和开放封闭原则的关系:开放封闭原则是面向对象设计原则的基础,而依赖倒置原则是实现开放封闭原则的一个基础。
2 初始Vue3项目
这里使用vite
来初始化Vue3
项目,这里不介绍vite
是什么,可移步vite官网查看。
$ npm create @vitejs/app --template vue
2.1 配置AlloyTeam代码规范检测
这里以VSCode
编辑器为例,介绍如何在Vue3
项目中配置AlloyTeam
代码规范检测。
VSCode
需要安装的插件:ESLint
、Prettier - Code formatter
、Vetur
、Auto Close Tag
(可选)、Auto Rename Tag
(可选)和Debugger for Chrome
(可选)VSCode
配置:使用组合键Ctrl+Shift+P
打开Command Palette
,选择Preference: Open Settings (JSON)
,在文件中配置一下内容:
{
"eslint.validate": ["javascript", "vue"],
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
},
"files.eol": "\n",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
}
- 项目安装依赖:
package.json
文件内容如下,可直接替换devDependencies
中的内容,然后使用npm install
安装依赖
{
"name": "vite-project",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"vue": "^3.0.5"
},
"devDependencies": {
"@types/eslint": "^7.2.6",
"@vitejs/plugin-vue": "^1.1.4",
"@vue/compiler-sfc": "^3.0.5",
"babel-eslint": "^10.1.0",
"eslint": "^7.20.0",
"eslint-config-alloy": "^3.10.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-vue": "^7.6.0",
"prettier": "^2.2.1",
"vite": "^2.0.1",
"vue-eslint-parser": "^7.5.0"
}
}
- 在
Vue3
项目根目录下增加两个文件:.prettierrc.js
、.eslintrc.js
.eslintrc.js
module.exports = {
extends: ['alloy', 'alloy/vue'],
env: {},
globals: {},
rules: {
'vue/component-tags-order': [
'error',
{
order: [['template', 'script'], 'style'],
},
],
},
};
.prettierrc.js
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
// 格式化嵌入的内容
embeddedLanguageFormatting: 'auto',
};
- 通过以上配置,在保存文件时,会自动检测相关代码,并按照约定的格式格则自动调整。
2.2 配置Git提交规范验证工具:husky
- 安装依赖项:
npm i -D husky
- 修改
package.json
:在devDependencies
后添加husky
,具体内容如下:
{
"name": "vite-project",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"vue": "^3.0.5"
},
"devDependencies": {
"@types/eslint": "^7.2.6",
"@vitejs/plugin-vue": "^1.1.4",
"@vue/compiler-sfc": "^3.0.5",
"babel-eslint": "^10.1.0",
"eslint": "^7.20.0",
"eslint-config-alloy": "^3.10.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-vue": "^7.6.0",
"husky": "^5.1.1",
"prettier": "^2.2.1",
"vite": "^2.0.1",
"vue-eslint-parser": "^7.5.0"
},
"husky": {
"hooks": {
"commit-msg": "node script/verify-commit.js"
}
}
}
- 在项目根目录下创建
script
文件夹,并在其中创建verify-commit.js
文件
const msgPath = process.env.HUSKY_GIT_PARAMS;
const msg = require('fs').readFileSync(msgPath, 'utf-8').trim();
// 提前定义好 commit message 的格式,如果不符合格式就退出程序。
const commitRE = /^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,50}/;
if (!commitRE.test(msg)) {
console.error(`
不合法的 commit 消息格式。
请查看 git commit 提交规范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md
`);
process.exit(1);
}
2.3 实现代码事件列表
- 删除
components
文件夹 - 增加
views
文件夹,并在其中创建Home.vue
文件
<script>
import { onMounted, ref } from 'vue';
export default {
name: 'Home',
setup() {
let todos = ref([]);
const fetchTodos = () => {
fetch('https://jsonplaceholder.typicode.com/todos/')
.then((response) => response.json())
.then((response) => {
todos.value = response;
});
};
onMounted(() => {
fetchTodos();
});
return {
todos,
};
},
};
</script>
<template>
<div>
<header class="header">
<nav class="header-nav"></nav>
<div class="container">
<h1>我的代办事项</h1>
</div>
</header>
<main>
<div class="container">
<div class="todo-list">
<div v-for="{ id, title, completed } in todos" :key="id" class="todo-list__task">
<span :class="{ 'todo-list__task--completed': completed }">
{{ title }}
</span>
</div>
</div>
</div>
</main>
</div>
</template>
<style lang="scss">
.header {
width: 100%;
&-nav {
background: teal;
width: 100%;
height: 56px;
}
}
.container {
padding: 1.5rem;
}
.todo-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
&__task {
width: 24%;
padding: 1.5rem;
margin: 0.5%;
text-align: left;
color: #4169e1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
&:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
&--completed {
color: #2e8b57;
text-decoration: line-through;
}
}
}
</style>
- 修改
App.vue
<script>
export default {};
</script>
<template>
<div id="app">
<router-view />
</div>
</template>
- 在
src
目录下新建router.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
const routerHistory = createWebHistory();
const router = createRouter({
history: routerHistory,
routes: [
{
path: '/',
name: 'home',
component: Home,
},
],
});
export default router;
- 修改
main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
- 实现效果:
$ npm run dev
3 SRP改造
在上面的示例中,前面页面的工作都是在Home.vue
这个文件中实现,包括:header bar
、nav bar
、todo list
等。如果将SRP
原则应用于当前现有的项目中:每个组件都应该只有一个改变的理由。很明显,当前的Home.vue
组件不能满足SRP
理由,因为它有多个可以改变的理由:
- 使用
axios
替换fetch
- 根据需求添加其他元素,例如:
sidebar
、img
- 改变列表的展示形式
- ...
当整个系统功能越来越多,系统越来越庞大的时候,我们可能对Home.vue
文件失去控制,因此,尝试使用SRP
原则对其进行改造。
3.1 抽取访问RESTful接口的内容
在项目根目录创建services
文件夹,然后在该文件夹中创建api.js
文件,内容如下:
export class Api {
constructor(url) {
this.baseUrl = 'https://jsonplaceholder.typicode.com/';
this.url = url;
}
async fetch() {
const response = await fetch(`${this.baseUrl}``${this.url}`);
return await response.json();
}
}
3.2 拆分页面组件
将header
从Home.vue
中拆分出来,在components
文件夹中创建MyHeader.vue
文件:
<script>
export default {
name: 'MyHeader',
props: ['listName'],
};
</script>
<template>
<header class="header">
<nav class="header-nav" />
<div class="container">
<h1>{{$props.listName}}</h1>
</div>
</header>
</template>
<style lang="scss">
.header {
width: 100%;
&-nav {
background: teal;
width: 100%;
height: 56px;
}
}
</style>
将todo-list
也从Home.vue
中拆分出来,在components
文件夹中创建MyTodoList.vue
文件:
<script>
export default {
name: 'MyTodoList',
props: ['todos']
}
</script>
<template>
<div class="container">
<div class="todo-list">
<div v-for="{ id, title, completed } in $props.todos" :key="id" class="todo-list__task">
<span :class="{ 'todo-list__task--completed': completed }">
{{ title }}
</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.todo-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
&__task {
width: 24%;
padding: 1.5rem;
margin: 0.5%;
text-align: left;
color: #4169e1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
&:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
&--completed {
color: #2e8b57;
text-decoration: line-through;
}
}
}
</style>
3.3 修改Home.vue
修改Home.vue
文件,引入刚刚拆分的组件和API
接口:
<script>
import { onMounted, ref } from 'vue';
import MyHeader from '../components/MyHeader.vue';
import MyTodoList from '../components/MyTodoList.vue';
import { Api } from '../services/api'
export default {
name: 'Home',
components: {
MyHeader,
MyTodoList
},
setup() {
let todos = ref([]);
const fetchTodos = async () => {
const api = new Api('todos')
return await api.fetch()
};
onMounted(async () => {
todos.value = await fetchTodos();
});
return {
todos,
};
},
};
</script>
<template>
<div>
<MyHeader :listName="代办事项列表"/>
<main>
<MyTodoList :todos="todos"/>
</main>
</div>
</template>
<style lang="scss">
.container {
padding: 1.5rem;
}
</style>
通过以上步骤,使演示示例基本满足SRP
原则的规定,即:
api.js
文件对应着RESTful
接口的调用MyHeader.vue
文件对应着header
MyTodoList.vue
文件对应着todo-list
代办事项列表
4 OCP改造
OCP
原则规定:对扩展开放,对修改关闭。会看下当前示例,现在展示具体的代办事项使用Card
的形式来展示的,那么如果想用Row
的方式来展示,就要修改现有的MyTodoList.vue
文件,这显然与不符合OCP
原则。具体的改造方法如下:
4.1 拆分Card表示形式
将MyTodoList.vue
文件中涉及的Card
表现形式的相关代码抽离出来,添加到具体的文件中。在components
文件夹中新建文件MyTodoCard.vue
文件,内容如下:
<script>
export default {
name: 'MyTodoCard',
props: ['todo']
}
</script>
<template>
<div class="todo-list__task">
<span :class="{ 'todo-list__task--completed': $props.todo.completed }">
{{ $props.todo.title }}
</span>
</div>
</template>
<style lang="scss" scoped>
.todo-list {
&__task {
width: 24%;
padding: 1.5rem;
margin: 0.5%;
text-align: left;
color: #4169e1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
&:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
0 10px 10px rgba(0, 0, 0, 0.22);
}
&--completed {
color: #2e8b57;
text-decoration: line-through;
}
}
}
</style>
4.2 修改MyTodoList
修改MyTodoList.vue
文件,主要是使用slot
插槽的形式替换之前具体的Card
表现形式,并移除多余的CSS
代码:
<script>
export default {
name: 'MyTodoList'
}
</script>
<template>
<div class="container">
<div class="todo-list">
<slot/>
</div>
</div>
</template>
<style lang="scss">
.todo-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
}
</style>
4.3 修改Home.vue
修改Home.vue
文件,引入MyTodoCar.vue
文件,MyTodoList
元素中引入MyTodoCar
,为代办事项设置对应的表示形式:
<script>
import { onMounted, ref } from 'vue';
import MyHeader from '../components/MyHeader.vue';
import MyTodoList from '../components/MyTodoList.vue';
import MyTodoCard from '../components/MyTodoCard.vue';
import { AxiosApi } from '../services/AxiosApi';
export default {
name: 'Home',
components: {
MyHeader,
MyTodoList,
MyTodoCard
},
setup() {
let todos = ref([]);
const fetchTodos = async () => {
const api = new AxiosApi()
return await api.fetch('todos')
};
onMounted(async () => {
todos.value = await fetchTodos();
});
return {
todos,
};
},
};
</script>
<template>
<div>
<MyHeader listName="代办事项列表"/>
<main>
<MyTodoList>
<!-- <MyTodoCard v-for="todo in todos" :key="todo.id" :todo="todo"/> -->
<MyTodoRow v-for="todo in todos" :key="todo.id" :todo="todo"/>
</MyTodoList>
</main>
</div>
</template>
<style lang="scss">
.container {
padding: 1.5rem;
}
</style>
4.4 新增Row.vue文件
增加一种新的代办事项的展示形式,来验证经过上述修改是否能满足OCP
原则的规定。
- 在
components
文件夹中创建MyTodoRow.vue
文件
<script>
export default {
name: 'MyTodoRow',
props: ['todo']
}
</script>
<template>
<div class="todo-list__row">
<span>{{$props.todo.id}}.</span>
<span :class="{ 'todo-list__row--completed': $props.todo.completed }">
{{$props.todo.title}}
</span>
</div>
</template>
<style lang="scss">
.todo-list {
&__row {
width: 100%;
text-align: left;
color: #4169e1;
&--completed {
text-decoration: line-through;
color: #2e8b57;
}
}
}
</style>
- 在
Home.vue
文件中使用MyTodoRow
替换MyTodoCard
<script>
import { onMounted, ref } from 'vue';
import MyHeader from '../components/MyHeader.vue';
import MyTodoList from '../components/MyTodoList.vue';
// import MyTodoCard from '../components/MyTodoCard.vue';
import MyTodoRow from '../components/MyTodoRow.vue';
import { Api } from '../services/api';
export default {
name: 'Home',
components: {
MyHeader,
MyTodoList,
// MyTodoCard,
MyTodoRow
},
setup() {
let todos = ref([]);
const fetchTodos = async () => {
const api = new Api('todos')
return await api.fetch()
};
onMounted(async () => {
todos.value = await fetchTodos();
});
return {
todos,
};
},
};
</script>
<template>
<div>
<MyHeader listName="代办事项列表"/>
<main>
<MyTodoList>
<!-- <MyTodoCard v-for="todo in todos" :key="todo.id" :todo="todo"/> -->
<MyTodoRow v-for="todo in todos" :key="todo.id" :todo="todo"/>
</MyTodoList>
</main>
</div>
</template>
<style lang="scss">
.container {
padding: 1.5rem;
}
</style>
可以看到,修改代办事项的表现形式,就需要增加一个.vue
文件来实现具体的表现形式,然后在Home.vue
文件中做简单的修改,并且MyTodoList.vue
文件保持不变。到这里,基本实现OCP
原则。
5 LSP改造
LSP
原则规定:当对父类进行扩展时,能够使用子类的对象来替换之前使用的父类的对象,从而不会破坏客户机的代码。具体到当前演示示例该如何实现呢?考虑这种情况:现在基于axios
来从服务器获取todo-list
,并且不修改Home.vue
的文件,这种情况该怎么实现呢?
5.1 修改API传参方式
将传递URL
具体地址的入口由new Api()
转为fetch
函数,即:api.js
的内容:
export class Api {
constructor() {
this.baseUrl = 'https://jsonplaceholder.typicode.com/';
}
async fetch(url) {
const response = await fetch(`${this.baseUrl}${url}`);
return await response.json();
}
}
Home.vue
也做相应的修改,主要是用来适配api.js
的修改,修改后的setup
的内容为:
setup() {
let todos = ref([]);
const fetchTodos = async () => {
const api = new Api('todos');
return await api.fetch();
};
onMounted(async () => {
todos.value = await fetchTodos();
});
return {
todos,
};
},
5.2 添加基类BaseApi
在services
文件夹中创建文件BaseApi.js
作为API
的基类,主要存储URL
中的服务器信息,并使用fetch
的方式来获取服务器返回数据,内容如下:
export class BaseApi {
constructor() {
this.baseUrl = 'https://jsonplaceholder.typicode.com/';
}
async fetch(url) {
const response = await fetch(`${this.baseUrl}${url}`);
return await response.json();
}
}
5.3 扩展基类使用axios查询RESTful接口
在services
文件夹中创建文件AxiosApi.js
,使用axios
来查询RESTful
接口。
import axios from 'axios';
import { BaseApi } from './BaseApi.js';
export class AxiosApi extends BaseApi {
async fetch(url) {
const { data } = await axios.get(`${this.baseUrl}${url}`);
return data;
}
}
5.4 修改api.js
接下来,需要修改api.js
文件,用来调用BaseApi.js
或者AxiosApi.js
import { BaseApi } from './BaseApi';
export class Api {
constructor() {
this.apiProvider = new BaseApi();
}
async fetch(url) {
return await this.apiProvider.fetch(url);
}
}
如果想替换成axios
的方式来获取服务器数据,只需要将引入import { AxiosApi } from './AxiosApi'
,并修改apiProvider
的实例化方式即可。这样我们就完成参照LSP
规则对演示示例的改造。
6 ISP改造
ISP
规则应用在组件情形时,表示:不应该强迫组件依赖于它们不使用的属性或者方法。来回顾之前的代码,主要观察对于MyTodoRow
和MyTodoCard
的传参方式,是把整个todo
对象传递给了这两个组件,但是对象中的userId
属性在这两个组件中都没有用到,并且MyTodoCard
没有使用到id
这个属性。可见,当前的演示示例违反了ISP
原则。
解决这个问题的两种方法:
- 将
todo
实例切分成更小的实例 - 向组件传递它需要的数据
这里采用第2种解决方法。分别修改MyTodoCard.vue
和MyTodoRow.vue
文件,将需要使用的数据属性添加到props
中,然后修改Home.vue
文件,修改向其传递数据的方式。
MyTodoCard.vue
<script>
export default {
name: 'MyTodoRow',
props: ['id', 'completed', 'title'],
};
</script>
<template>
<div class="todo-list__row">
<span>{{ $props.id }}.</span>
<span :class="{ 'todo-list__row--completed': $props.completed }">
{{ $props.title }}
</span>
</div>
</template>
<style lang="scss">
.todo-list {
&__row {
width: 100%;
text-align: left;
color: #4169e1;
&--completed {
text-decoration: line-through;
color: #2e8b57;
}
}
}
</style>
MyTodoCard.vue
<script>
export default {
name: 'MyTodoCard',
props: ['completed', 'title'],
};
</script>
<template>
<div class="todo-list__task">
<span :class="{ 'todo-list__task--completed': $props.completed }">
{{ $props.title }}
</span>
</div>
</template>
<style lang="scss" scoped>
.todo-list {
&__task {
width: 24%;
padding: 1.5rem;
margin: 0.5%;
text-align: left;
color: #4169e1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
&:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
&--completed {
color: #2e8b57;
text-decoration: line-through;
}
}
}
</style>
Home.vue
<script>
import { onMounted, ref } from 'vue';
import MyHeader from '../components/MyHeader.vue';
import MyTodoList from '../components/MyTodoList.vue';
import MyTodoCard from '../components/MyTodoCard.vue';
// import MyTodoRow from '../components/MyTodoRow.vue';
import { Api } from '../services/api.js';
export default {
name: 'Home',
components: {
MyHeader,
MyTodoList,
MyTodoCard,
// MyTodoRow,
},
setup() {
let todos = ref([]);
const fetchTodos = async () => {
const api = new Api();
return await api.fetch('todos');
};
onMounted(async () => {
todos.value = await fetchTodos();
});
return {
todos,
};
},
};
</script>
<template>
<div>
<MyHeader listName="代办事项列表" />
<main>
<MyTodoList>
<MyTodoCard v-for="todo in todos" :key="todo.id" : :completed="todo.completed" />
<!-- <MyTodoRow
v-for="todo in todos"
:id="todo.id"
:key="todo.id"
:
:completed="todo.completed"
/> -->
</MyTodoList>
</main>
</div>
</template>
<style lang="scss">
.container {
padding: 1.5rem;
}
</style>
7 DIP改造
DIP
规则规定:高级类(组件)不能依赖低级类(组件),他们两个都应该依赖抽象,抽象不能依赖细节,而是细节应该依赖抽象。其中:
- 低级类用来实现基础的操作,例如:与
API
对接; - 高级类包含了复杂的业务逻辑,这些逻辑用来指导低级类进行某些操作。
参照DSP
规则,修改api
相关的类文件。
作者原文中使用TypeScript
实现的DIP
规则。JavaScript
中没有接口这个功能,但是可以使用模块封装来实现类似的行为,例如:导出类或者方法。这里使用导出类的方法来实现DIP
规则。
- 修改
BaseApi.js
文件,实现类似接口的功能
export class BaseApi {
constructor() {
this.baseUrl = 'https://jsonplaceholder.typicode.com/';
}
async fetch(url) {
throw new Error('没有具体的实现方法');
}
}
- 在
services
文件夹创建FetchApi.js
文件,继承BaseApi.js
方法,使用fetch
方法来实现获取数据
import { BaseApi } from './BaseApi.js';
export class FetchApi extends BaseApi {
async fetch(url) {
const response = await fetch(`${this.baseUrl}${url}`);
return await response.json();
}
}
- 修改
api.js
文件,在这个文件中,可以切换AxiosApi.js
和FetchApi.js
文件的引用,调用不同的方式来获取服务器数据
import { AxiosApi } from './AxiosApi';
export class Api {
constructor() {
this.apiProvider = new AxiosApi();
}
async fetch(url) {
return await this.apiProvider.fetch(url);
}
}
8 参考
- 设计模式之SOLID原则
- 前端工程(二):统一规范
- How to avoid SOLID principles violations in Vue. JS application
- Decoupling code in JavaScript with the Dependency Inversion Principle
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论