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

寫這篇文章的目的,更可能是讓本身更熟悉vue-cli腳手架建立項目的依據和項目結構,其次是但願個人學習過程能夠幫到有疑惑的同窗,有什麼錯誤還但願能夠獲得指教css

爲何要分上、下,由於最近學習react.js,發現項目框架除了使用的js庫不一樣(vue.js、react.js),配置基本上是大同小異的html

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

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

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

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

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

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

1、路由

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

1.引入vue-router

a.下載
cnpm i vue-router --save-dev
複製代碼
b.實例化

咱們在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
複製代碼
c.掛載

到這裏還沒結束,咱們還須要將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

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

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

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,能夠訪問到下面的頁面

2、與服務端交互

官方推薦與服務端交互使用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頁面

3、細節的完善

到這裏項目框架功能已經算是擼完了,可是它有不少地方不夠正規,和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的文件,咱們只須要提供插件,和設置是否開啓它

若是你看到這裏了,那你的毅力告訴我,你之後技術確定更加厲害

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息