插件
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.js
里 routes:[]
数组内承载的就是被称为路由构造的对象。对象内属性:
🐱 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-link
的 to
属性传一个对象:
<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) => {
// ...
}
}
]
})
🍇 组件内的钩子
使用 beforeRouteEnter
和 beforeRouteLeave
,在路由组件内直接定义路由导航钩子:
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
就是这样一个不错的插件,它封装了 XMLHttpRequest
和 JSONP
,实现异步加载服务端数据。
🌰:由服务器提供 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 对象!
🔗: