都说Vue2简单,上手容易,但小马过河,自己试了才晓得,除了​​ES6​​语法和webpack的配置让你感到陌生,重要的是思路的变换,以前随便拿全局变量和修改dom的锤子不能用了,变换到关注数据本身。vue的官方文档还是不错的,由浅到深,但是当你用vue-cli构建一个工程的时候,发现官方文档还是不够用,得参考git上开源的项目并去学习es6。而且vue的全家桶(vue-cli,vue-router,vue-resource,vuex)还是都要上的。

1.vue-cli

这个构建工具大大降低了webpack的使用难度,支持热更新,有webpack-dev-server的支持,相当于启动了一个请求服务器,给你搭建了一个测试环境,你只关注开发就好。

Vue 插件写法_github

# 全局安装 vue-cli $ npm install --global vue-cli # 创建一个基于 webpack 模板的新项目 $ vue init webpack my-project # 安装依赖,走你 $ cd my-project $ npm install $ npm run dev

Vue 插件写法_github

热更新的机制是检测文件的变化并用websocket通知客户端做出相应的更新。详细的可以移步:​​【webpack】-- 模块热替换​

2.vue-router

vue的路由还是很方便的,比ag1的时候要方便很多。这种方便体现在三个方面:

1个是路由和页面(组件)对应:

Vue 插件写法_github

