《从零构建前后分离的 web 项目》实战 - 欲善其事必先利其器 继续打磨前端架构

本贴最后更新于 2011 天前,其中的信息可能已经斗转星移

工欲善其事必先利其器 - 继续打磨前端架构

抱歉生病拖更了,1024 快乐

本文永久更新地址

填坑

上回还真的有同学提到了这个问题,感谢细心的你。@_noob

其实是没任何问题的,只不过看起来违背了常见的结构,像是有问题。其实是上文为了照顾初学者,怕大家因为麻烦而放弃,并没有一次性改的“看起来那么复杂”,我们来填下坑。

为了照顾没有实时跟着我连载的同学,每一章的代码单独发布在我的 Github 博客,不进行覆盖更新 [除非代码有错误进行修改],这样避免了 2019 年小明同学望着前两章和 Github 最终版本代码发呆 ( release 也不是特别友好)

我们把上文的 renderer/src 改成比较容易理解的 src/renderer ,这其实是一个编程习惯问题。

上文 renderer/src 的问题

eslint-config-alloy : 想写出更规范的代码可以参考这个规则

下面进入本章正题


axios

使用了 vue 的你,发现 Vue 居然不能发请求,于是你 Google 了下,发现可以用 Vue-Resource。
你去问别人 Vue-Resource 怎么样,他说不要用 Vue-Resource,因为 Vue-Resource 官方已经停止维护了,你应该用 Axios、或者 fetch。但是我们想拥抱 ES6 排除掉了 ES5 的 fetch(当然也有 ES6-fetch),这里我们使用 Axios!

Tips

这里呢也科普一下:什么时候依赖需要放到 dependencies、什么时候依赖需要放到 devDependencies:

devDependencies:顾名思义,仅在开发(dev)模式下如:webpack..loader、eslint、babel、打包后部署时完全用不到的、仅在开发需要 编译、检测、转换 的放在这里。
dependencies:例如:axios、chart、js-cookie、less、lodash、underscore 等运行时的库或工具类等相关依赖我们要放在这里

不过基本不用担心,官网都会提供 start 说明,但是我们要大概明白意思,不要机械般的 copy。

引入 Axios

  • 直接玩最新的

2018-09-28 截图 npmjs.com

  • 添加依赖
"dependencies": {    
    "axios": "^0.18.0"
 }

基于上一章内容,别忘了重新 npm i 下载一下

还记得我们自动生成的 vue 主页面脚本 main.js 吗?

封装 axios

我们在 src/renderer/utils 建立一个 request.js 在这个请求脚本中,对 Axios 做一些必要的封装,大概内容是用 拦截器 axios.interceptors 对请求和响应做些拦截,定义一下 API 的前缀,处理一些常见的 HTTP 状态码。

  • interceptors 文档

我尽可能的为大家写了详细的注释。

// src/renderer/utils/request.js
import axios from 'axios'

//这里一般指后端项目API的前缀,例如 /baidu/*/*/1.api  /mi/*/*/2.api
const BASE_API = ""

export function axiosIntercept(Vue, router) {
    const axiosIntercept = axios.create({
        baseURL: BASE_API
    })

    //http request 拦截器 一般用来在请求前塞一些全局的配置、或开启一些 css 加载动画
    axios.interceptors.request.use(
        (config) => {
            // 判断是否存在token,如果存在的话,则每个http header都加上token
            // if (store.getters.accessToken) {
            //     console.log(store.getters.accessToken)
            //     config.headers.Authorization = `token ${store.getters.accessToken}`;
            // }

            //todo:加载动画

            //若有需求可以处理一下 post 亦或改变post传输格式
            if (config.method === 'post') {

            };

            return config;
        }, function (err) {
            return Promise.reject(err);
        });


    //http response 拦截器 一般用来根据一些后端协议特殊返回值做一些处理,例如:权限方面、404... 或关闭一些 css 加载动画
    axiosIntercept.interceptors.response.use(function (response) {
        // todo: 暂停加载动画
        return response;
    }, function (err) {
        //捕获异常
        if (err.response) {
            switch (err.response.status) {
                case 401:
                    // do something 这里我们写完后端做好约束再完善
            }
        }
        return Promise.reject(err);
    });
    return axiosIntercept;
}


大家还记得我们用 vue-cli 生成的 vue 主页脚本 main.js 吧,这里我们需要对 Axios 和 Vue 做一个耦合。

// src/renderer/main.js
import axios from 'axios'
import { axiosIntercept } from './utils/request'

// 将Axios扩展到Vue原型链中
Vue.prototype.$http = axiosIntercept(Vue)

这样我们在写业务逻辑,直接在 Vue 的上下文中 使用 this.$http 来发送请求。既实现了拦截、又实现了状态的共享。

知其然,知其所以然

  • 这样做的意义在哪?

节省代码量,让代码更加易读

  • 为什么?

扩展到原型链,使 Axios 运行时共享 Vue 原型链的内容,减少了很多指代 Vue 的临时变量

  • 举个栗子

传统情况

import axios from 'axios'


new Vue({
  data: {
    user: ""
  },
  created: function () {
    //此时作用域在 Vue 上,缓存起来,要依赖此变量
    let _this = this;
    axios.get("/user/getUserInfo/" + userName).then(res => {
            if (res.data.code === 200) {
                //此时作用域在axios上,拿不到vue绑定的值,只能借助刚才缓存的_this上下文
                _this.data.user = res.data.user
            }
        });
    }
})

代理之后

 


new Vue({
  data: {
    user: ""
  },
  created: function () {
    // axios 成为了 vue 的原型链一部分,共享vue状态。
    
    this.$http.get("/user/getUserInfo/" + userName).then(res => {
            if (res.data.code === 200) {
                //注意,axios回调,应该尽量使用箭头函数,可以继承父类上下文,否则类似闭包,还是无法共享变量、
                // 更优雅了一些
                this.data.user = res.data.user
            }
        });
    }
})

不懂 prototype 可以翻翻我以前写的文章

proxy

先简单弄一下,为前后分离打个小铺垫

webPack

  • webPack 的别名
resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': resolve('src/renderer'),
    }
  },

为了使用起来更加优雅,可以为每个常用的目录都建立别名

resolve: {
   extensions: ['.js', '.vue', '.json'],
   alias: {
     'vue$': 'vue/dist/vue.esm.js',
     '@': resolve('src/renderer'),
     'assets': resolve('src/renderer/assets'),
     'components': resolve('src/renderer/components'),
     'container': resolve('src/renderer/container'),
     'utils': resolve('src/renderer/utils')
   }
 },

生产和开发的跨域问题

dev 是开发时启动的指令
build 是预发布时 webPack 打包的指令

假设笔者只是一个前端,通常呢,在开发调试过程当中,无法避免需要与后端的同学进行 API 的对接,那也就难免会出现跨域问题。当然传统 javaWeb 不需要跨域,(ip 域 端口 任何一个不同皆为跨域) 在 DEV 模式调试中,我们都是尽量选择前端环境规避跨域问题,而不会去额外搭建 nginx 或更改后端代码。

跨域只是针对 JavaScript 的,因为开发者认为浏览器上的脚本是不安全的。

既然我们的 vue 项目是 node 全家桶,依靠 node、webPack 编译 我们直接配置 node 的 proxyTable 作为开发的代理器,这样最简单,依次配置,团队受益。

cnode 掘金 社区 API 举例

https://cnodejs.org/api
上边 cnode 的 API 是可以随意调用的,因为后端做了处理。

看看掘金的:
https://xiaoce-timeline-api-ms.juejin.im/v1/getListByLastTime?src=web&pageNum=1
请求一下,不出意外浏览器做了跨域报警。

哦,我们适配一下 node 代理

官方例子在这:
https://vuejs-templates.github.io/webpack/proxy.html

扩展一下 proxyTable:


proxyTable: [{
      //拦截所有v1开头的xhr请求
      context: ['/v1'], 
      target: "https://xiaoce-timeline-api-ms.juejin.im",
      cookieDomainRewrite: {
        // 不用cookie
      },
      changeOrigin: true,//重点,此处本地就会虚拟一个服务替我们接受或转发请求
      secure: false
    }],

再次发送请求。

  • 愉快的拿到了数据

这样,前后分离的项目可以这样借助 swagger 测试接口,不需要骚扰任何人。实现自己的业务逻辑,简单实现一点。

代码:

// blog/index.vue
<template>
  <div class="Blog">
    <h1>{{ msg }}</h1>
     
  <div v-for="(blog,index) in blogList" v-bind:key="index">
    <h3 >
      <a :href="`https://juejin.im/book/`+blog.id" >
        <span>{{blog.title}}</span>
      </a>
      
      </h3>
  </div>
     
  </div>
</template>

<script>
export default {
  name: "Blog",
  data() {
    return {
      msg: "掘金小册一览",
      blogList: []
    };
  },
  created() {
    this.getBlog();
  },
  methods: {
    getBlog() {
      this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => {
        this.blogList = res.data.d;
      });
    }
  }
};
</script>

发布

  • 扫盲

写完代码之后部署到线上,是不会在线上 clone 代码之后 Npm run dev 的😆,那样会有太多太多的垃圾依赖,为用户带来了灾难性的网络请求,通常借助 webPack 打包之后发布到服务器的 web 服务当中。

运行

 npm run build

打包目录是之前配置的 webpack

好了,很多人直接双击 index.html 是不行的。

Tip: built files are meant to be served over an HTTP server.
Opening index.html over won't work.

需要 http 服务启动,可以扔到本地或服务器的 nginx、apache、tomcat 等容器测试,我通常使用 python 启动一个 http 服务来运行(脚本地址)、当然,自己 ide 支持 http 启动也可以。

生产中的跨域

生产当中,因为前端后端必然不同端口,避免跨域,通常使用 nginx 的正向/反向代理作为跨域的手段。(并非负载均衡,两个概念)

server {
        listen       80;
        server_name  localhost;
 
        location ^~ /v1 {
        proxy_pass              https://xiaoce-timeline-api-ms.juejin.im;#代理。
        proxy_set_header        X-Real-IP $remote_addr;#转发客户端真实IP
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        Host $http_host;
        }
 }       

简单配置一下就可以了,不多讲,前端同学了解一下就可以了,nginx 能干的事情还有很多。

API 的规范化

你是否为了找某一个业务的接口头痛
你是否还在使用全局搜索找自己的接口
你是否某一个接口不同组件重复写了多次
整理一下自己的接口吧,像上文的 router 一样整齐的划分吧。

/renderer 下建立一个 api 的文件夹

webpack.base.conf.js 添加一条 api 的别名,方便我们日后大量调用

'api': resolve('src/renderer/api')

我们建立 /renderer/api/juejin.js


import axios from 'axios'

let Api = Function()

Api.prototype = {
    getBlog(page, fn) {
        axios.get(`/v1/getListByLastTime?src=web&pageNum=${page}`).then(res => {
            // if (res.data.code === 200) {
            fn(res.data)
            // }
        }).error()
    }
}
export default new Api()


修改一下我们刚才 /blog/index.vue 的 Axios 请求:

  • 先引入 api
import juejin from "@/api/juejin";

注掉之前离散的 axios ,使用从 api 中定义过的 XHR 请求数据。

getBlog() {
      // this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => {
      //   this.blogList = res.data.d;
      // });
      juejin.getBlog("1", response => {
        this.blogList = response.d;
      });
    }

OK,很多同学不理解,本来简单的事情为什么搞复杂了?其实这样不复杂,看起来还很清晰,倘若一个大项目上千个请求,不仅重复导致代码覆盖率低,看起来还很乱,不利于团队协作。本系列文章在带领大家前后分离的基础上,会为大家提供更多有利于团队的开发方式,养成良好习惯。

本章全部代码在这里

关于我

往期文章

《从零构建前后分离 WEB 项目》 序 - 开源的意义

《从零构建前后分离 web 项目》:开篇 - 纵观 WEB 历史演变

《从零构建前后分离 web 项目》探究 - 深入聊聊前后分离架构

《从零构建前后分离 web 项目》准备 - 前端了解过关了吗?前端基础架构和技术介绍

《从零构建前后分离 web 项目》实战 - 5 分钟快速构建规范的前端项目骨架

《从零构建前后分离 web 项目》实战 - 欲善其事先利其器 继续打磨前端架构

  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    140 引用 • 441 回帖
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    261 引用 • 662 回帖
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...