寫這篇文章的目的,更多是讓自己更熟悉vue-cli腳手架創建項目的依據和項目結構,其次是希望我的學習過程可以幫到有疑惑的同學,有什麼錯誤還希望可以得到指教
為什麼要分上、下,因為最近學習react.js,發現項目框架除了使用的js庫不同(vue.js、react.js),配置基本上是大同小異的
這也是我佔坑(臉大)的理由
代碼看這裡
這次的目的是接著上篇在框架中添加vue-router,在第三方工具包的基礎上針對業務進行封裝
路由是vue組件能夠靈活切換的關鍵所在,vue-router是vue的官方路由。
cnpm i vue-router --save-dev
我們在src目錄下新建router文件夾,並在router新建index.js,同時在components下新建login.vue
import Vue from vue import VueRouter from vue-router Vue.use(VueRouter)
const router = new VueRouter({ mode: "hash", routes: [ { path: "/", name: "index", components: require("../components/hello.vue") }, { path: "/login", name: "login", components: require("../components/login.vue") } ] })
export default router
到這裡還沒結束,我們還需要將router對象掛載到根實例上,在index.js中,如下操作
import router from "./router/index";
new Vue({ el: "#app", router, render: h => h(App) });
在你需要渲染組件的地方添加router-view標籤
<template> <div> <router-view /> </div> </template>
現在你可以在瀏覽器中訪問了
但是有時候我們的項目要求沒有登陸是不能進入首頁的,這個功能可以利用router的前置導航守衛實現
beforeEach就是前置導航守衛的鉤子函數,它接收三個參數,to、from、next
我使用了h5的localStorage模擬cookie保存用戶信息,這裡只是個測試,如果你喜歡cookie,可以使用自己喜歡的cookie包,個人喜歡js-cookie
思路是這樣的,登錄的時候驗證完對的用戶名和密碼,就設置一個字元串作為token存儲在本地,token的作用就是下次可以免登錄(這種做法的安全性是個問題)
在router/index.js中
router.beforeEach((to, from, next) => { // 如果登錄的時候設置過 if(localStorage.getItem("token") != null){ if(to.name == login){// 如果還訪問登錄頁就導向首頁 next({path: /}) }else{// 給所有其它頁面放行 next() } }else{// 如果沒有設置這個值,為空,說明沒有登錄,導向登錄頁 if (to.name == "login") { next(); } else { if (to.name == "login") { next(); // 這裡要記得給登錄頁放行,不然會進入死循環 } else { next({ path: "/login" }); } } } })
雖然這裡已經做得很不錯了,但依然沒有發揮出vue-router靈活和強大的一面。換句話說,這裡能做的事還有很多很多,比如項目是一個管理系統的話,可能會因角色不同,進入首頁的側邊欄目是不一樣的,雖然看上去是個很複雜的過程,但是仔細分析下也就幾步,感興趣的同學可以看這裡
同級路由就是兩個router-view標籤是並列的,分別給兩個標籤用name屬性命名,所以命名視圖就是給視圖命名了後的視圖router-view
// app.vue <router-view name="navbar"></router-view> <router-view name="main"></router-view>
// 對應的路由寫法就是 import { NavbarComponent, MainComponent } from "@/components" const router = new VueRouter({ routes: [ { path: /, components: { navbar: NavbarComponent, main: MainComponent } } ] }) // 在app.vue中 <template> <div> <router-view name="navbar" /> <router-view name="main" /> </div> </template>
這裡訪問路由看到這個結果
嵌套路由在管理系統更為常見,我們經常使用的layout佈局就是嵌套路由實現的
// 這次我這樣定義路由 { path: "/index", name: "index", components: require("@/components/hello"), children: [ { path: "login", name: "login", components: require("@/components/login") } ] },
// 並且這樣寫hello組件 <template> <div class="test">index page <router-view></router-view> </div> </template>
我在瀏覽器中訪問http://localhost:8080/#/index/login,可以訪問到下面的頁面
官方推薦與服務端交互使用axios,它是基於ajax封裝的,用起來十分簡潔
cnpm i axios --save-dev
axios有兩種使用方法一種是使用axios對象調用get或者post請求方法, 另一種是使用axios api,使用axios()函數,參數是個配置對象options
axios.get(/getUser) .then(data=>{ // 請求成功處理函數 console.log(data) }) .catch(err=>{ // 請求失敗處理函數 console.log(err) }) // 或者 axios({ url: /getUser, mothod: get }) .then(data=>{ // 請求成功處理函數 console.log(data) }) .catch(err=>{ // 請求失敗處理函數 console.log(err) })
在實際的項目中,經常需要對發送的請求或者服務端響應的結果進行處理,請求很多時,挨個處理就很繁瑣,也很不切合實際,希望在全局就已經處理好了,我們只負責發送請求和接收服務端響應。
好在axios提供了這種方法。
在src下新建http文件夾,下面新建request.js文件
import axios from "axios";
// 重新實例化一個axios實例,這個實例會覆蓋所有默認屬性 const server = axios.create({ baseURL: "/api", timeout: 5000, heads: { content-type: application/x-www-form-urlencoded }, });
// 或者通過修改實例的defaults屬性,這兩種方法是等價的 server.defaults.baseURL = "/api"; server.defaults.timeout = 5000;
export default server
不僅如此,我們還希望在每次發送請求的時候帶上登錄是設置的token值,在收到伺服器錯誤時可以做出相應的反饋,比如返回的狀態碼為404,就導航到404頁面
這裡可以使用axios提供的攔截器對象,具體做法如下
// 設置攔截器 // 請求攔截器 server.interceptors.request.use( config => { config.headers.token = localStorage.get("token"); return config; }, err => { return Promise.reject(err); } ); // 響應攔截器 server.interceptors.response.use( response => { return response; }, err => { switch (err.response.status) { case 404: router.push({ path: "/404" }); break; case 504: router.push({ path: "/504" }); break; } return Promise.reject(err); } );
記得添加錯誤對應的頁面和路由
// router/index.js { path: "/404", name: "404", components: require("../components/404.vue") }, { path: "/504", name: "504", components: require("../components/504.vue") }
簡單的封裝已經完成了,只需要在使用的地方引入它
這裡我們就得說說跨域了,這是前後端分離項目無法避開的。對於前後端分離的項目,之所以跨域是因為瀏覽器有同源策略的安全限制,來防止跨站請求偽造和跨站腳本攻擊
跨域就是違背了同源策略,webpack-dev-server提供瞭解決跨域的方案
// webpack-dev-server方案 // 在webpack.config.js中devServer devServer: { port: 8080, proxy: { "/api": { target: "http://127.0.0.1:10000", // 代理的目標地址 changeOrigin: true // 是否開啟跨域 } } }
當然axios也提供了跨域方案,只是比較複雜,還需要後端同學的設置配合纔行,這裡就不說了
這裡使用一個例子來說明使用方法。當然,我沒有寫登錄功能,所以記得把之前寫的路由前置守衛的鉤子函數注釋掉,不然無法跳轉
邏輯是這樣的,在接入首頁時自動發送請求,獲取用戶列表,請求方式為get
// hello.vue import axios from ../http/request.js
export default { created() { axios({ url: /getUsers, method: get }) .then(data =>{console.log(data)}) .catch(err =>{console.log(err)}) } };
如果你已經有後端伺服器的支持,但是沒有寫對應的路由,那它會在控制檯報錯超時,即狀態碼為504,頁面會自動跳轉到504頁面
假如你和我一樣沒有後端服務程序,那控制檯報錯為404,即狀態碼為404,頁面會自動跳轉到404頁面
到這裡項目框架功能已經算是擼完了,但是它有很多地方不夠正規,和cli創建的項目相比,生產環境和開發環境還沒有分離;其次一個合格的大眾框架,應該有eslint語法檢測;雖然主流的瀏覽器已經開始支持es6測試語法,但是最好可以加入babel-loader,將es6語法轉換es5語法。
babel-loader主要是將es6語法轉換成瀏覽器兼容的es5語法,但是項目中node_moudles下也有很多js文件,全都轉換會使得速度變慢,並且使項目無法運行,所以需要配置轉換文件的路徑或者屏蔽掉無需轉換的路徑
babel-loader只是個載入器,轉換代碼的工作是babel-core babel在轉換 ES2015 語法為 ECMAScript 5 的語法時,babel 會需要一些輔助函數, 例如 _extend。babel 默認會將這些輔助函數內聯到每一個 js 文件裏,這樣文件多的時候,項目就會很大。
所以 babel 提供了 transform-runtime 來將這些輔助函數「搬」到一個單獨的模塊 babel-runtime 中,這樣做能減小項目文件的大小
// babel-preset-env這是babel預設的語法規則,babel-preset-是前綴,env是包名 // @babel/plugin-transform-runtime是插件,babel-plugin-是前綴,transform-runtime是插件名
cnpm i babel-core babel-loader babel-preset-env @babel/plugin-transform-runtime --save-dev
這裡要注意下載的版本,起初我下載的是babel-plugin-transform-runtime,結果一直報錯
TypeError: this.setDynamic is not a function
後來我下載了@babel/plugin-transform-runtime才解決了這個問題
// rules中 // include 表示哪些目錄中的 .js 文件需要進行 babel-loader // exclude 表示哪些目錄中的 .js 文件不要進行 babel-loader module: { rules: [ { test: /.js$/, loader: "babel-loader", // include: [path.resolve("src")], exclude: /node_modules/, options: { presets: [env], // env提供語法轉化的規則,這裡是babel預設的 plugins: [transform-runtime] // 這裡放我們自己想要使用的插件 } } ] }
babel-loader默認是什麼都不會做的,需要在預設presets選項中指定插件為你工作的語法規則,babel已經為我們提供了幾套預設方案,babel-preset-env就是最新的語法轉換規則,其中babel-preset-只是前綴, 另外babel-loader還可以提供豐富的插件做更多的事,只需要在options下的plugins選項中指明
注意:要用的插件一定要記得下載
下面除了想自動語法轉化外還希望將組件懶載入,使載入變快,這時候需要使用插件babel-plugin-syntax-dynamic-import,所以先下載這個包,然後將它放入plugins中
// 下載 cnpm i babel-plugin-syntax-dynamic-import --save-dev
// 在module選項下 rules: [ { test: /.js$/, loader: "babel-loader", include: [path.resolve("src")], exclude: [path.resolve("node_modules")], options: { presets: [env], // env提供語法轉化的規則,這裡是babel預設的 plugins: [@babel/transform-runtime,"syntax-dynamic-import"] } } ]
假如你需要的插件很多,還需要單獨的配置,你可以在項目根目錄下創建一個.babelrc的文件替換掉options,配置語法完全同json,下面給個示例,完全等同上面的options
// 在.babelrc文件中,必須是要有兩個數組類型的選項:presets、plugins { "presets": ["env"], "plugins": ["@babel/transform-runtime", "syntax-dynamic-import"] }
presets更多選項看這裡
eslint對初學者來說是極其不友好的,因為它有嚴格的要求,使得很多人都想著關閉它,但是不得不說eslint在多人協作團隊來說是至關重要的,它可以保證代碼風格的一致性
cnpm i eslint-plugin-vue eslint-friendly-formatter eslint-loader eslint --save-dev
// 此外記得全局安裝eslint支持vue的插件,否則會報錯Cannot find module eslint-plugin-vue,但是局部仍然要安裝 cnpm i eslint-plugin-vue -g
在webpack配置文件中,添加新的rules
{ test: /.(js|vue)$/, loader: "eslint-loader", include: [path.resolve(__dirname, src)], // 指定檢查的目錄 options: { // 這裡的配置項參數將會被傳遞到 eslint 的 CLIEngine formatter: require(eslint-friendly-formatter) // 指定錯誤報告的格式規範 } }
eslint官網提到了三種使用方法(這也是查找規則所在位置的順序)
我選擇效仿別人使用.eslintrc.js形式,既然是js文件我們就需要跟寫js模塊文件一樣暴露出一個對象
// 像這樣 module.exports = {}
這裡我只說幾個常用的或者重要的配置選項,官網支持中文版,比babel中文版友好很多
// 像這樣 module.exports = { // 默認情況下,ESLint 會在所有父級目錄裏尋找配置文件,一直到根目錄。如果你想要你所有項目都遵循一個特定的約定時,這將會很有用,但有時候會導致意想不到的結果。為了將 ESLint 限制到一個特定的項目,在你項目根目錄下的 package.json 文件或者 .eslintrc.* 文件裏的 eslintConfig 欄位下設置 "root": true。ESLint 一旦發現配置文件中有 "root": true,它就會停止在父級目錄中尋找。 "root": true, // extends: 繼承屬性,值可以是 "eslint:recommended" "eslint:all" 或者是個插件 或者是個文件 extends:[ "eslint:recommended", "eslint:all" // "plugin:react/recommended" ], // env: 指定運行的環境,可選值有browser、node、es6等等,值為布爾類型,true為開啟,默認為false "env": { "browser": true }, // plugins: 指定插件,使用之前需下載,vue項目中使用的話vue就是屬於插件 "plugins": [ "vue" // "react" ], // rules: 指定語法規則,分為0,1,2三個等級對應off(關閉檢測),warn(只警告),error(直接報錯) "rules": { "eqeqeq": "off", "curly": "error", // if結構中必須使用{} "quotes": ["error", "double"] // 更多rules看這裡 https://www.jianshu.com/p/80267604c775 // 這裡要說下rules的extends特性 // 假如你的設置是這樣的 // "eqeqeq": ["error", "allow-null"] // "eqeqeq": "warn" // 那麼它最後生成的配置是 "eqeqeq": ["warn", "allow-null"] },
}
優秀的框架肯定會根據配置文件查看是否開啟eslint,我在配置文件中定義了一個可配置的變數esLint,當它的值為true時開啟eslint語法檢測,但默認值為false
const esLint = false
// 替換掉配置好的eslint-loader .... { test: /.js$/, exclude: /node_modules/, loader: "babel-loader" }, esLint?{ test: /.(js|vue)$/, loader: "eslint-loader", include: [path.resolve(__dirname, "src")], // 指定檢查的目錄 options: { // 這裡的配置項參數將會被傳遞到 eslint 的 CLIEngine formatter: require("eslint-friendly-formatter") // 指定錯誤報告的格式規範 } }:{}, ...
項目裏我一直使用的是相對路徑,雖然有友好的代碼提示,但是一旦你改變了文件位置,就會報錯不止,直到你把所有路徑修改正確,最好的做法就是使用絕對路徑,並且給路徑添加別名,可以方便我們的書寫
resolve: { alias: { ··· "@": resolve("src") } }
這樣我們在import中書寫路徑時可以用@表示根目錄到src,後面繼續跟剩下的路徑
import hello from "@/components/hello.vue";
resolve: { extensions: [".js", ".vue", ".json"], // 當然,這裡還可以添加.css、.less、.sass,這都是允許的 ··· }
現在你在import時有.js或者.vue文件時不用再寫後綴名了
import hello from "@/components/hello";
花了兩天時間終於把框架擼完了,說實話,以前沒有在意的細節現在都很通透,當然這對我來說只是一小步,我還打算選擇一套UI框架來封裝常見的業務,使框架更加的模塊化和完善
另外值得一提的是,一段時間前已經發布了@vue/cli3,也就是vue腳手架第三個版本,看了文檔發現它只是將插件配置用vue.config.js代替了,它會自動根據vue.config.js的配置生成一份webpack.config.js的文件,我們只需要提供插件,和設置是否開啟它
如果你看到這裡了,那你的毅力告訴我,你以後技術肯定更加厲害
推薦閱讀: