简单了解前后端分离与Vue.js的基本实践(上)

Posted Python之美

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单了解前后端分离与Vue.js的基本实践(上)相关的知识,希望对你有一定的参考价值。

作者: 杜志鹏

原文链接:https://zhuanlan.zhihu.com/p/30995429

已授权发布

概述

在本期中,我们将优化之前的实践项目。通过优化工作的实践,我们会对时下流行的前、后端分离技术有一个初步的认识,并能使用时下流行的前端框架Vue.js的一些基本功能。

为什么要前、后端分离?

我们先了解清楚为什么,再来考虑怎么做。

让我们回顾一下在之前从第五期到第八期的实践,我们的网站程序是拥有前端展现的,并且是靠两个静态文件 index.html 和 return.html 做到这点。我们当时的作业方式是,先编写好这两个前端文件,并灌入一些假数据调试好样式,然后再到编写后端程序时,去改写这两个文件,去除其中的假数据,并替换成flask框架所采用的模板引擎(Jinja2)对应的标记语言代码。

实际上,上面的这个作业方式现在十分常见。如果你现在在一家互联网公司工作,很有可能这家公司的前端工程师和后端工程师也是这么配合干活的,即使后端采用的是Java或者别的程序语言。总是UI做好效果图后,交给前端,前端编写好静态页(.html文件),打包静态页、样式表(CSS)、脚本(JS)还有图片等资源给到后端开发,后端开发再根据需求改写好静态文件和编写后台逻辑。

那么这样做问题在哪里呢?问题到处都是!

首先,前端编写静态页时,为了样式调试的便利,往往需要自己往页面上写一些假数据。而不同的页面一般需要大量的测试数据编写工作,实在是浪费了前端工程师宝贵的时间。可以想象到,最佳的方式肯定是调用真实数据,那么为什么不这样做呢?因为前端要这样做,需要后台给接口传出数据,就像豆瓣的开放接口这样。但后端这样做,等同于改变了之前的程序架构,朝前、后端分离的方向去做了。

再有,一个更要命的问题就是网站界面改版。比如逢年过节各大网站都喜欢搞些节日元素,或者经过一段时间也喜欢改个版让用户感受下新鲜气息。但这种改版基本都是纯前端上的变化,功能逻辑层面几乎都没什么改变,我们之前实践的体会,应该很容易的让我们联想到,一旦是这样的需求,我们需要重制静态文件,然后在后端(Python程序)不作任何修改的情况下,再改写前端页面。既然后端功能逻辑没什么变化,为什么不让前端去承担全部工作呢?很简单,因为实际工作中,前端工程师和后端工程师是不同的语言栈,就拿Flask来说,学会jinja2的 {{ xxx }} 这种语法根本不是前端工程师该做的,更别说通常前端工程师写代码用的编辑器和开发环境,和后端用的根本就不同,所以,结果就是前端工程师不能把后端改写过的静态文件(很可能已经不是.html文件了,比如php里,一般情况下改写后就成了PHP文件)拿来直接修改;后端工程师也不会直接去改已和后端程序耦合的模板文件。

偶尔,因为页面变化实在太过简单,后端可以直接去改模板文件,但这是非常极端的情况。

综上,以前的前端和后端过分耦合的作业方式,容易造成程序可用性不高(拿假数据做的总归不真实),以及开发效率低下(前端要花时间制造调试数据;没后端大哥什么事的,非让人插一脚)。

虽然还有很多不好的地方,这里不多赘述。只上面这样的原因,足够让我们看到传统作业方式的缺陷。

现在我们再看看自己程序面临的问题,来进一步加深对前、后端分离的必要性的认识。

再问:这次为什么要前、后端分离?

豆瓣的电影查询接口,有下图中描述的限制:

简单了解前、后端分离与Vue.js的基本实践(上)

怎么理解这个限制呢?因为我们的程序逻辑是,用户前台提交查询请求到我们的服务器(网站程序)上,我们的服务器(网站程序)再去查豆瓣,豆瓣返回结果给我们后,我们再返回给用户。即每次查询,是由我们的服务器最终提交给豆瓣的,而服务器的ip是固定的一个(单ip),也就是说,因为最终的查询是我们服务器作出的,所以我们每小时只有150次查询机会,用完就没了。如果每个用户随便查询几次(这太正常了),我们的网站程序可能一小时还不能服务多于10个人,这实在是太坑爹了。

我们马上能想到的一个问题就是,能不能让用户直接去查询豆瓣,不要通过我们的服务器去查呢?

答案是当然可以,如果把前、后端分离了,就能非常容易的实现这点。这样,看来我们这次还真是要马上学习下怎么弄,不然之前做的程序也太弱鸡了(虽然已经很弱鸡了)。

初识Vue

我们先弄清楚Vue这个英文单词怎么读。Vue不读V-U-E(逐字母发音),而是与view读音接近,view这个词学Python写过flask的早已不陌生了。

在前端的领域的发展史中,前后涌现了不少新式技术,比如

简单了解前、后端分离与Vue.js的基本实践(上)

应该多少有点了解,至少听过的jQuery。jQuery是一个非常经典的前端工具库,事实上我们这次的前、后端分离光靠jQuery就行,完全用不着Vue,但为什么要让大家接触Vue呢?因为Vue包含了很多近代前端开发领域的先进编程思想,而且入门门槛不高的同时也兼有深度,并且愈来愈多的互联网公司开始采用这门技术,所以相关的资源非常丰富。更为重要的是,Vue是我们国人同胞开发的,这意味着Vue有十分完善的中文资料,这十分有助于我们更好的掌握相关知识和将之应用。

Vue与jQuery一样,全部是基于javascript这门语言构建。但两者区别很大,前者是框架,后者是一个库。怎么理解这个说法呢?这就好比Flask和Requests的区别,大家可以类比的想象一下。

Vue可以从很多方面去解读其用处和价值,这之中最常遇到的两个概念便是「双向数据绑定」和「渐进式框架」。有关这些概念的理解,我觉得最好的方式便是充分的实践,所以我这里暂且不和大家说明这些概念,而在之后的实践中,通过具体的案例再向大家作以相关的说明。

Vue的简单应用

大凡大家对HTML(前端)有一点基础,并使用过jQuery,就会知道应用jQuery的方式之一就是在网页里写入jQuery库的网址,这样jQuery就会生效于当前的网页应用中。相比较我们在Python中使用的pip预先安装库的方式,这实在是很有特色的一种方式。让我们就用曾经熟悉的方式,开始使用Vue。

 
   
   
 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4.    <meta charset="utf-8">

  5.    <script src="https://cdn.jsdelivr.net/npm/vue"></script>

  6.       <title>Hello Vue.js!</title>

  7. </head>

  8. <body>

  9. <div id="app">

  10.    <p>{{ message }}</p>

  11. </div>

  12. <script>

  13.    new Vue({

  14.        el: '#app',

  15.        data: {

  16.            message: 'Hello Vue.js!'

  17.        }

  18.    })

  19. </script>

  20. </body>

  21. </html>

上述是一个HTML文件的全部内容,我们在这个文件中引入并使用了Vue,做了一个简单的 Hello World 式网页。代码非常简短,理解起来不难,但我们应该马上会有两个问题。

第一个问题,用浏览器打开这个页面后,页面显示 Hello Vue.js!,在通过阅读源代码,我们发现「Hello Vue.js!」这句话并不是直接写在 <p>标签里的,而是写在了 <script>里,这对于首次接触Vue这样类似技术的人来说,实在是一个有些古怪的实现方式,因为感觉把简单的事情复杂化了。那么,Vue为什么要这样做?

第二个问题,可以通过上面的源代码很容易看出来显示Hello Vue.js!的地方是

<p>{{ message }}</p> 这段。对于使用过Python的Flask框架的人来说,这个{{ }} 的符号和用法十分眼熟,甚至和Vue一样,都是用在HTML文件里的。那么两者有什么联系吗?

对Vue再多点了解

先来看看上面的第一个问题。

没错,如果单纯的看 Hello World程序,显然为了实现网页能显示一段文字,这样的做法是复杂化了,但实际应用中, 类似的目的Vue从来就没有要求这样进行实现,这段示例代码这样做纯粹是为了展现Vue的特色。

所谓Vue的特色,就是把页面的容器(div标签)所需的一系列资源组件化,并提供近似统一的API。这么说可能还不够形象,让我们再来看看一个更有意思的实现。

这是一个在Vue官网上的示例,大家也可以动手去测试一下。

简单了解前、后端分离与Vue.js的基本实践(上)

这个示例的功能是,当你在输入框中输入内容或更新之前输入的内容时,页面上会同步显示最新的内容,这个特性非常有意思,并且非常实用。显而易见,这个同步的变化是前端直接完成的,并没有和服务器进行通信。实现这个功能,全部的代码就是演示里灰色色块的部分,加上引入Vue和初始化的那段script(见前面的代码)。如果用jQuery去实现上面的这个功能,则麻烦很多,大家有兴趣的可以去试试。

上面的这个例子,我们很容易的通过其简短的代码,去理解其表面的运行逻辑:首先,我们对 <input>增加了一个Vue特有的v-model属性,使其关联message,然后我们在 <p>里去用 {{ message }} 这样的语法方式调用message,并打印其内容。结果就是当我们在输入框中输入点什么时, {{ message }} 就会展现(打印)对应的输入内容。

上面的这个演示,就是Vue里所谓「双向数据绑定」概念的一个实例。

不过,如果仅看上面这个例子,从感觉上来说,这个例子怎么看都像是「单向」的,而不是双向的。怎么说呢?就是我们在输入框中输入内容,让下面的文字改变,这难道不是单一方向的变化么?事实也是我们在这个例子中并没有看到改变文字然后去影响输入框中的内容,如果有这个功能,似乎才是我们理解的双向。

其实上面的这个例子的确是「双向」的,因为我们第一次看到这个页面时,输入框中没有默认值,所以你知道的,脑筋急转弯有时是件困难的事情。没关系,既然是简明教程,我们就应当更简明的说清楚这点。

到现在为止,我们总共看了一段hello代码外加一个案例,那不如让我们结合一下:

 
   
   
 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4.    <meta charset="utf-8">

  5.    <script src="https://cdn.jsdelivr.net/npm/vue"></script>

  6.       <title>Hello Vue.js!</title>

  7. </head>

  8. <body>

  9. <div id="app">

  10.    <p>{{ message }}</p>

  11.    <input v-model="message" placeholder="edit me">

  12.    <input v-model="message" placeholder="edit me">

  13. </div>

  14. <script>

  15.    new Vue({

  16.        el: '#app',

  17.        data: {

  18.            message: 'Hello Vue.js!'

  19.        }

  20.    })

  21. </script>

  22. </body>

  23. </html>

上面这段 Hello Vue.js! 代码和最开始的那段代码区别不大,一眼就能看出多加了两个输入框而已。我们直接运行这个HTML页面,在浏览器中第一次看到这个页面时,是这样的:


