Vue(六):插件

插件

Vue.js 可以通過插件擴展自己的能力。

因爲插件的功能會使用 Vue 全局對象或者實例來調用,或者被修改從而在 Vue 的鉤子函數內起作用。🌰比如用於 http 調用的插件v ue-resource 被插入到 vue 後,可以使用: Vue.http.get(url) 的方式使用此插件提供的服務。

創建插件

// p1.js
var get = function(a){console.log('Hello  ' +a)}
var plugin = {}
plugin.install = function(Vue) {
    if (plugin.installed) {
        return;
    }
    Vue.who = get;
    Object.defineProperties(Vue.prototype, {
        $who: {
            get() {
                return {get:get}
            }
        }
    });
    Vue.mixin({
        created: function () {
          console.log('Plugin activiated')
        }
    })
}
if (typeof window !== 'undefined' && window.Vue) {
    window.Vue.use(plugin);
}
<html>
  <body>
    <script type="text/javascript" src="https://vuejs.org/js/vue.js"></script>
    <script type="text/javascript" src="p1.js"></script>
    <script type="text/javascript">
        var vue = new Vue()
        vue.$who.get('Vue Instance')
        Vue.who('Global Vue')
    </script>
  </body>
</html>

路由插件

vue-router 是一個 vue 官方提供的路由框架,使用它讓完成一個 SPA(Single Page App ,單頁應用)變得更加容易。

假設我們做一個 SPA,共兩個頁面,分爲爲 home、about,並提供導航 URL,點擊後分別切換這兩個頁面,默認頁面爲 home。那麼,可以有兩種方法完成此路由應用,差別在於是否使用腳手架。

不使用腳手架

創建 SPA 應用是非常簡單的,我們只要把組件和 URL 做好映射,並通知 vue-router 知道即可。

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Router test</h1>
  <p>
    <router-link to="/home">Go home</router-link>
    <router-link to="/about">Go about</router-link>
  </p>
  <router-view></router-view>
</div>
<script>
// 首先創建組件Home和About
const Home = { template: '<div>home</div>' }
const About = { template: '<div>about</div>' }
// 其次,做好組件和URL的映射
const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About },
]
// 通知router映射關係
const router = new VueRouter({
  routes :routes
})
// 把router註冊到app內,讓app可以識別路由
const app = new Vue({
  router
}).$mount('#app')
</script>
  • 首先引入 Vue.js 和 Vue-router.js 文件。
  • 使用自定義組件 router-link 來指定頁面導航,通過屬性 to 指定頁面導航的 URL。組件 router-link 會被渲染爲 <a> 標籤。
  • 使用自定義組件 <router-view> 作爲組件渲染的定界標記,符合當前導航 URL 的組件將會被渲染到此處。

使用腳手架

安裝依賴:npm i vue-router --save

  • main.js 文件:
    import Vue from 'vue'
    import App from './App'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    const Home = { template: '<div>home page</div>' }
    const About = { template: '<div>about page</div>' }
    const router = new VueRouter({
      routes :[
          { path: '/home', component: Home },
          { path: '/about', component: About },
          { path: '/', redirect: '/home' }
      ]
    })
    new Vue({
      el: '#app',
      template: '<App/>',
      router: router,
      components: { App }
    })
    
  • app.vue 文件:
    <template>
      <div id="app"><p>hi</p>
        <router-link to="/home">Home2</router-link>
        <router-link to="/about">About1</router-link>
        <router-view></router-view>
      </div>
    </template>
    

路由構造對象

👆main.jsroutes:[] 數組內承載的就是被稱爲路由構造的對象。對象內屬性:

🐱 path 路徑

路徑可以是絕對路徑,比如 /a/b/c,或者是相對路徑 a/b/c,並且路徑內可以使用 : 來設置參數。比如 /user/:id,這裏的 :id 就是一個參數。有了參數化能力,就可以做動態的路由匹配。

const router = new VueRouter({
  routes: [
     // 動態路徑參數 以冒號開頭
     { path: '/user/:id', component: User }
  ]
})

此處的 /user/:id 會匹配如下的模式:

/user/foo
/user/bar

並且在代碼中可以使用 $route.params.id 獲得匹配參數,這裏的情況下,匹配參數爲:

foo
bar
🐶 name 名稱

通過名稱來標識路由有時候很方便:

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      name: 'user',
      component: User
    }
  ]
})

要鏈接到一個命名路由,可以給 router-linkto 屬性傳一個對象:

<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>

會把路由導航到 /user/123

🐰 alias 別名

假設有一個路徑爲 A,它有一個別名爲 B,當用戶訪問 B 時,URL 保持爲 B,但是實際訪問的是 A。此功能讓你可以自由地將 UI 結構映射到任意的 URL,特別是在嵌套路由結構的情況下。

