使用 Flask 和 Vue.js 來構建全棧單頁應用
- 2019 年 12 月 5 日
- 筆記
在這個教程中,我將向你展示如何將 Vue 的單頁面應用和 Flask 後端連接起來。
簡單的來說,如果想在 Flask 中使用 Vue 框架是沒有什麼問題的。但在實際中存在一個明顯的問題就是 Flask 的模版引擎 Jija 和 Vue 一樣使用雙花括弧來渲染,
對於 Jinja 模板和 Vue 的語法衝突問題,這裡有一個很好的解決方案 (https://github.com/yymm/flask-vuejs)。
做一個用 Vue.js 做前端 (用單頁組件,HTML5 歷史模式的「vue-router」,以及其他好的特性),用 Flask 做後端的單頁應用怎麼樣?
簡單地說,這個應用應該是這樣的:
- Flask 用來驅動一個包含 Vue.js app 的 index.html
- 前端開發過程中我用到 Webpack 和它提供的所有酷的特性
- Flask 有我能從 SPA 訪問到的 API 埠
- 在我開發前端時,我能運行 Node.js 來訪問 api 埠
聽起來很有意思吧?我們開始吧。
客戶端
為了生成基本的 Vue.js 文件結構,我將使用 vue-cli。如果你沒有安裝它,請運行下邊的命令:
$ npm install -g vue-cli
客戶端和後端程式碼將會被拆分到不同的文件夾中, 請運行下邊命令初始化前端部分:
$ mkdir flaskvue $ cd flaskvue $ vue init webpack frontend
下邊是安裝過程中我的設置:
- Vue build — Runtime only
- Install vue-router? — Yes
- Use ESLint to lint your code? — Yes
- Pick an ESLint preset — Standard
- Setup unit tests with Karma + Mocha? — No
- Setup e2e tests with Nightwatch? — No
下一步:
$ cd frontend $ npm install # 安裝完成後運行下邊命令 $ npm run dev
到這裡,你應該安裝好 Vue.js 了吧!那就讓我們添加一些頁面。
在 frontend/src/components
文件夾中添加 Home.vue
和 About.vue
兩個文件。並添加如下內容到對應的文件中:
// Home.vue文件的內容 <template> <div> <p>主頁</p> </div> </template>
和
// About.vue文件的內容 <template> <div> <p>關於</p> </div> </template>
我們將使用它們正確地識別我們當前的位置 (根據地址欄)。現在,我們需要更改 frontend/src/router/index.js
文件來呈現我們的新組件:
import Vue from 'vue' import Router from 'vue-router' const routerOptions = [ { path: '/', component: 'Home' }, { path: '/about', component: 'About' } ] const routes = routerOptions.map(route => { return { ...route, component: () => import(`@/components/${route.component}.vue`) } }) Vue.use(Router) export default new Router({ routes, mode: 'history' })
如果您嘗試輸入 localhost:8080
和 localhost:8080/about
,您應該會看到相應的頁面。

為了創建一個包含靜態資產的包,我們幾乎已經準備好構建一個項目了。在此之前,讓我們為它們重新定義輸出目錄。
在前端 frontend/config/index.js
索引。找到下一個設置
index: path.resolve(__dirname, '../dist/index.html'), assetsRoot: path.resolve(__dirname, '../dist'),
然後把它們變成下面這樣
index: path.resolve(__dirname, '../../dist/index.html'), assetsRoot: path.resolve(__dirname, '../../dist'),
因此,帶有 html/css/js 包的 /dist
文件夾將與 /frontend
具有相同的級別。現在您可以運行 $ npm run build
來創建一個包。

Back-end
我將使用 python 3.6 來進行 flask 應用程式開發。在根目錄 /flaskvue
下創建一個子目錄來放後端程式碼,並在子目錄中初始化一個虛環境:
$ mkdir backend $ cd backend $ virtualenv -p python3 venv
執行下面的命令來激活虛環境 (macOs 作業系統):
$ source venv/bin/activate
在 windows 中激活虛環境請參考此文檔 docs.
在虛環境中安裝 flask:
(venv) pip install Flask
現在我們開始開發 flask 應用程式。在根目錄下創建 run.py
文件:
(venv) cd .. (venv) touch run.py
將下面程式碼添加到這個文件中:
from flask import Flask, render_template app = Flask(__name__, static_folder = "./dist/static", template_folder = "./dist") @app.route('/') def index(): return render_template("index.html")
這段程式碼與 Flask starter Hello world 程式碼略有不同。主要的不同之處在於,我們指定了靜態和模板文件夾來用前端包指向 /dist
文件夾,在根文件夾中運行 Flask 服務:
(venv) FLASK_APP=run.py FLASK_DEBUG=1 flask run
這將在 localhost:5000
上啟動一個 web 伺服器。FLASK_APP
指向伺服器啟動文件,FLASK_DEBUG=1
將在調試模式下運行。
如果一切都是正確的,您將看到熟悉的主頁,您在 Vue 上所做的。
與此同時,如果你試圖添加一個 /about
頁面。Flask 將拋出一個頁面未找到的錯誤。
確實如此,因為我們在 vue-router
中使用了 HTML5 歷史模式,我們需要去 配置我們的伺服器 讓所有路由跳轉到 index.html
. 這個在 Flask 中很容易做到 。將現有的路由修改為如下內容:
@app.route('/', defaults={'path': ''}) @app.route('/<path:path>') def catch_all(path): return render_template("index.html")
新的 URL 鏈接 localhost:5000/about
將會跳轉到 index.html
,並且 vue-router
將會自己處理其餘的事情。
添加 404 頁面
因為我們定義了一個將所有請求跳轉到 index.html
的路由,因此 Flask 將無法捕獲到 404 錯誤(以及不存在的頁面),將一些找不到頁面的請求也跳轉到 index.html
。
所以我們需要在 Vue.js
的路由文件中設置一條路由規則去處理這種情況。
在 frontend/src/router/index.js
中添加一行:
const routerOptions = [ { path: '/', component: 'Home' }, { path: '/about', component: 'About' }, { path: '*', component: 'NotFound' } ]
這裡的 '*'
是 vue-router
中的通配符,用以代表任何除了我們已經定義好的路由之外的其他情況。
接下來我們在 /components
文件夾中創建一個 NotFound.vue
文件,並寫幾行簡單的程式碼:
// NotFound.vue <template> <div> <p>404 - Not Found</p> </div> </template>
現在通過運行 npm run dev
來重新運行前端伺服器,並嘗試一些不存在的 URL 鏈接。
例如 localhost:8080/gljhewrgoh
。你就可以看到 「Not Found」 的消息提示了.
添加 API 端點
我的 'Vue.js/Flask' 的最後一個例子。'Vue.js/Flask' 教程將在伺服器端創建 API 並在客戶端發送。
我將創建一個簡單的端點,它將返回一個從 1 到 100 的隨機數。
打開 run.py
並添加:
from flask import Flask, render_template, jsonify from random import * app = Flask(__name__, static_folder = "./dist/static", template_folder = "./dist") @app.route('/api/random') def random_number(): response = { 'randomNumber': randint(1, 100) } return jsonify(response) @app.route('/', defaults={'path': ''}) @app.route('/<path:path>') def catch_all(path): return render_template("index.html")
首先,我從 'Flask' 庫導入了 'random' 庫和 'jsonify' 函數。然後我添加了新的路由 ' /api/random ' 來返回 JSON,如下所示:
{ "randomNumber": 36 }
您可以通過導航到 localhost:5000/api/random
來測試此路由。
此時,伺服器端工作已經完成。是時候在客戶端展示了。我會改 Home.vue
組成來顯示我的隨機數:
<template> <div> <p>Home page</p> <p>Random number from backend: {{ randomNumber }}</p> <button @click="getRandom">New random number</button> </div> </template> <script> export default { data () { return { randomNumber: 0 } }, methods: { getRandomInt (min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min }, getRandom () { this.randomNumber = this.getRandomInt(1, 100) } }, created () { this.getRandom() } } </script>
在這個階段,我只是在客戶端模擬隨機數生成過程。所以,這個組件是這樣工作的:
- 初始化變數
randomNumber
等於0
- 在
methods
部分 ,我們有getRandomInt(min, max)
方法, 它將返回一個指定範圍內的數字,getRandom
函數,將調度之前的函數,並將其值賦給randomNumber
- 創建組件方法後,將調用
getRandom
來初始化randomNumber
- 觸發按鈕事件後,我們將調用
getRandom
獲取新數字
在前端,現在在首頁你應該看到我們的隨機數產生。讓我們把它連接到後端。
為此,我們將使用 ' axios' 庫,它允許我們發出 HTTP 請求並返回帶有 JSON 響應的 JavaScriptPromise
。讓我們安裝它:
(venv) cd frontend (venv) npm install --save axios
再次打開 Home.vue
文件並 在 <script>
區域添加一些更改:
import axios from 'axios' methods: { getRandom () { // this.randomNumber = this.getRandomInt(1, 100) this.randomNumber = this.getRandomFromBackend() }, getRandomFromBackend () { const path = `http://localhost:5000/api/random` axios.get(path) .then(response => { this.randomNumber = response.data.randomNumber }) .catch(error => { console.log(error) }) } }
在最開始我們導入 axios 庫。然後有一個新方法 getrandomfrombackend
,它將使用 AXIOS 非同步訪問 API 並檢索結果。最後,方法 getRandom
現在應該使用 getRandomFromBackend
函數來獲取隨機值。
保存文件,轉到瀏覽器中,再次運行開發伺服器,刷新 localhost:8080
然後… 您應該在控制台中看到一個錯誤,並且沒有隨機值。
但別擔心,一切都正常。我們得到 [cors]錯誤,這意味著我們的 flask 伺服器 API 默認關閉到其他 Web 伺服器(在我們的情況下,它是運行 vue.js 應用程式的 node.js 伺服器)。
如果您使用 npm run build
創建一個 bundle 並打開 localhost:5000
(就是 flask 伺服器),您將看到正在工作的應用程式。但是,每次對客戶端應用程式進行一些更改時,創建一個包並不十分方便。
讓我們使用 Flask 的 CORS 插件,這將允許我們為 API 訪問創建規則。插件名為 [flask-cors](http://flask-cors.readthedocs.io/en/latest/),讓我們安裝它:
(venv) pip install -U flask-cors
您可以閱讀插件的文檔,文檔中更好地說明了在伺服器上啟用 CORS 的方法。
我將使用特定於資源的方法並將 {「origin」「:」*「}
應用於所有 / api / *
路由(所以每個人都可以使用我的 / api
端點)。在 run.py
中:
from flask_cors import CORS app = Flask(__name__, static_folder = "./dist/static", template_folder = "./dist") cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
通過以上更改,您可以直接從前端開發伺服器調用 Flask API。
更新:
實際上,如果你通過 Flask 提供靜態文件,則不需要更新 CORS 擴展。感謝 [Carson Gee](https://github.com/carsongee)這個技巧。
解決思路如下。如果應用程式處於調試模式,它將只代理我們的前端伺服器。
否則(在生產模式)提供靜態文件。以下是實現的程式碼:
import requests @app.route('/', defaults={'path': ''}) @app.route('/<path:path>') def catch_all(path): if app.debug: return requests.get('http://localhost:8080/{}'.format(path)).text return render_template("index.html")
實現方式簡單而優雅,像魔術一樣✨!
現在,您擁有一個使用自己喜歡的技術構建的全棧應用程式啦。


後記
最後,我想就如何改進此解決方案說幾句話。
首先,只有在您想要讓 API 可供外部伺服器訪問時才使用 CORS 擴展。否則只需使用代理前端開發伺服器的技巧。
另一項改進是避免在前端硬編碼 API 路由。也許您需要創建一個包含 API 路由名稱的辭彙集。
因此,當您更改 API 路由時,您只需刷新這個辭彙集即可。前端關於路由名稱的程式碼不需要更改。
通常在開發過程中,您將至少需要兩個終端窗口:一個用於 Flask ,另一個用於 Vue.js 。在生產環境中,你將不需要為 Vue 運行單獨的 Node.js 伺服器。
源程式碼:https://github.com/oleg-agapov/flask-vue-s…
感謝您的閱讀!
原文地址:https://codeburst.io/full-stack-single-p…
譯文地址:https://learnku.com/python/t/24985