import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import Chat from '@/components/Chat' import Firends from '@/components/Firends' import Logon from '@/components/Logon'  Vue.use(Router) let router=new Router({   routes: [     {       path: '/home',       name: 'Home',       component: Home     },      { path: '/', redirect: '/home' },//重定向     {       path: '/chat',       name: 'Chat',       component: Chat     },     {       path: '/firends',       name: 'Firends',       component: Firends     },     {       path: '/logon',       name: 'Logon',       component: Logon     }   ] });

Vue 插件写法_github

常人的思路就是这样,而ag1中还需要带上控制器(vue中没有这个概念,取而代之的是关注组件就行),这样用起来更简单。MVC模式中,需要指到controller下的action,如果导航分类多,对应策略是嵌套路由。

2个是可以具体到元素了:


<router-link    class="footer-item"  exact  to="home">首页</router-link>


这个to后面的home(忽略大小写)就是上面定义的路由名称。这样就很方便了。类似于Asp.net MVC 的路由可以用名称来渲染出路径,而不用什么时候都要输入路径。

3个是事件拦截:

如果我们要做验证,最好不过的就是在用户到达页面之前进行验证:

Vue 插件写法_github

router.beforeEach((to, from, next) => {   //todo 以后增加不需要验证的地址判断   if(to.path!=="/logon"&&!store.state.userInfo.Account){      next({ path: '/logon' })      return;   }else{    next();   }  })

Vue 插件写法_github

比如在beforeEach中进行处理。它还有很多功能,就不一一枚举了,官方文档:http://router.vuejs.org/zh-cn/

3.组件中使用组件

看了几个移动UI库,一开始奇怪怎么没有footer组件,现在明白,路由都这么方便了,第三方ui没必要封装了footer,也不方便封装(因为依赖路由)。于是导航可能是你需要自己来写的第一个组件。

比如定义了个Footer.vue Vue 插件写法_github

<template>     <footer class="footer">      <router-link    class="footer-item"  exact  to="home">      <span class="icon icon-home">      </span>      <label>首页</label>      </router-link>      <router-link    class="footer-item"   to="statistics">       <span class="icon icon-stat">       </span>       <label>统计</label>      </router-link>        <router-link    class="footer-item"   to="more">       <span class="icon icon-more">       </span>       <label>更多</label>      </router-link>   </footer> </template>

<script>

export default {

name: 'VFooter'

}

</script>

Vue 插件写法_github

只在App.Vue中就需要引入

Vue 插件写法_github

import VFooter from './VFooter'   export default { name: 'app', data () { return { msg: 'this is home' } }, components:{VFooter} }

Vue 插件写法_github

然后在App.Vue中就可以使用了


<VFooter></VFooter>

如果是要注册到全局 就用先在main.js中引入

import Footer from './components/VFooter' // Vue.component('VFooter', Footer)//写在构造函数之前


到了这一步就可以愉快的组装自己的页面了。如果需要给页面加上滑动效果,可以加一个transition(位于app.vue):


<transition  name="slide-in" mode="out-in">     <router-view></router-view>    </transition>

Vue 插件写法_github

.slide-out-enter-active, .slide-out-leave-active, .slide-in-enter-active, .slide-in-leave-active {   will-change: transform;   transition: all 400ms;   position: absolute; } .slide-out-enter {   opacity: 0;    transform: translateX(-100%);    } .slide-out-leave-active {   opacity: 0;    transform: translateX(100%);    } .slide-in-enter {   opacity: 0;    transform: translateX(100%);  } .slide-in-leave-active {   opacity: 0;    transform: translateX(-100%);

Vue 插件写法_github

如何做到左右切换呢?

4.vue-resource


页面敲好了,得能发请求。​​https://github.com/pagekit/vue-resource​​ 而前端如果不是自己写的接口首先关心的还是怎么设置代理,这个位于config/index.js下面的proxyTable。

Vue 插件写法_github

proxyTable: {     '/api':      {            target: 'http://11.111.249.12:11',            changeOrigin: true,            pathRewrite:            {               '^/api': ''             }     },

Vue 插件写法_github

跨域的设置不能少了changeOrigin。另外需要注意最后的这个'^/api': ''。 要注意你选择的"/api"是不是原路径中的一部分,不然容易出错。

然后还有一部分就是设置请求头(main.js):

Vue 插件写法_github

import VueResource from 'vue-resource' Vue.use(VueResource); Vue.http.interceptors.push(function(request, next) {// modify method   //request.method = 'POST';    // modify headers          request.headers.set('token',“token”);// continue to next interceptor   next(); });

Vue 插件写法_github

5.vuex

全家桶里面一开始最让我懵逼的就是这个vuex。这是个什么鬼,为什么需要这个。没有玩过React,不知道什么是状态管理。当你想用一个全局变量的时候,你发现之前的招都不灵了。比如设置一个登录状态

Vue 插件写法_github

let login=(name,pwd,success,fail)=>{
Vue.http.post(loginUrl,{account:name,password:pwd}).then(res=>{
//....
window.hasLogin=true;
},res=>{ fire(fail,"请求失败"); }) }

Vue 插件写法_github

然后再首页显示出来:


<div @click="toggle">{{hasLogin}}</div>

Vue 插件写法_github

data () {     return {       hasLogin:window.hasLogin     }   },    methods:{      toggle(){        window.hasLogin=!window.hasLogin;        console.log("clicked",window.hasLogin)      }    },

Vue 插件写法_github

你发现你登录之后确实显示了true,但怎么点击也不会切换false或true。

Vue 插件写法_构造函数_21

需要再赋值一次:


this.hasLogin=window.hasLogin;


为什么呢?因为你自己定义的变量,根本不属于vue的model。也就是没有处理过geter和seter,虽然变量的值是变化了,但仍然无法改变界面上的状态。所以你需要一个状态管理,按照一定的机制把你的变量变换成vue内部的模型。这个东西就是vuex。因为约定比较多,略显复杂点,但是耐心看一下还是很容易接受的。它主要分四个部分,state,getters,mutations,actions。先定义一个user.js如下:

state就是我们放共享变量的地方。比如下面的userInfo.

Vue 插件写法_github

import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex);  const userStore=new Vuex.Store({    state:{        userInfo:{            userName:""        }    },    getters:{        getUserInfo(state){            return state.userInfo;        }    },    mutations:{        setUserInfo(state,userInfo){            state.userInfo=userInfo;        }    },    actions:{        setUserInfo({commit}){            commit('setUserInfo');        }    }  })   export default userStore;

Vue 插件写法_github

而getters顾名思义就是获取接口,mutations(突变)这个词有点唬人,其实就是setters嘛。里面的方法自带state参数。而actions就是mutations的异步方法。然后再main.js中引用一下:

Vue 插件写法_github

import store from './store/user'; //...  new Vue({   el: '#app',   router,   store,   template: '<App/>',   components: { App } })

Vue 插件写法_github

然后我们在设置或获取的时候就可以使用指定的方法:


import store from '@/store/user';
//...
store.commit('setUserInfo',user)


可以直接通过store.state获取变量,也可以通过getters接口:


computed:{    ...mapGetters({username:'getUserName'})   },


这三个点是es6中的扩展运算符。将一个数组转为用逗号分隔的参数序列。

当然这些状态刷新之后就没有了,如果想要暂存下来,可以放到sessionStorage中:


if (window.sessionStorage.user) {     var json=JSON.parse(window.sessionStorage.user);     store.commit('setSessionUser',json) }


当然要在muations中放进去

Vue 插件写法_github

//个人信息
setUserInfo(state,user)
{
state.userInfo=user;
window.sessionStorage.user=JSON.stringify(state.userInfo);
},

Vue 插件写法_github

官方文档: ​​https://vuex.vuejs.org/zh-cn/​

小结:到这儿基本五脏俱全了,可以愉快的玩耍了。还有一个是对生命周期的理解。组件实例每次加载都会执行一遍,所以在生命周期的方法中做请求和绑定的时候要清楚这一点。

其他:

Demo ​​https://github.com/CommanderXL/x-blog​​​ ​​https://github.com/bailicangdu/vue2-elm​​​ ​​https://github.com/shinygang/Vue-cnodejs​​​ UI: ​​http://element.eleme.io/#/zh-CN​​​ element ​​http://mint-ui.github.io/docs/#/zh-cn2​​​ Mint UI ​​https://github.com/airyland/vux​​ vux

学习了Vue全家桶和一些UI基本够用了,但是用元素的方式使用组件还是不够灵活,比如我们需要通过js代码直接调用组件,而不是每次在页面上通过属性去控制组件的表现。下面讲一下如何定义动态组件。

Vue.extend

思路就是拿到组件的构造函数,这样我们就可以new了。而Vue.extend可以做到:​​https://cn.vuejs.org/v2/api/#Vue-extend​

Vue 插件写法_github

// 创建构造器 var Profile = Vue.extend({   template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',   data: function () {     return {       firstName: 'Walter',       lastName: 'White',       alias: 'Heisenberg'     }   } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point')

Vue 插件写法_github

官方提供了这个示例,我们进行一下改造,做一个简单的消息提示框。

动态组件实现

创建一个vue文件。widgets/alert/src/main.vue

Vue 插件写法_github

<template>  <transition name="el-message-fade"> <div  v-show="visible" class="my-msg">{{message}}</div>   </transition> </template>  <script  >     export default{         data(){            return{                message:'',                visible:true            }          },         methods:{             close(){                 setTimeout(()=>{                      this.visible = false;                 },2000)             },         },         mounted() {         this.close();       }     } </script>

Vue 插件写法_github

这是我们组件的构成。如果是第一节中,我们可以把他放到components对象中就可以用了,但是这儿我们要通过构造函数去创建它。再创建一个widgets/alert/src/main.js

Vue 插件写法_github

import Vue from 'vue'; let MyMsgConstructor = Vue.extend(require('./main.vue'));  let instance;  var MyMsg=function(msg){  instance= new MyMsgConstructor({      data:{          message:msg }})  //如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。 instance.$mount();    document.body.appendChild(instance.$el)  return instance; }   export default MyMsg;

Vue 插件写法_github

require('./main.vue')返回的是一个组件初始对象,对应Vue.extend( options )中的options,这个地方等价于下面的代码:


import alert from './main.vue' let MyMsgConstructor = Vue.extend(alert);


而MyMsgConstructor如下。

Vue 插件写法_ico_34

参考源码中的this._init,会对参数进行合并,再按照生命周期运行:

Vue 插件写法_github

Vue.prototype._init = function (options) {     ...// merge options     if (options && options._isComponent) {       // optimize internal component instantiation       // since dynamic options merging is pretty slow, and none of the       // internal component options needs special treatment.       initInternalComponent(vm, options);     } else {       vm.$options = mergeOptions(         resolveConstructorOptions(vm.constructor),         options || {},         vm       );     } // expose real self     vm._self = vm;     initLifecycle(vm);     initEvents(vm);     initRender(vm);     callHook(vm, 'beforeCreate');     initInjections(vm); // resolve injections before data/props     initState(vm);     initProvide(vm); // resolve provide after data/props     callHook(vm, 'created');      ...      if (vm.$options.el) {       vm.$mount(vm.$options.el);     }   };

Vue 插件写法_github

而调用$mount()是为了获得一个挂载实例。这个示例就是instance.$el。

Vue 插件写法_ide_37

可以在构造方法中传入el对象(注意上面源码中的mark部分,也是进行了挂载vm.$mount(vm.$options.el),但是如果你没有传入el,new之后不会有$el对象的,就需要手动调用$mount()。这个方法可以直接传入元素id。


instance= new MessageConstructor({      el:".leftlist",      data:{          message:msg }})


这个el不能直接写在vue文件中,会报错。接下来我们可以简单粗暴的将其设置为Vue对象。

调用

在main.js引入我们的组件:

Vue 插件写法_github

//.. import VueResource from 'vue-resource' import MyMsg from './widgets/alert/src/main.js'; //.. //Vue.component("MyMsg", MyMsg); Vue.prototype.$mymsg = MyMsg;

Vue 插件写法_github

然后在页面上测试一下:

Vue 插件写法_github

<el-button type="primary" @click='test'>主要按钮</el-button> //..

 methods:{

  test(){

  this.$mymsg("hello vue");

  }

 }

Vue 插件写法_github

Vue 插件写法_json_42

这样就实现了基本的传参。最好是在close方法中移除元素:

Vue 插件写法_github

close(){     setTimeout(()=>{        this.visible = false;        this.$el.parentNode.removeChild(this.$el);       },2000)    },

Vue 插件写法_github

回调处理

回调和传参大同小异,可以直接在构造函数中传入。先修改下main.vue中的close方法:

Vue 插件写法_github

export default{         data(){            return{                message:'',                visible:true            }          },         methods:{             close(){                 setTimeout(()=>{                      this.visible = false;                       this.$el.parentNode.removeChild(this.$el);                  if (typeof this.onClose === 'function') {                  this.onClose(this);                 }                 },2000)             },         },         mounted() {         this.close();       }     }

Vue 插件写法_github

如果存在onClose方法就执行这个回调。而在初始状态并没有这个方法。然后在main.js中可以传入

Vue 插件写法_github

var MyMsg=function(msg,callback){   instance= new MyMsgConstructor({      data:{          message:msg     },     methods:{         onClose:callback     }  })

Vue 插件写法_github

这里的参数和原始参数是合并的关系,而不是覆盖。这个时候再调用的地方修改下,就可以执行回调了。


test(){       this.$mymsg("hello vue",()=>{         console.log("closed..")       });     },


你可以直接重写close方法,但这样不推荐,因为可能搞乱之前的逻辑且可能存在重复的编码。现在就灵活多了。

统一管理

如果随着自定义动态组件的增加,在main.js中逐个添加就显得很繁琐。所以这里我们可以让widgets提供一个统一的出口,日后也方便复用。在widgets下新建一个index.js

Vue 插件写法_github

import MyMsg from './alert/src/main.js';  const components = [MyMsg];  let install =function(Vue){   components.map(component => {     Vue.component(component.name, component);   });   Vue.prototype.$mymsg = MyMsg;  }  if (typeof window !== 'undefined' && window.Vue) {   install(window.Vue); };  export default {     install }

Vue 插件写法_github

在这里将所有自定义的组件通过Vue.component注册。最后export一个install方法就可以了。因为接下来要使用​​Vue.use​​。


安装 Vue.js 插件。如果插件是一个对象,必须提供 ​​install​​ 方法。如果插件是一个函数,它会被作为 install 方法。install 方法将被作为 Vue 的参数调用。


也就是把所有的组件当插件提供:在main.js中加入下面的代码即可。

Vue 插件写法_github

... import VueResource from 'vue-resource' import Widgets from './Widgets/index.js'  ... Vue.use(Widgets)

Vue 插件写法_github

这样就很简洁了。

小结: 通过Vue.extend和Vue.use的使用,我们自定义的组件更具有灵活性,而且结构很简明,基于此我们可以构建自己的UI库。