插件
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 對象!
🔗: