vue插件

Vue.js提供了插件机制,可以在全局添加一些功能。它们可以简单到几个方法、属性,也可以很复杂,比如一整套组件库。

注册插件需要一个公开的方法install,它的第一个参数是Vue构造器,第二个参数是一个可选的选项对象。示例如下:

MyPlugin.install = function(Vue,options){
//全局注册组件(指令等功能资源类似)
Vue.component('component-name',{
//组件内容
});
//添加实例方法
Vue.prototype.$Notice = function() {
//逻辑。。。
};
//添加全局混合
Vue.mixin({
mounted:function() {
//逻辑。。
}
})
}

通过Vue.use(MyPlugin)或者

Vue.use(MyPlugin,{

  //参数

})

来使用插件。绝大多数情况下,开发插件主要是通过NPM发布后给别人使用的。在自己的项目中可以直接在入口调用以上的方法。

前端路由

webpack主要使用场景是单页面富应用(SPA),而SPA的核心就是前端路由。什么是路由,通俗的讲就是网址。专业一点就是每次get或post等请求在服务端有一个专门的正则配置列表,然后匹配到具体的一条路径后,分发到不同controller,进行各种操作,最终将html或数据返回给前端,这就完成了一侧IO。

目前绝大多是的网站都是后端路由,也就是多页面的,这样的好处很多,比如可以在服务端渲染好直接返回给浏览器,不用等待前端加载任何js和css就可以直接显示网页内容,对SEO的友好等。缺点也很明显,就是模板由后端累维护或改写html结构,所以html和数据、逻辑混为一谈,维护起来臃肿麻烦。

然后就有了前后端分离的开发模式。后端只提供API来返回数据,前端通过AJAX请求获取数据,在用一定的方式渲染都页面中。这样出现很多前端技术栈,比如jQuery + artTemplate +requirejs + gulp为主的开发模式可谓是万金油了。

在Node.js出现后,这种现象有了改善,html模板可以完全由前端累控制,同步或异步渲染完全由前端*决定,并且由前端维护一套模板。

SPA就是在前后端分离的基础上加上路由。

vue-router的基本用法

新建一个项目router,复制上一章代码并安装完成后,再通过NPM来安装vue-router:

npm install --save vue-router

在main.js里使用Vue.use()加载插件:

import Vue from 'vue';

import VueRouter from 'vue-router';

import App from './app.vue';

Vue.use(VueRouter);

每个页面对应一个组件,也就是对应一个.vue文件。在router目录下创建views目录,用于存放所有页面,然后在views里创建index.vue和about.vue两个文件:

// index.vue

<template>
<div>首页</div>
</template> <script>
export default { }
</script> <style scoped> </style>

// about.vue

<template>
<div>介绍页</div>
</template> <script>
export default { }
</script> <style scoped> </style>

回到main.js完成路由剩余配置,创建一个数组来制定路由匹配列表,每一个路由映射一个组件:

const Routers = [
{
path: '/index',
component: (resolve) => require(['./views/index.vue'],resolve)
},
{
path: '/about',
component: (resolve) => require(['./views/about.vue'],resolve)
}
]

Routers里每一项的path属性就是指定当前匹配的路径 ,component 是映射的组件。上面的写法,webpack会把每个路由都打包为一个js文件,在请求到该页面时,才回去加载这个页面的js,也就是异步实现的懒加载。如果想要一次性加载,可以这样写:

{
path: '/about',
component: require(['./views/index.vue'])
}

使用异步路由后,便一处的每个页面的js都叫做chunk(块),它们命名默认是0.main.js、1.main.js....可以在webpack配置出口里通过设置chunkFilename字段修改chunk命名,例如:

output:{
publicPath:'/dist/',
filename: '[name].js',
chunkFilename: '[name].chunk.js'
}

有了chunk后,在每个页面(.vue文件)里写的样式也需要配置后才会打包进main.css,否则仍然会通过javascript动态创建<style>标签写入,配置插件:

//webpack.config.js

plugins: [
new ExtractTextPlugin({
filename:'[name].css',
allChunk:true
})
]

然后继续在main.js里完成配置和路由实例:

const RouterConfig = {
//使用html5的history路由模式
mode: 'history',
routes: Routers
}; const router = new VueRouter(RouterConfig); //创建Vue实例
new Vue({
el: "#app",
router: router,
render: h => h(App)
});

在RouterConfig里,设置mode为history会开启HTML5的history路由模式,通过"/"设置路径。如果不配置mode,就会使用"#"来设置路径。开启history路由,在生产环境时服务端必须进行配置,将所有路由都指向同一个html。

webpack-dev-server也要配置下来支持history路由,在package.json中修改dev命令:

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js",
"build": "webpack --mode production"
},

增加了-history-api-fallback,所有的路由都会指向index.html。

配置好了这些,最后在app.vue添加一个<router-view>来挂载所有路由组件:

<template>
<div>
<router-view></router-view>
</div>
</template> <script> export default { }
</script> <style scoped>
div {
color: #f60;
font-size: 24px;
}
</style>

运行网页时,<router-view>会根据当前路由动态渲染不同的页面组件。网页中的一些公共部分,比如顶部的导航栏,侧边导航栏,底部的版权信息,这些也可以直接写在app.vue里,与<router-view>同级。路由切换时,切换的是<router-view>挂载的组件,其他内容并不会变化。

运行npm run dev启动服务,然后访问127.0.0.1:8080/index和127.0.0.1/about就可以访问这两个页面了。

在列表里,可以在最后新加一项,当访问路径不存在时,重定向到首页:

{
path: '*',
redirect: '/index'
}

路由列表的path也可以带参数,比如“个人主页”的场景,路由的一部分时固定的,一部分时动态的:/user/123456,其中用户id"123456"就是动态的,但是路由到同一个页面,这个页面需要获取到这个id,请求对应的数据,在路由里可以这样配置参数:

{
path: '/user/:id',
component: (resolve) => require(['./views/user.vue'],resolve)
},

在views目录下新建user.vue文件

<template>
<div>{{$route.params.id}}</div>
</template> <script>
export default {
mounted () {
console.log(this.$route.params.id);
}
}
</script> <style scoped> </style>

这里的this.$router可以访问到当前路由的很多信息。

跳转

vue-router有两种跳转页面的方法,第一种是内置的<router-link>组件,它会被焕然成一个a标签。

<template>
<div>
<h1>首页</h1>
<router-link to="/about">跳转到about</router-link>
</div>
</template>

用法和一般的组件一样,to是一个prop,指定需要跳转的路径,也可用v-bind来绑定动态设置。在html5的histtory模式下会拦截点击,避免浏览器重新加载页面。<router-link>还有其他一些prop,常用的有:

tag

tag可以指定渲染成什么标签,比如<router-link to='/about' tag='li'></router-link>渲染的结果就是<li>

replace

使用replace不会留下history记录,所以导航后不能后退返回上一个页面,<router-link to='/about' replace>

active-class

当router-link 对应的路由匹配成功时,会自动给当前元素设置一个名为router-link-active的clas,设置prop:active-class可以修改默认的名称。在做类似的导航栏时,可以使用该功能高亮显示当前页面对应的导航菜单项,但是一个不修改active-class

如果跳转需要在javascript里进行,可以使用第二种方法

<template>
<div>
<h1>介绍页</h1>
<button @click="handleRouter">跳转到user</button>
</div>
</template> <script>
export default { methods:{
handleRouter() {
this.$router.push('/user/123')
} }
}
</script> <style scoped> </style>

添加点击事件,this.$router.push就可以。

$router还有一些其他的方法:

replace

this.$router.replace('/user/123'),替换当前的history记录。

go

类似前进和后退多少步。

this.$router.go(-1);

高级用法

如何修改网页标题?

vue-router提供了导航钩子beforeEach和afterEach,它们会在路由即将改变前和改变和触发,所以设置标题可以在beforeEach钩子完成。

const Routers = [
{
path: '/index',
meta:{
title:'首页'
},
component: (resolve) => require(['./views/index.vue'],resolve)
},
{
path: '/about',
meta:{
title:'关于'
},
component: (resolve) => require(['./views/about.vue'],resolve)
},
{
path: '/user/:id',
meta:{
title:'个人主页'
},
component: (resolve) => require(['./views/user.vue'],resolve)
},
{
path: '*',
redirect: '/index'
}
]; const RouterConfig = {
//使用html5的history路由模式
mode: 'history',
routes: Routers
}; const router = new VueRouter(RouterConfig);
router.beforeEach((to, from, next) => {
window.document.title = to.meta.title;
next();
console.log('before')
});

导航钩子有三个参数:

to   即将要进入的目标路由对象。

form 当前导航即将要离开的路由对象。

next  调用该方法后,才能进入下一个钩子。

路由列表的meta字段可以自定义一些信息,比如将title写入了meta来统一维护,beforeEach钩子可以从路由对象to里获取meta信息,从而改变标题。

有了这两个钩子,还能做很多来提升用户体验。比如页面较长,滚动到某个位置,在跳到另一个页面,滚动条默认实在上一个页面停留的位置,而最好是能返回顶部,通过钩子afterEach就可以实现

router.afterEach((to,from,next) => {
window.scroll(0,0)
})

类似的需求还有从一个页面过度到另一个页面时,可以吹安一个全局的loading动画。到新页面加载完成后再结束动画。

next()方法还可以设置参数。

某些页面需要检验是否登录,登录了就可以访问,不然就打登录页面。

router.beforeEach((to,form,next) => {
if(window.localStorage.getItem('token')){
next();
}else{
next('/login');
}
})

状态管理与Vuex

非父子组件通信时,使用了bus的一个方法,用来触发和接收事件,进一步起到了通信的作用。Vuex所解决的问题与bus类似,作为一个插件来使用,可以更好的维护整个项目的组件状态。

Vuex的基本用法

npm install --save vuex

用法和vue-router类似,在main.js里,通过Vue.use()使用Vuex。

import Vuex from 'vuex'

Vue.use('Vuex')

const store = new Vuex.Store({
//vuex配置
})
new Vue({
el: "#app",
router: router,
store:store,
render: h => h(App)
});

仓库store包含了应用的数据和操作过程。Vuex里的数据都是响应式的,任何组件使用同一store的数据时,只要store的数据变化,对应的组件也会立即更新。

数据保存在Vuex选项的state字段内,实现一个计数器,定义一个count,初始值为0:

const store = new Vuex.Store({
state: {
count:0
},
})

在任何组件内,可以直接通过$store.state.count读取:

<template>
<div>
<h1>首页</h1>
{{count}}
</div>
</template>
<script>
export default {
computed: {
count () {
return this.$store.state.count;
}
}
}

mutations是Vuex的第二个选项,用来直接修改state里的数据。我们给计数器增加2个mutations,用来加1或者减1

const store = new Vuex.Store({
state: {
count:0
},
mutations: {
increment (state) {
state.count ++;
},
decrease (state) {
state.count --;
}
},
})

然后在组件内,通过this.$store.commit方法来执行mutations。在index.vue中添加按钮用于加和减:

<template>
<div>
<h1>首页</h1>
{{ count }}
<button @click="handleIncrement">+1</button>
<button @click="handleDecrease">-1</button>
</div>
</template>
<script>
export default {
computed:{
count () {
return this.$store.state.count;
},
},
methods: {
handleIncrement () {
this.$store.commit('increment');
},
handleDecrease () {
this.$store.commit('decrease');
},
}
}
</script>
<style scoped> </style>

mutations还可以接受第二个参数,可以是数字、字符串或对象等类型。

increment (state, n=1) {
state.count += n;
},

然后在组件里面

handleIncrement () {
this.$store.commit('increment', 5);
},

高级用法

Vuex还有其他3个选项可以使用:getters、actions、modules。

const store = new Vuex.Store({
state: {
count:0
},
getters: {
filteredList: state => {
return state.list.filter(item => item < 10);
},
listCount: (state, getters) => {
return getters.filteredList.length;
}
},
}) //getters可以写一些通用的方法在里面,方便各个组件使用
<template>
<div>
<h1>首页</h1>
<div>{{ list }}</div>
<div>{{listCount}}</div>
</div>
</template> <script>
export default {
computed:{
count () {
return this.$store.state.count;
},
list () {
return this.$store.getters.filteredList;
},
listCount () {
return this.$store.getters.listCount;
}
},
}
</script> <style scoped> </style>

getters也可依赖其他的getter,把getter作为第二个参数。

mutationi不应该异步操作数据,所以有了actions选项。action与mutation很像,不同的是action里面提交的是mutation。并且可以异步操作业务逻辑。

action在组件内通过$store.dispatch触发,主要在异步的时候使用。

    actions: {
asyncIncrement (context) {
return new Promise(resolve => {
setTimeout(() => {
context.commit('increment');
resolve();
},1000)
})
}
}
            handleActionIncrement () {
this.$store.dispatch('increment').then(() => {
console.log(this.$store.state.count);
})
}

mutations、actions看起来很相似,Vuex很像是一种与开发者的约定,涉及改变数据的使用mutations,存在业务逻辑的就用actions。

最后一个选项是modules,它用来将store分给到不同的模块中。当项目足够大时,store里的state、getters、mutations、actions会非常多,都放在main.js不是很好,使用modules可以将它们写到不同的文件中,每个module拥有自己的state、getters、mutatios、actions,而且可以多层嵌套。

const moduleA = {

  state:{....},

  mutations:{.....},

  actions:{....},

  getters:{.....}

}

const moduleB = {

  state:{....},

  mutations:{.....},

  actions:{....},

  getters:{.....}

}

const store = new Vuex.Store({

  modules: {

    a: moduleA,

    b: moduleB

  }

})

store.state.a  //moduleA的状态

store.state.b  //moduleB的状态

const moduleA = {
state: {
count:0
},
getters: {
sumCount (state, getters, rootState) {
return state.count + rootState.count;
}
}
}

module的mutation和getter接收的第一个参数state是当前模块的状态。在actions和getters中,还可以接收一个参数rootState,来访问根节点的状态。

上一篇:JavaScript高级程序设计学习(五)之对象


下一篇:【PAT】1103 Integer Factorization(30 分)