寫這篇文章的目的,更多是讓自己更熟悉vue-cli腳手架創建項目的依據和項目結構,其次是希望我的學習過程可以幫到有疑惑的同學,有什麼錯誤還希望可以得到指教

為什麼要分上、下,因為最近學習react.js,發現項目框架除了使用的js庫不同(vue.js、react.js),配置基本上是大同小異的

這也是我佔坑(臉大)的理由

徒手擼個vue項目框架(上)

徒手擼個vue項目框架(下)

徒手擼個react項目框架(上)

徒手擼個react項目框架(下)

代碼看這裡

mmdctjj/meow?

github.com
圖標

這次的目的是接著上篇在框架中添加vue-router,在第三方工具包的基礎上針對業務進行封裝

一、路由

路由是vue組件能夠靈活切換的關鍵所在,vue-router是vue的官方路由。

1.引入vue-router

a.下載

cnpm i vue-router --save-dev

b.實例化

我們在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

c.掛載

到這裡還沒結束,我們還需要將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>

現在你可以在瀏覽器中訪問了

2.利用導航守衛實現登錄控制

但是有時候我們的項目要求沒有登陸是不能進入首頁的,這個功能可以利用router的前置導航守衛實現

beforeEach就是前置導航守衛的鉤子函數,它接收三個參數,to、from、next

  • to:即將進入的路由對象
  • from:離開的路由對象
  • next:一定要調用該方法來resolve這個鉤子

我使用了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靈活和強大的一面。換句話說,這裡能做的事還有很多很多,比如項目是一個管理系統的話,可能會因角色不同,進入首頁的側邊欄目是不一樣的,雖然看上去是個很複雜的過程,但是仔細分析下也就幾步,感興趣的同學可以看這裡

3.利用命名視圖實現特殊佈局

a.同級路由

同級路由就是兩個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>

這裡訪問路由看到這個結果

同級路由

b.嵌套路由

嵌套路由在管理系統更為常見,我們經常使用的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封裝的,用起來十分簡潔

1.下載

cnpm i axios --save-dev

2.介紹

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)
})

3.封裝

在實際的項目中,經常需要對發送的請求或者服務端響應的結果進行處理,請求很多時,挨個處理就很繁瑣,也很不切合實際,希望在全局就已經處理好了,我們只負責發送請求和接收服務端響應。

好在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")
}

簡單的封裝已經完成了,只需要在使用的地方引入它

4.跨域代理

這裡我們就得說說跨域了,這是前後端分離項目無法避開的。對於前後端分離的項目,之所以跨域是因為瀏覽器有同源策略的安全限制,來防止跨站請求偽造和跨站腳本攻擊

跨域就是違背了同源策略,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也提供了跨域方案,只是比較複雜,還需要後端同學的設置配合纔行,這裡就不說了

5.使用

這裡使用一個例子來說明使用方法。當然,我沒有寫登錄功能,所以記得把之前寫的路由前置守衛的鉤子函數注釋掉,不然無法跳轉

邏輯是這樣的,在接入首頁時自動發送請求,獲取用戶列表,請求方式為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語法。

1. babel-loader

babel-loader主要是將es6語法轉換成瀏覽器兼容的es5語法,但是項目中node_moudles下也有很多js文件,全都轉換會使得速度變慢,並且使項目無法運行,所以需要配置轉換文件的路徑或者屏蔽掉無需轉換的路徑

a.下載

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才解決了這個問題

b.配置

// 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選項中指明

注意:要用的插件一定要記得下載

c. 一個例子說明.babelrc文件的用法

下面除了想自動語法轉化外還希望將組件懶載入,使載入變快,這時候需要使用插件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更多選項看這裡

2.eslint語法檢測

eslint對初學者來說是極其不友好的,因為它有嚴格的要求,使得很多人都想著關閉它,但是不得不說eslint在多人協作團隊來說是至關重要的,它可以保證代碼風格的一致性

a.下載

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

b.使用

在webpack配置文件中,添加新的rules

{
test: /.(js|vue)$/,
loader: "eslint-loader",
include: [path.resolve(__dirname, src)], // 指定檢查的目錄
options: { // 這裡的配置項參數將會被傳遞到 eslint 的 CLIEngine
formatter: require(eslint-friendly-formatter) // 指定錯誤報告的格式規範
}
}

c.配置規則

eslint官網提到了三種使用方法(這也是查找規則所在位置的順序)

  • 使用注釋的形式嵌入到代碼
  • 在package.json中添加eslintConfig欄位,這裡指定配置
  • 使用.eslintrc.*文件(任何後綴名)

我選擇效仿別人使用.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"]
},

}

4.設置是否開啟eslint

優秀的框架肯定會根據配置文件查看是否開啟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") // 指定錯誤報告的格式規範
}
}:{},
...

3.使用絕對路徑

項目裏我一直使用的是相對路徑,雖然有友好的代碼提示,但是一旦你改變了文件位置,就會報錯不止,直到你把所有路徑修改正確,最好的做法就是使用絕對路徑,並且給路徑添加別名,可以方便我們的書寫

resolve: {
alias: {
···
"@": resolve("src")
}
}

這樣我們在import中書寫路徑時可以用@表示根目錄到src,後面繼續跟剩下的路徑

import hello from "@/components/hello.vue";

4.省略後綴名

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的文件,我們只需要提供插件,和設置是否開啟它

如果你看到這裡了,那你的毅力告訴我,你以後技術肯定更加厲害

推薦閱讀:

相關文章