約定 A 的路徑爲 /a ,別名 B 爲/b ,那麼對應的路由配置爲:

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})
🐻 children 嵌套路由

實際的路由URL常常是由多層組件構成。比如:

/user/:id/profile
/user/:id/posts

這樣的嵌套結構可以用 children 屬性來完成:

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          path: 'profile',
          component: UserProfile
        },
        {
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

匹配到 /user/:id/profile 的話,渲染 UserProfile,匹配到 /user/:id/posts 的話,渲染 UserPosts。

🐼 redirect 重定向

此屬性可以把指定的路徑(path)重定向到此路徑(redirect)上。比如:

const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })

會重定向 /a/b

🐸 beforeEnter 導航鉤子

導航鉤子主要用來攔截導航,讓它完成跳轉或取消。配置如下:

const router = new VueRouter({
    routes: [
      {
        path: '/foo',
        component: Foo,
        beforeEnter: (to, from, next) => {
          // ...
        }
      }
    ]
})

參數說明:

  • to: 即將要進入路由對象。
  • from: 將要離開的路由對象。
  • next: 一定要調用該方法來 resolve 這個鉤子,可以有三種調用方式:
    • next(): 進行管道中的下一個鉤子。
    • next(false): 中斷當前的導航。
    • next(’/’) 或者 next({ path: ‘/’ }): 跳轉到一個不同的地址。
🦆 meta 元數據

定義路由的時候可以配置 meta 字段:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

那麼如何訪問這個 meta 字段呢?隨後可以在路由對象中使用此字段信息。典型情況是在 beforeEach 鉤子函數內使用此數據。

matched

例如,根據上面的路由配置,/foo/bar 這個 URL 將會匹配父路由記錄以及子路由記錄。

一個路由匹配到的所有路由記錄會暴露爲 $route 對象(還有在導航鉤子中的 route 對象)的 $route.matched 數組。因此,我們需要遍歷 $route.matched 來檢查路由記錄中的 meta 字段。

下面例子展示在全局導航鉤子中檢查 meta 字段:

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
  	// this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 確保一定要調用 next()
  }
})
🐔 components?: 命名視圖組件

有時候想同時(同級)展示多個視圖。例如創建一個佈局,有 sidebar(側導航) 和 main(主內容) 兩個視圖,這個時候命名視圖就派上用場了。

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Named Views</h1>
  <ul>
    <li>
      <router-link to="/">/</router-link>
    </li>
    <li>
      <router-link to="/other">/other</router-link>
    </li>
  </ul>
  <ul>
    <li>
  <router-view ></router-view> </li>
    <li>  <router-view  name="a"></router-view> </li>
    <li>  <router-view name="b"></router-view> </li>
  </ul>
</div>
<script>
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const Baz = { template: '<div>baz</div>' }
const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/',
      // a single route can define multiple named components
	  // which will be rendered into <router-view>s with corresponding names.
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    },
    {
      path: '/other',
      components: {
        default: Baz,
        a: Bar,
        b: Foo
      }
    }
  ]
})
new Vue({
    router,
  el: '#app'
})
</script>

路由鉤子函數

路由鉤子主要用來攔截導航(URL切換),在此處有一個完成跳轉或取消的機會。

鉤子類型有:全局路由鉤子;獨享路由鉤子;組件路由鉤子。

🍇 全局鉤子函數
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
   <h1>Router test</h1>
   <p>
     <router-link to="/home">Go home</router-link>
     <router-link to="/about">Go about</router-link>
   </p>
<router-view></router-view>
</div>
<script>
   const Home = { template: '<div>home</div>' }
   const About = { template: '<div>about</div>' }
   const routes = [
   { path: '/home', component: Home },
   { path: '/about', component: About },
   ]
   const router = new VueRouter({
       routes :routes
   })
   router.beforeEach((to, from, next) => {
       console.log(to.path,from.path,)
       next()
   });
   const app = new Vue({
     router
   }).$mount('#app')
</script>

當點擊 home 和 about 鏈接時,URL發生了切換,並且每次調用鉤子函數,此時案例會打印出 router 切換的來源 URL 和去向 URL,並調用 next() 函數完成本次導航。鉤子的參數有三個:

  • to:路由對象。指示來源。
  • from:路由對象。指示來源。
  • next:函數。如果是 next(),就完成跳轉到 to 指示的路由對象。如果傳遞參數爲 false,則取消此次導航;如果指定了地址或者路由對象,就跳到指定的地址或者對象。
路由對象