如果你有一点点HTML的基本知识,应该知道,当在 <input>中指定placeholder时,输入框默认应该显示placeholder=的内容,就上面这段代码,就是输入框中应显示 edit me ,但实际情况并不是这样,而是显示了Hello Vue.js!。

我们再试着改变其中一个输入框中的内容,有意思的现象出现了:页面文本部分和另一个输入框也跟着变。再试试另一个输入框,也是同样的效果。

这样,我们在两个输入框中任何一个输入内容,另一个输入框也会同步变化,当然,文本部分更自不用说。

这样,我们改变A,B会变;我们改变B,A会变,这可不就是「双向」的了?!

好了,理解了这个概念,我们再来看下 {{ }} 这个和我们熟悉的Flask里的{{ }}究竟有什么关系?

先说答案,那就是没关系(你一定猜到了对不对)。

没关系就是没关系,没什么好说的,但是却有影响,而且是很严重的。

什么影响呢?就是上面这段代码,如果作为Flask的模板文件直接使用,会根本没办法用,Flask会就此报错,大致意思是告诉你 {{ message }} 这个变量并没有在(服务端)程序里传入。

原因是Vue的花括号,是Vue自己用的,而Vue能生效,是页面加载到浏览器,也就是客户端,再加载完成 <script>里我们指定的库文件后才能生效。你应该知道,Flask处理模板里的{{ message }} 是在服务器上就已经做完的事情,给到浏览器的页面上早已经把 {{ message }} 换成了对应的东西,而这个时候(在服务器上)Vue根本还没生效呢。

这种情况,其实就是语法冲突。怎么解决呢?好在Vue有替代方式,而且十分简单,就是:

 
   
   
 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4.    <meta charset="utf-8">

  5.    <script src="https://cdn.jsdelivr.net/npm/vue"></script>

  6.       <title>Hello Vue.js!</title>

  7. </head>

  8. <body>

  9. <div id="app">

  10.    <p v-text="message"></p>

  11.    <input v-model="message" placeholder="edit me">

  12.    <input v-model="message" placeholder="edit me">

  13. </div>

  14. <script>

  15.    new Vue({

  16.        el: '#app',

  17.        data: {

  18.            message: 'Hello Vue.js!'

  19.        }

  20.    })

  21. </script>

  22. </body>

  23. </html>

啊?哪里变了?一眼还真是看不出来呢。还是不卖关子了,注意下 <p v-text="message"></p> ,之前是 <p>{{ message }}</p>

没错,我们把花括号的方式,改为标签属性的方式,用 v-text="message”,去将

标签应该显示的内容绑定到了message上。

这样的变化,还是一样的效果,但是上面这段代码我们放到Flask里,就不会报错了。

“我”请求“你” - 浏览器直接访问豆瓣

要完成本次的目标,还有一点至关重要,就是实现客户端直接与豆瓣的通信,而不走我们的服务器去完成,只有这样,才不至于快速用完豆瓣给的门票(单IP每小时限制)。

既然我们谈到Vue是一个框架,类似Flask,那么想必也有类似Requests这样的插件咯?!答案是肯定的,这就是axios(https://github.com/axios/axios)。当然,除了axios我们还有其他的诸多选择,但因为axios是Vue「官方优选」计划中的指定品牌,那我们还是用点好的吧。

我们先看下之前写的服务端程序,可以看到从头到尾我们只用到了GET方法请求,这样,意味着我们对于axios的使用,也只要学会如何完成本地GET请求即可,点到即止。当然,你有兴趣的话可以好好的尝试一下axios的其他用法。

在官方的Github主页上,有详细的使用指南,我们只要照葫芦画瓢,就能学会怎么用了。

但有一点需要注意,我们目前都是采用直接在页面上引入第三方库链接文件的方式去引入第三方框架或插件的,但是官方给的链接可能在国内访问速度不理想,而如果你有基本的HTML相关的知识,就知道一个网页在浏览器展现总是先试图加载完JS文件的,这意味着如果引入的库文件访问速度不理想,会造成页面等半天才有内容显示出来,而之前都是白屏。解决这个问题比较粗暴的方案是把文件直接下载到本地,然后放到自己的服务器上,之后在网页中引入本地文件,而不是一个其他网站的链接。

比如,axios的官方指南推荐引入的链接是:

 
   
   
 
  1. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

如果你发现上面这个链接的文件打开的速度很慢,作为解决方案的一种,你可以下载这个文件,然后再引入本地下载好的文件。

至于GET请求的方法,官方指南的第一个例子就是了:

简单了解前、后端分离与Vue.js的基本实践(上)

这里我们选择GET里的第二种模式。我们可以看下之前写的后端程序,会发现我们的程序是要带一个可变参数的,这样正好对应第二种模式

简单了解前、后端分离与Vue.js的基本实践(上)

变脸 - 更换前端模板框架

既然我们这次要运用Vue这样的前端框架,作为附带的一个福利,我们可以尝试用下基于Vue的前端模板了。有什么好的选择呢?我觉得饿了么的就不错:Element。我们可以看到,相比较之前我们采用的模板,起码这次的这个是有中文文档的,使用起来便利不少。而且看起来我们即将采用的新方案也非常漂亮,各种封装好的组件比之前的功能更丰富更强大。

引入方式也非常简单:

简单了解前、后端分离与Vue.js的基本实践(上)

小试牛刀

通过前面的了解,想必大家都跃跃欲试了。但先别急着下手,我们先来规划一下:

(1)我们应该先尝试一下引入axios以及使用axios去与豆瓣通信。如果通信正常,能拿到豆瓣返回的,和之前服务端请求方式得到的是一样的数据,我们就可以放心删除后端对应的逻辑代码了。

(2)接着我们应该让输入框输入的内容不再往后台提交,而是直接改变axios里请求时每次附加的参数。通过前面的了解,我们知道可以靠Vue做到这点。

(3)如果到此一切顺利,我们可以清理一下之前后端里的表单逻辑相关的代码,因为再也用不上了。

至此,我们虽然做到可以让前端直接请求豆瓣,输入表单去改变请求时附带的参数,但触发请求和展现请求返回的结果(电影数据)这些至关重要的事情我们还没解决。于是乎,我们还有最后几件事儿:

(4)弄清楚前端怎么让用户点击输入框边上的按钮能触发程序去请求豆瓣。

(5)弄清楚怎么像之前在模板里利用 {% for %} 和 {% if %} 去展现查询结果一样去仅利用前端来完成这件事。毕竟往后数据不过服务器了,这样不拿掉 {% for %} 这种东西,程序会报错不是?!

(6)最后最后,再看看,还有什么该删的删,不用的则弃之。

让我们先从(1)开始。

非常明确的是,我们肯定先从index.html下手,按照axios的官方指南,我们直接复制一些代码到网页的对应位置。

简单了解前、后端分离与Vue.js的基本实践(上)

简单了解前、后端分离与Vue.js的基本实践(上)

上面这段修改后的代码不难理解。console.log(xxx) 这部分是类似于Python之中的log,但我个人认为实际作用则十分接近Python的print(),用于打印程序的输出,帮我我们调试程序。在上面这段代码中,console.log两次出现,意思是在请求成功或请求失败后,分别打印成功得到的数据和失败的信息。

我们运行整个Python程序,并打开这个页面,启用浏览器的开发者模式(一般F12开启),然后看到一个悲剧:

简单了解前、后端分离与Vue.js的基本实践(上)

这个悲剧的意思是,我们跨域了。我们再核实下请求的情况:

简单了解前、后端分离与Vue.js的基本实践(上)

我们发现请求是成功的,并且得到了返回的数据,但是因为跨域问题,浏览器不会去应用这些得到的数据,也就是说,不解决这个问题,我们不能在页面上把结果最终展现给用户。除非告诉用户「敬请您按下键盘的F12,启用开发者模式,然后……」。

跨域 - 善意的麻烦

以往我们的开发都不涉及真正的前后端分离,总是客户端请求自己的服务器,所以很少甚至从来也不会遇到跨域的问题。但是一旦步入到前后端分离的阵营,这个问题可真是要家常便饭了。

什么是跨域?就是主要为了安全的原因,浏览器限制了你的网页随随便便去访问另一个网站上的数据。

这里有一点难以理解的在于,我们知道在网页上是可以链接别的地方的图片,CSS文件等资源,甚至JS也可以的(前面引入JS库和框架的方式),我们也知道前述的这些方式本质也是一个GET请求,这里同为GET请求,怎么就不行了呢?原来,为了安全上的需要,浏览器不允许随便引入些危险的东西,比如JS脚本,但又考虑到便于网页的开发,浏览器给了一个很小的让步,即允许以下面这种方式引入JS资源:

 
   
   
 
  1. <script src=”http://www.www.com/xxx.js”></script>

但除了这种方式以外,用其他方式试图实现利用JS脚本进行与外部网站通信时,都会被浏览器视为有风险的行为而加以限制和制止。这种机制导致的问题便是跨域问题。

这里又有两点需要说明:

(1)就算使用上面这种允许的方式去请求,如果返回的是纯JSON格式的数据,也会出问题。

(2)我们一直说「另外一个网站」或者「外部网站」,但没有给出明确的定义,而事实可能真的出乎你的意料,因为这种机制把 www.abc.com 和abc.com也当成两个不同的网站,即互为对方的外部网站。甚至我们再熟悉不过的 http://127.0.0.1:5200/ (Flask本地开发服务器)和 http://127.0.0.1 也不能算同一个。是不是出乎你的意料?那么具体的定义或是规则究竟是如何的呢?我们有一张表:

简单了解前、后端分离与Vue.js的基本实践(上)

说白了,http://www.abc.com/ 这部分必须一摸一样的,才能算自己人。

上面这张表说明的就是浏览器如何判定请求跨域还是不跨域的,整张表是一个规则或者说策略,好心人给这张表取了一个响当当的名字,叫「同源策略」。

时代在发展,这种坑爹的策略阻碍了我们的前行,于是乎有一个混社会的组织提出了一个名为「跨域资源共享」的新标准新设想,我们不必弄清前因后果,只需要知道这个标准已经基本被现代浏览器所广泛接受就是,然后这个标准大致的意思就是,想要跨域?可以!只要通信双方(客户端、服务器)都同意就可以进行了。

但在这个美好设想出现之前的黑暗时期,聪明伶俐的人儿想到了另一个办法解决跨域的需求,就是JSONP大法,啊,又一个新名词!

无需紧张,我们先弄清楚JSONP本身的意思,JSONP 就是JSON with Padding的缩写,这么说来是和JSON这个数据格式相关的咯?答案确实是这样,上面不是谈到,就算用引入JS脚本这种许可的方式去运作,一旦返回的数据是纯JSON格式也会出问题,所以JSONP起初就是为了解决这个问题而生的。同样的,JSONP也需要服务端予以支持。

我们对于跨域的相关知识,就先了解到这。

解决跨域

有两个坏消息和一个好消息,你打算先听哪个?

第一个坏消息是,前面说到过跨域需求在现代浏览器里被一种标准所支持,条件是客户端和服务器双方都同意,遗憾的是,这次我们的服务器不再是我们自己的了,而是豆瓣,因为我们让客户端直接去联系豆瓣了呀,但是,豆瓣说他们不同意用新标准跨域。

好消息是,豆瓣说「朋友,都是朋友吗~我们可以JSONP」。

第二个坏消息是,想用JSONP,你之前学的axios就白学了。因为axios不支持JSONP。「君子动口不动手……」,切勿恼怒,我让你这期学axios必然有针对你的善良用意,不过,眼下我们用不上了而已。

我们先替换掉axios,引入JSONP插件库,至于用哪个库,不如我们就用axios官方指南里,JSONP示例推荐的那个:

 
   
   
 
  1. <script src=”http://wzrd.in/standalone/jsonp@latest”></script>

简单了解前、后端分离与Vue.js的基本实践(上)

由于我们不是以安装包的方式安装JSONP插件的,所以「var jsonp =……」 这句话不用要,其余部分我们直接复制过来,然后照着后台的请求逻辑修改下,核心代码部分如下:

简单了解前、后端分离与Vue.js的基本实践(上)

我们再次运行下程序,看看情况:

简单了解前、后端分离与Vue.js的基本实践(上)

可以看见已经能成功接收豆瓣返回的数据。至此,我们的跨域问题就解决了。上面这些工作都是在完成(1)里的目标,虽然遇到挫折,我们不得不放弃axios,改用JSONP,不过总归是解决了问题。

上Vue

我们来看看规划(2)中的问题如何解决。

有了前面的经验,我们已经知道如果想使用Vue,必须先初始化Vue,于是我们马上在index.html中添加了之前已经见过的部分:

简单了解前、后端分离与Vue.js的基本实践(上)

我们也知道输入框部分该如何处理了。需要注意,所有与Vue相关的代码,必须包裹在

里,我们回顾下之前的有关Vue的各种例子,会发现确实有这个细节,真是一不注意就忽视了这小小的细节。经过这步,我们的代码变成了下面这样:

简单了解前、后端分离与Vue.js的基本实践(上)

运行下看看。看起来界面变化不大,但是输入框中自己有内容了。通过之前的了解,我们可以猜到这是Vue再起作用了,因为我们在下面的JS里定义了「 message: 'Hello Vue.js!’」。同样的,我们也已经了解到,如果我们改变输入框中的内容,也会造成前面的message同步变更。现在的问题是,我们要将这种变更去影响到JSONP里的请求,更准确的说是要能让请求的查询参数「q=雷神3」跟着变。

简单了解前、后端分离与Vue.js的基本实践(上)

怎么做呢?好在编程语言很多东西都是相通的,我们可以举一反三,对于上面这个问题,可以马上想到肯定要使用一个变量作为中间变量,之后让上面几个要变的部分用同一个中间变量就能达成目的。

我们已经顺利的让输入框能影响到Vue里的message这个变量,但接下来的工作就完全摸不着头脑了。

没关系,就跟考试一样,不会写的难题,我们一会再回过头来看。

更换前端模板框架

就改动到现在的代码而言,表单这部分显然处理得并不干净,我们可爱的大按钮还是用的FLask提供的技术去完成的。之前我们简短的介绍了一下新的前端框架,如果之前你有兴趣点开看了下,应该看到左侧五花八门的组件中,赫然写着按钮、输入框、表单……,那我们还等什么?!

根据手册指示,我们引入该框架的样式库(CSS),脚本库(JS),我们发现这两个文件还是挺大的,首次打开页面速度受到很大影响,所以我们按照之前讲过的,把这些资源弄到本地去引入。

当然的,我们也要跟bulma说拜拜了:简单了解前、后端分离与Vue.js的基本实践(上)

简单了解前、后端分离与Vue.js的基本实践(上)

完成相关依赖的引入后,我们直接看手册里表单组件人家是怎么实现的:

简单了解前、后端分离与Vue.js的基本实践(上)

经过仔细的研读相关介绍和示例代码,大概能了解一二:原来,和bulma很不同的在于,这个前端框架不仅帮我们实现了样式,连同一些判断校验,也一并实现了,还提供了很多函数方法供我们使用。

虽然我们目前还是有些看不太懂,不过照葫芦画瓢,我们尝试改改表单,一个输入框,一个按钮,然后照着我们的需要,改改细节:

简单了解前、后端分离与Vue.js的基本实践(上)

这样之前表单部分的HTML已经被完全替换为新的。我们通过官方指南和示例知道,输入框影响的数据部分,需要写成一个类似Python字典的形式,于是改写之前的message为下面这样的形式:

简单了解前、后端分离与Vue.js的基本实践(上)

我们还发现按钮部分有一段「 @click="onSubmit”」,通过官方指南,我们知道这就是用户点击按钮后触发的方法,根据示例,我们把方法也增加到Vue部分:

简单了解前、后端分离与Vue.js的基本实践(上)

对比官方的第一个例子,看起来该添加的都添加完了。唉?编辑器怎么红线报错?!没事,我们先忽略这个情况。这个错误提示不影响程序的运行,我们运行下程序:

简单了解前、后端分离与Vue.js的基本实践(上)

非常好,虽然看起来页面有点古怪(错位),但大体的结构和样子都出来了。而且我们点击「查询」按钮,会看到开发界面里新出现了一条信息。回顾下代码,我们知道是「 console.log('submit!’);」在起作用,因为之前我们也已经讲过console.log()这个方法的意义。

简单了解前、后端分离与Vue.js的基本实践(上)

这里有一个小技巧,对于javascript这门语言,有一些很类似Python

的特性,比如关键字this代表一个对象实例化后的自己本身(类似Python的self),于是,我们在 new Vue()(javascript的对象实例语法)里,如果用this.form.message,能获取到data里的message,可能你要问为什么不是this.data.form.message呢?似乎这样才对吗!原因是Vue做了一些工作,给了你方便,你就别钻牛角尖了,享受这个好处吧。于是我们稍微改一下之前的代码:

简单了解前、后端分离与Vue.js的基本实践(上)

再运行试试,嘿~好玩,现在我们输入什么,点击「查询」按钮后这儿就显示什么:

简单了解前、后端分离与Vue.js的基本实践(上)

看起来刚才我们做了一件小事,但仔细想想真是意义非凡。因为我们不知不觉应用了Vue挂载方法函数的接口methods,而我们凭借一点点基本的javascript语法知识和举一反三这个强有力的思想工具,知道onSubmit()是一个方法,而且只在我们想他工作的时候工作(点击「查询」按钮),在这个方法中,我们又能读到message这个变量,那么……,没错,我们有了一个思路,就是能不能把之前的jsonp这个方法放到onSubmit()里,然后再把请求参数去调用message,这样不仅可以改变请求参数,而且仅在用户点击了「查询」按钮的情况下才去请求豆瓣,而不是我们现在这样,页面一加载就自动去请求豆瓣一下。

顺便说句,没错,虽然看起来大段大段代码,但仔细看看jsonp形式是jsonp(){ ……},这就是javascript里的函数的语法。我们知道在Python里,一个方法可以去调用另一个方法,javascript也应该是可以的。而且我们看到console.log()这个方法可以按照现在的样子写到onSubmit()内部,那么jsonp()是否也能类似处理呢?答案是肯定的!我们试试:

简单了解前、后端分离与Vue.js的基本实践(上)

记住,错误提示先忽略。我们直接运行下程序看看,首先,我们发现程序不会自动请求豆瓣了,那么我们点击下「查询」按钮再看看:

简单了解前、后端分离与Vue.js的基本实践(上)

简单了解前、后端分离与Vue.js的基本实践(上)

我们测试一下效果:

简单了解前、后端分离与Vue.js的基本实践(上)

成了!因为我们的输入框中有默认值,所以什么都不输入时,点击「查询」按钮,就能以输入框中的默认值作为查询关键字去查询,当然,并没有一部电影叫「Hello Vue.js!」的。所以我们应该考虑输入框中就不要有默认值了,怎么办呢?太简单了,因为看代码很容易看出来导致默认值的就是写Hello Vue.js!的地方,于是我们删除他成了:

简单了解前、后端分离与Vue.js的基本实践(上)

再运行下看看页面的变化。嘿,有效。不过这儿有个小缺陷了,就是我们此刻点击「查询」按钮,会发现直接触发了查询。也就是说,之前我们使用后端提交的方式,Flask的表单插件会帮助我们检查输入情况,比如处理什么也没输入的这种情形,但当我们抛弃这种方式了,连数据都不经过后端转发了,后端也就没办法检查输入的异常情况了,于是我们这儿什么也不输入,依然可以提交查询,这显然不对。

不过咱们不是已经知道解决办法了。

简单了解前、后端分离与Vue.js的基本实践(上)

但是这会儿,咱们先得解决编辑器对于代码报错的问题了。因为这会儿不解决,一会复制粘贴代码量比较多时,如果大段代码报错,究竟错在哪儿我们就不容易发现了。编辑器之所以报错,是因为咱们用的是PyCharm,是用来写Python的,本身并没有考虑太多写javascript的场景,但事实是这家的编辑器很强大,只要配置得当,支持写javascript还不是轻飘飘的一件事情。怎么配置得当呢?针对上面报错的问题,其实就是编辑器选择的javascript规范版本选错了,一般当前的选择是5.1,我们选6就行了。

简单了解前、后端分离与Vue.js的基本实践(上)

选择完毕保存,编辑器需要重新加载一些东西,之后生效,看,之前的红线没有了:

简单了解前、后端分离与Vue.js的基本实践(上)


我们继续解决前面的表单校验问题。通过官方指南,我们知道如果要对表单进行校验,要完善以下几个地方:

第一,我们要写好表单的校验规则。这没有难度,把指南的代码直接复制到同样的位置即可,由于我们仅需要判断表单不能为空,所以就留一个校验是否为空的规则即可。

简单了解前、后端分离与Vue.js的基本实践(上)

第二,把表单关联规则组,再把表单项(咱们这儿是一个输入框)关联到规则组里的具体某一条规则。这里大家按照Python里的字典和列表语法去理解javascript里这部分的层次关系时,应该不难理解。

简单了解前、后端分离与Vue.js的基本实践(上)

第三,改写按钮的提交函数,接入校验判断逻辑。这部分也不难理解,按照官方的示例,经过改写的方法首先是开始接受一个参数,之后在内部引用表单组件的校验方法,如果校验通过,则继续下一步,于是我们把之前的请求代码放到校验通过的逻辑分支里;不成功逻辑分支那块,只留个return false就行了。

简单了解前、后端分离与Vue.js的基本实践(上)

第四,既然提交函数变化了,成了要接受一个参数的形式,那想必表单提交按钮的那块也要改写成传入一个参数的形式。这里只需要注意,不要原封不动的照抄官方的即可,因为有些参数的名字要根据我们自己的代码相应的变成我们自己的。

简单了解前、后端分离与Vue.js的基本实践(上)

完成上面的修改后,我们再运行一下代码看看效果。

通过调试信息,我们已经能够确认一切都达到了我们的预期。

至此,我们完成了之前规划的(2)、(4),剩下的(3)、(6)都是一类问题,比较简单,我们可以放到最后一起解决。现在来看看(5)该如何处理。

全新的if和for

到现在为止,我们总算处理完了从用户访问输入,到豆瓣返回查询结果这一段的事情,能够坚持到现在实在是值得一件鼓励的事儿,让让我们再接再厉,一鼓作气!

由于之前的工作,导致我们的代码在主逻辑上已经前后端分离,这意味着现在留在前端页面上的for和if这堆东西无法再正常生效。现在之所以这些历史遗留不会造成页面报错,是因为当前的程序并没有触发他们去运行,所以我们得以躲过一劫。

没有什么能难倒我们的不是?!我们这回采取「逐个击破」法,先来看看index.html,这个页面的「历史遗留」如下:

简单了解前、后端分离与Vue.js的基本实践(上)

显然,这段代码完全可以不要了,因为我们知道这些都是后端表单校验的提示信息的交互实现,而之前的工作里我们早就把这些在前端进行实现了,因此可以全部删除了。

再看看剩下的return.html。我们发现,抛开和index.html同质化的部分,剩下的关键代码就是下图的这部分。

简单了解前、后端分离与Vue.js的基本实践(上)

这部分代码的作用我们早已如数家珍,毕竟是我们之前亲自下厨的杰作。现在的问题是,当我们前后端分离后,这个页面不能再像之前那样访问到了,换句话说,我们怎么让index.html跳转到return.html都是个问题。

不过话说回来,之前如果把两个页面写成一个也是完全可行的,只需要利用{ % if % }做点手脚,把return.html上面的内容藏到if里面,如果有返回结果,把if置真就行了。有了这样的思路,那么剩下的问题就是如何写这个if了。

因为前后端分离,后端是已经全完不知道用户在前端上的操作的,所以必须在前端上实现所有逻辑。而我们知道{ % if % }这样的语法是Flask在服务端渲染页面时的专用语法,不能为浏览器所用。不过,

简单了解前、后端分离与Vue.js的基本实践(上)

官方文档不难理解,细节上的操作一会再说。我们先得处理一个问题,就是返回数据的数据模型,即之前传递给return.html的data。因为之前是在后端处理data的,所以前端上只有接收到data后的渲染逻辑,现在我们要把所有的逻辑都放到前端上完成。

要说这件事情,可真是一点难度都没有,为什么呢?因为豆瓣返回的JSON格式数据,是javascript的「自家人」。另一方面,可以照着之前的后端代码逻辑去改写前端,有得参考。

首先,之前的data是取的豆瓣返回数据里的subjects这个字段下的数组,每一个元素是一部影视剧的信息,我们在前端取到这段是这样写:

简单了解前、后端分离与Vue.js的基本实践(上)

这种语法实在是很熟悉不是?!这里我们是先尝试打印下看看是不是正确的,再继续后续步骤。查询关键字「雷神3」,点击「查询」按钮,看下效果。

简单了解前、后端分离与Vue.js的基本实践(上)

看起来完全可行。那么剩下的问题就是怎么找个地方放这些数据了。

嗯,这不就是变量的工作?!通过前面的实践,虽然有些javascript语法细节操作我没讲明白,但是聪明的你应该能猜到既然此刻的数据是一个数组,那么声明一个数组变量就是了,至于javascript怎么声明一个数组变量,以及怎么结合Vue使用,那更容易想到了,因为之前的表单校验rules下面的校验规则不就是一个一个数组,然后放到了该放的位置,我们照葫芦画瓢,在rules后面放一个空数组。

简单了解前、后端分离与Vue.js的基本实践(上)

相应的,我们也早已学会怎么在函数中去拿到这里的数据,至于怎么把数据写入到变量,那还不就是一个赋值语句而已。既然如此,我们这里一次尝试这两个玩法吧。

简单了解前、后端分离与Vue.js的基本实践(上)

上面的代码用意是,我们把接收到的数据写入到变量movies里,之后我们再尝试输出打印movies,来看看movies是否正常接收到了我们期望存入的数据。运行程序看看。

简单了解前、后端分离与Vue.js的基本实践(上)

看起来一切正常呢。真这样想?!哈哈,你上当了!

因为照这样的写法,你期望的那个movie并没有存入任何数据。怎么回事?!你不相信?!你可以进行这样一个测试,只需要把console.log(this. movies)改成console.log(this.form.message)就行了,照理说,这里会打印this.form.message的信息,毕竟我们前面刚刚使用其拼接请求参数来着,但事实是我们看到了调试器里显示一堆的报错信息。

说起来,这算是javascript里一个广为诟病的坑,问题出在 this 这个关键字上。前面我们在jsonp()的方法里,使用this.form.message去拼接请求参数,这样我们就有一个惯性思维,认为jsonp()里也可以使用this. movies去完成期望的操作,殊不知前面的this和这儿的this根本不是一回事了,即代指的对象已经不是同一个。由于我们本期不以科普javascript为主,所以里面的细枝末节暂且不表,反正你清楚不是同一个就是,然后知道如何解决,那就是下面这样处理。

简单了解前、后端分离与Vue.js的基本实践(上)

具体来说,就是我们在按钮提交的方法内新增一个变量self,让其等同于第一次出现的this,之后在最里层函数里将之前的this改写成self,这样我们就能保证对其的操作都是我们期望的了。

这个问题在搜索引擎上有不少答案,因为太多人遇到这个问题但不能理解,若你现在就想弄个明白,大可以自己搜索一下了解了解。

这样,我们的数据模型看起来已经建立成功和正常运作了。

接下来,我们回到开始的问题,怎么去写if和for。这里,我们先思考一下层次的先后关系。毫无疑问,return.html里的那部分逻辑放到index.html中首先就需要一个if来决定是否出现,出现的前提条件应该是豆瓣已经返回了数据,之后我们再使用for循环遍历一下数组,把查询到的结果展示出来。这样,层次非常明确了,即外层一个if,内层一个for。

从外到内,我们先来看看if怎么处理。Vue官方的指南里,告诉我们if这样写。

简单了解前、后端分离与Vue.js的基本实践(上)

非常简短的一个示例,我们能够明白是靠 v-if= 右侧的值来决定条件的真假,如果为真,则对应的HTML标签(和标签内的内容)展现在页面上。结合之前写表单时碰到的 v-model 这样的标签属性,我们能猜到 v-if= 右侧是可以放一个变量的,既然如此,我们再增加一个if这件「隐身衣的开关」变量好了。

简单了解前、后端分离与Vue.js的基本实践(上)

上面的变动是两处;一处是我们增加了一个变量 getmovies,并设定默认值是 false,这符合刚进入页面时的情况(没有查询),另一处我们都会了,作用也很明显,既然用户点了按钮,也查询到了数据,那么getmovies得是真了,这样里面的内容就展示了。这些变动都没什么难度,就是一个细节,我们发现javascript里的false和true首字母都是小写的。

接着,我们随便找块地方写一个

(但一定要在之间)来测试一下是否行得通。

简单了解前、后端分离与Vue.js的基本实践(上)

简单了解前、后端分离与Vue.js的基本实践(上)

通过测试,可以确认经过上面的改写工作后,if的运行非常成功,符合期望。剩下的工作就是把之前的for塞到if里了。

先一股脑儿的把之前return.html里的相应部分复制到index.html里来,至于放到哪里,那还用思考?!

简单了解前、后端分离与Vue.js的基本实践(上)

之后对照Vue的示例改写原有的代码,这对于已经学到这儿的我们来说,大部分都没有任何难度,我们可以直接下手。

简单了解前、后端分离与Vue.js的基本实践(上)

因为对于<p>标签使用v-text=属性改写后,会直接替换其中的全部内容。所以对于有固定前缀的字段,比如上映年份,我们用了一个小技巧,即在

里再写一层,对应用变化即可。

现在还剩下一个图片和一个超链接标签里的历史遗留我们还不知道怎么处理,碰到这种问题,先看看官方指南有没有应对之法。这里的历史遗留是Flask框架提供的模板引擎语法,类似的,我们能够确定这个层面的问题也应该是Vue框架相关的,而不是element ui(模板)层面的问题,因为这里跟表现形式无关。

因此这不属于前端模板层面的问题,应该去查阅Vue的指南,而这又显然是语法问题,所以进一步确认应该看看Vue的语法有何解决方案。

简单了解前、后端分离与Vue.js的基本实践(上)

我们在官方指南中找到了一个以超链接标签作为案例的说明,在这篇指南的末尾,我们还了解到了其缩写语法。

简单了解前、后端分离与Vue.js的基本实践(上)

对照指南,我们改写下自己的代码。

然后运行下看看效果。

虽然页面看起来有些问题,但主要的功能都可以正常运作了。这样,之前的规划中(5)的部分我们也已经完成。

美中不足

至此,我们已经完成了前后端分离的主要工作,并且能让程序的主要功能运行正常。我们再也不用担心IP限制的问题了,有谁使坏想消耗我们的门票,那也只是在消耗他自己的名额而已了。不过仍有些小问题,比如页面样式错位还有电影海报无法正常显示等等,限于篇幅,也是给大家消化消化新知识的时间,我们将在下一期中继续完善我们的程序,以解决这些美中不足。

好了,大家可以休息休息了,喝杯水,得空了删除那些已经没有用的代码和页面吧,最后给大家罗列了一个清理清单(据说删除东西是一件很爽快的事情):

(1)return.html已经彻底无用,删除。

(2)后端程序里flaskwtf、wtforms这些表单插件可以卸载了,主程序中类class SearchForm(FlaskForm)以及路由index里的if form.validateon_submit():等等表单相关的代码都可以删除了。

(3)唯一剩下的路由index(),因为不用再接收客户端的POST请求,因此methods=['GET', 'POST'] 可以删除。

(4)由于我们使用了新的前端框架和前端模板,因此static里的bulma整个文件夹都可以删除掉了。

(5)index.html里的console(console.log/error)都可以删除了,因为这个是供我们开发时调试用的,就像后端的print(),上线的时候都不需要了。

最后的最后,别忘记了更新下requirements.txt,另外照旧,本次实践的全部代码可以在GitHub上下载到:https://github.com/duzhipeng/ZhihuJianMingJiaoCheng

以上是关于简单了解前后端分离与Vue.js的基本实践(上)的主要内容,如果未能解决你的问题,请参考以下文章

前端大讲堂基于Vue的前后端分离实践

前后端分离实践:基于vue实现网站前台的权限管理

前后端分离实践:基于vue实现网站前台的权限管理

聊天系统Vue.jsReact.jsnode.jsMongoDBwebsocketsocket.io前后端分离毕

使用Django + Vue.js快速而优雅地构建前后端分离项目

第一步:类似前后端分离的基础框架思路