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/vue@2.0.6/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/vue@2.0.6/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 对象!




🔗:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值