之前提到的 to、from 都是路由對象。對象內屬性有:

  • path。路徑,總是解析爲絕對路徑。
  • matched。數組,包含全部路徑的路由記錄。比如嵌套路由定義爲:
    routes: [
    { 
      path: '/user/:id', component: User,
      children: [
        { path: 'posts', component: UserPosts }
      ]
    }
    
🍇 獨享路由鉤子

可以在路由配置上直接定義 beforeEnter 鉤子:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
🍇 組件內的鉤子

使用 beforeRouteEnterbeforeRouteLeave,在路由組件內直接定義路由導航鉤子:

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
  },
  beforeRouteLeave (to, from, next) {
  }
}

異步組件

使用時才裝入需要的組件,可以有效提高首次裝入頁面的速度。在單頁面應用中,往往在路由切換時才載入組件,這就是一個典型場景。

🥑 實現

Vue.js 允許將組件定義爲一個工廠函數,動態地解析組件的定義。工廠函數接收一個resolve 回調,成功獲取組件定義時調用。也可以調用 reject(reason) 指示失敗

舉例:

// about.js
Vue.component('about', {
  template: '<div>About page</div>'
});
<!--index.html-->
<html>
<head>
  <title>Async Component test</title>
</head>
<body>

  <div id="app">
    <router-link to="/home">/home</router-link>
    <router-link to="/about">/about</router-link>
    <router-view></router-view>
  </div>

  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  <script>
    function load(componentName, path) {
      return new Promise(function(resolve, reject) { 
      // 組件定義爲function(resolve,reject){}函數,其內調用load函數,成功後resolve,否則reject。
        var script = document.createElement('script');
        script.src = path;
        script.async = true;
        script.onload = function() {
        // 函數load內通過創建標籤script加載指定文件,並通過onload事件當加載完成後,通過Vue.component驗證組件,存在就resolve,否則reject。
          var component = Vue.component(componentName);
          if (component) {
            resolve(component);
          } else {
            reject();
          }
        };
        script.onerror = reject;
        document.body.appendChild(script);
      });
    }
    var router = new VueRouter({
      routes: [
        {
          path: '/',
          redirect:'/home'
        },
        {
          path: '/home',
          component: {
            template: '<div>Home page</div>'
          },
        },
        {
          path: '/about',
          component: function(resolve, reject) {
            load('about', 'about.js').then(resolve, reject);
          }
        }
      ]
    });
    var app = new Vue({
      el: '#app',
      router: router,
    });
  </script>
</body>
</html>

爲了加載在服務器的 js 文件,我們需要一個 HTTP 服務器。可以使用 node.js 的 http-server 實現。安裝並啓動一個服務器的方法:

  • npm install http-server -g
  • http-server

    訪問:http://127.0.0.1:8080
🥑 異步組件的 webpack 方案

如果使用 webpack 腳手架,加載異步組件將會更加直觀。

用上面的案例,使用 webpack 實現:

首先創建腳手架,並安裝依賴:

vue init webpack vuetest #vue-cli版本在3以後創建項目就不用init改用create了
cd vuetest
npm i
npm run dev #vue-cli 3 以後,啓動命令變爲:npm run serve

訪問 localhost:8080,可以看到 Vue 的默認頁面。然後替換 main.js 文件爲:

import Vue from 'vue'
import App from './App'

import VueRouter from 'vue-router'
import About from './components/about'
Vue.use(VueRouter)

const Home = { template: '<div>home page</div>' }
// const About = { template: '<div>about page</div>' }
const router = new VueRouter({
  routes :[
    { path: '/home', component: Home },
    { path: '/about', component: function (resolve) {
    // Vue.js支持component定義爲一個函數:function(resolve){} ,在函數內,可以使用類似node.js的庫引入模式
        require(['./components/about'], resolve) 
        }
    },
    { path: '/', redirect: '/home' }
  ]
})
new Vue({
  el: '#app',
  template: '<App/>',
  router: router,
  components: { App }
})

並添加組件 about 到 src/components/about.vue :

<template>
  <div>about page</div>
</template>

再次訪問 localhost:8080,可以看到 Home 組件和 about 組件的鏈接。

http訪問插件

vue.js 本身沒有提供網絡訪問能力,但是可以通過插件完成。vue-resource 就是這樣一個不錯的插件,它封裝了 XMLHttpRequestJSONP,實現異步加載服務端數據。

🌰:由服務器提供 json 數據,啓動後等待客戶端的請求。數據爲 user 信息,內容爲:

var users = [
   {"name" : "1"},
   {"name" : "2"},
   {"name" : "3"},
]

從GET方法開始

從最簡單的 GET 方法入手,場景如下:

  • 客戶端使用 HTTP GET 方法來訪問 /users
  • 服務端返回整個 json 格式的 user
  • 客戶端檢查返回的結果,和期望做驗證。

使用了 express.js 庫做 HTTP Server,且它本身就已經提供了 GET 方法監聽的內置支持。

首先初始化項目,並安裝依賴:npm init npm i express --save
然後創建 index.js 文件,內容爲:

