寫這篇文章的目的,更可能是讓本身更熟悉vue-cli腳手架建立項目的依據和項目結構,其次是但願個人學習過程能夠幫到有疑惑的同窗,有什麼錯誤還但願能夠獲得指教css
爲何要分上、下,由於最近學習react.js,發現項目框架除了使用的js庫不一樣(vue.js、react.js),配置基本上是大同小異的html
這也是我佔坑(臉大)的理由vue
此次的目的是接着上篇在框架中添加vue-router,在第三方工具包的基礎上針對業務進行封裝html5
路由是vue組件可以靈活切換的關鍵所在,vue-router是vue的官方路由。node
cnpm i vue-router --save-dev
複製代碼
咱們在src目錄下新建router文件夾,並在router新建index.js,同時在components下新建login.vuereact
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中,以下操做webpack
import router from "./router/index";
new Vue({
el: "#app",
router,
render: h => h(App)
});
複製代碼
在你須要渲染組件的地方添加router-view標籤ios
<template>
<div>
<router-view />
</div>
</template>
複製代碼
如今你能夠在瀏覽器中訪問了es6
可是有時候咱們的項目要求沒有登錄是不能進入首頁的,這個功能能夠利用router的前置導航守衛實現web
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的文件,咱們只須要提供插件,和設置是否開啓它
若是你看到這裏了,那你的毅力告訴我,你之後技術確定更加厲害