var express = require('express');
var app = express();
var path = require('path')
var public = path.join(__dirname, 'public')
app.use('/',express.static(public)) //指明運行後,客戶端url的根目錄指向的是服務器的public目錄內。此目錄用來放置靜態文件,html+js+css等。
var users = [
   {"name" : "1"},
   {"name" : "2"},
   {"name" : "3"},
]
app.get('/users', function (req, res) {
  res.end( JSON.stringify(users)); //會監聽對/users的GET請求,如果發現請求到來,就會調用回調函數,並在req、res內傳遞Request對象和Response對象。我們在res對象內把users對象做一個字符串化,然後由res對象傳遞給客戶端。
})
var server = app.listen(8080, function () {
  var host = server.address().address
  var port = server.address().port
  console.log("listening at http://%s:%s", host, port)
})

客戶端訪問代碼:

<!-- 文件名:public/index.html -->
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"></script>
<div id="app">
  {{msg}}
</div>
<script>
var app = new Vue(
  {
      el:'#app',
      data:{
        msg:'hello'
      },
      mounted(){
        this.$http.get('/users').then((response) => {
          var j = JSON.parse(response.body)
          console.log(j.length == 3,j[0].name == '1',j[1].name == '2',j[2].name == '3')
        }, (response) => {
          console.log('error',response)
        });
    }
  })
</script>

啓動服務器:node index.js ➡️ 訪問:localhost:8080
控制檯輸出:

true true true true

打印出來的結果全部爲 true,就表明我們已經完整地取得了 users 對象。


完整的URL訪問

另外幾種請求方法,監聽的做法和我們使用的針對 GET 類的 HTTP 請求方法是類似的。不同之處在於,客戶端可能會傳遞 json 過來到服務器,服務器則需要解析 JSON 對象。此時有一個庫可以幫我們做這件事,它就是 body-parser 庫。

var bodyParser = require('body-parser')
app.use(bodyParser.json())

body-parser 庫的 .json() 作爲插件,插入到 express 內,這樣我們就可以使用:response.body 取得客戶端發來的 json 對象了。因此,安裝 body-parser 是必要的:

npm install body-parser

🌰:

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"></script>

<div id="app">
  {{msg}}
</div>
<script>
  var app = new Vue(
  {
    el:'#app',
    data:{
      msg:'hello'
    },
    mounted(){
       this.a()
       this.b()
       this.c()
       this.d()
       this.e()
    },
    methods:{
      a(){
        this.$http.get('/users').then((response) => {
          var j = JSON.parse(response.body)
          console.log('getall',j.length == 3,j[0].name == '1',j[1].name == '2',j[2].name == '3')
        }, (response) => {
          console.log('error',response)
        });
      },
      b(){
        this.$http.get('/user/0').then((response) => {
          var j = JSON.parse(response.body)
          console.log('getone',j.name == '1')
        }, (response) => {
          console.log('error',response)
        });
      },
      c(){
        this.$http.put('/user/0',{name:'1111'}).then((response) => {
          var j = JSON.parse(response.body)
          console.log('put',j.length == 3,j[0].name == '1111')
        }, (response) => {
          console.log('error',response)
        });
      },
      d(){
          this.$http.post('/user',{name:'4'}).then((response) => {
          var j = JSON.parse(response.body)
          // console.log(j)
          console.log('post',j.length == 4,j[3].name == '4')
        }, (response) => {
          console.log('error',response)
        });
      },
      e(){
        this.$http.delete('/user/2').then((response) => {
          var j = JSON.parse(response.body)
          // console.log(j)
          console.log('delete',j.length == 2)
        }, (response) => {
          console.log('error',response)
        });
      }
    }
  })
</script>

<!-- 最後打印出來的結果全部爲true,就表明我們的代碼和期望是一致的。-->

狀態管理插件

vuex

🌰:一個微小的應用,有一個標籤顯示數字,兩個按鈕分別做數字的加一和減一的操作。

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
  <p>{{count}}
    <button @click="inc">+</button>
    <button @click="dec">-</button>
  </p>
</div>
<script>

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    inc: state => state.count++,
    dec: state => state.count--
  }
})

const app = new Vue({
  el: '#app',
  computed: {
    count () {
        return store.state.count
    }
  },
  methods: {
    inc () {
      store.commit('inc')
    },
    dec () {
        store.commit('dec')
    }
  }
})
</script>
  • 新的代碼添加了對 vuex 腳本的依賴。
  • methods 數組還是這兩個方法;但是方法內的計算邏輯,不再是在函數內進行,而是提交給 store 對象!這是一個新的對象!
  • count 數據也不再是一個 data 函數返回的對象的屬性;而是通過計算字段來返回,並且在計算字段內的代碼也不是自己算的,而是轉發給 store 對象。再一次 store 對象!




🔗:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章