5

Vue2.x 总结

Vue 是一套用于构建用户界面的渐进式框架

也意味着,既可以把VUE作为该应用的一部分嵌入到一个现成的服务端应用,或者在前后端分离的应用中,利用Vue 的核心库及其生态系统,把更多的逻辑放在前端来实现。

A Progressive Framework

渐进式框架

与Vue相比,React学习曲线陡峭,在学习React之前,需要了解JSX和ES2015,当然入门后,发现还要学习React全家桶。而Vue就可以在简单阅读了文档后,开始构建应用程序。

这就要得益于Vue主张的 渐进式
可以简单看下官方给出这张图:

a progressive framework

可以看出来,主要是介绍了Vue设计思想,就是框架做分层设计,每层都可选,可以单独引入,为不同的业务需求制定灵活的方案。主张最少,不会多做职责以外的事。

Vue作者尤雨溪的观点,Vue设计上包括的解决方案很多,但是使用者完全不需要一上手,就把所有东西全都用上,因为完全没有必要,一般都是根据项目的复杂度,在核心的基础上任意选用其他的部件,不一定要全部整合在一起。

这样渐进式的解决方案,使得学习成本大大减少了。

声明式渲染

也就是说,DOM状态只是数据状态的一个映射,基本所有的框架都已经认同了这个看法,Vue也是主张 数据驱动状态

说到这里,基本都会提到现在主流的MVVM的模式。
mvvm

采用了双向数据绑定的思想,基本可以分为三层:

  • M(Model,模型层),负责业务数据相关。
  • V(View,视图层),视图相关,展示给用户的交互界面,同时捕获用户的操作
  • VM(ViewModel, V与M连接的桥梁,也可以看做控制器)。

基于这个思想,Vue从一开始就利用ViewModel与view,model进行交互
Vue mvvm
ViewModel是Vue.js的核心,它是一个Vue实例,作用在某个HTML元素上,一般都是指定 id= app的元素,图中 的DOM listenersData Bindings可以看做两个工具,它们是实现双向数据绑定的关键。

从用户(View)角度看,DOM Liisteners利用在相应的元素上添加事件绑定,捕获用户的点击,滑动等手势动作,在事件流中改变对应的Model。比如 常用的 v-model 指令,就是捕获表单元素的inputchange等事件,改变相应的绑定值。

从Model方向看,Data Bindings则将操作的数据变化,反应到view上。比如通过ajax 从后台获取的数据,可以刷新数据列表,反应到用户界面。这也是实现双向数据绑定的关键。

Vue2中是通过Object.definedProperty方法中定义的getters和 setters构造器来实现数据响应的。可以简化下源码中的实现:

   Object.defineProperty(obj, key, {
         enumerable: true,
         configurable: true,
         get: function reactiveGetter () {
            return value
         },
         set: function reactiveSetter (newVal) {
             var value = getter ? getter.call(obj) : val;
             /* eslint-disable no-self-compare */
             if (newVal === value || (newVal !== newVal && value !== value)) {
               return
             }
             /* eslint-enable no-self-compare */
             if ("development" !== 'production' && customSetter) {
               customSetter();
             }
             if (setter) {
               setter.call(obj, newVal);
             } else {
               val = newVal;
             }
             childOb = !shallow && observe(newVal);
             dep.notify();
           }
       });
   }

通过这种方法定义对象obj上的某个属性,每次获取属性值的时候就,会主动触发get对应的回调函数,然后给该属性赋值时,就会触发里面的set对应的回调函数,在set回调函数里面,加入了dep.notify()方法,然后可以看下这个方法

notify () {
 // stabilize the subscriber list first
 const subs = this.subs.slice()
 for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update()
 }
}

里面的定义的常量subs每次深拷贝this.subs数组,数组里面保存的就是所有的subscriber订阅者,对应的发布者就是obj里面对应的属性,或者说是Vue中的data值。通知所有的订阅者,数据更新了。原生js实现发布订阅模式(publish/Subscribe),可以参考这里

常用基础语法

hello world

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>hello world</title>
      <script src="https://gw.alipayobjects.com/as/g/h5-lib/vue/2.4.4/vue.min.js"></script>
    </head>
    <body>
      <div id="app">
        {{message}}
      </div>
      <script>
        var app = new Vue({
          el:"#app",
          data:{
            message:'hello vue'
          }
        })
      </script>
    </body>
  </html>

这样就简单创建了一个Vue 应用,数据message 和DOM页面产生了关联,类似html模板引擎,把相应的数据渲染到页面中。

指令

指令 (Directives) 是带有 v- 前缀的特殊属性,这些特殊属性可以响应式的作用域DOM,

  • v-if 接受Boolean 类型,比如: <p v-if = "seen">现在你看到我了</p> ,通过seen的真假来插入/移除< p>元素。 这里判断的时候使用 === 全等,seen = “false” 的时候,也会插入
  • v-bind,响应式的更新HTM属性。完整形式<a v-bind:href="url">...</a> 。 缩写形式<a :href="url">...</a>。 常用于改变dom的style, class ,href ,src 等属性。 动态绑定的属性可以写成 :属性名="属性值"
  • v-on,绑定点击事件,比如 完整形式<a v-on:click="doSomething">...</a>,简写形式 <a @click="doSomething">...</a>, doSomething对应的指向methods里面定义的函数。 注意,除非在需要传递参数的时候,写成 @click = "doSomething($event,args1,args2)",$event代表事件对象,args代表自定义参数

style or class

v-bind用于classstyle时,Vue.js做了专门的增强,表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定HTML Class

  • 直接赋值。

    < div :class="className"> </div>
    data:{
       className:"div-class"
    }

    结果:

       <div class="div-class"></div>
  • 对象语法。

       < div class="static" :class="{active:isActive,'text-danger':hasError}" /></div>
    
       data: {
         isActive:true,
         hasError:false,
       }

    结果:

     <div class="static active" ></div>
    
  • 数组语法,

      <div :class="['one',bTwo?'two':'three']" </div>
    
      data:{
         bTwo:true
      }
    
      <style>
         .one{}
         .two{}

    结果:

       <div class='one two'></div>

绑定内联样式

  • 对象语法

         <div :style = "{color:activeColor,fontSize:fontSize+'px'}"></div>
         data: {
           activeColor: 'red',
           fontSize: 30
         }

    结果:

          <div style="color:red:font-size:30px;"></div>
  • 数组语法

      <div v-bind:style="[baseStyles, overridingStyles]"></div>
    
      data:{
        baseStyles:{
          color:'red'
        },
        overridingStyles:{
          fontSize:'30px'
        }
    
      }
    

    结果:

        <div style="color:red:font-size:30px;"></div>

条件渲染

  • v-if

    同样也是一个指令,添加到一个元素上,对应利用 === 全等判断绑定的值truefalse来决定是否渲染里面的节点。
    可以与它一起使用的指令有 v-else ,v-else-if,v-else 元素必须紧跟在都有v-if或者v-else-if的元素后面

      <div v-if= "num===0">
       0
       </div>
       <div v-else-if ="num ===1">
       1
       </div>
       <div v-else>
          not 0/1
       </div>
    
       data:{
         num:3
       }

    结果:

    <div>
        not 0/1
    </div>
  • v-show

    根据条件展示元素的选项,简单的切换元素的内联样式display

    适用场景:

    • 页面复用的(modal)弹出窗,利用v-show,控制显示或者隐藏。参考这里
    • tab页面切换的时候,不同页面不相互影响,利用v-show不会销毁元素,参考这里

列表渲染

  • v-for 把一个数组对应为一组元素,推荐给每个列表,添加唯一标识的key

      <div v-for="(item,idx) in items" :key="idx">
         {{idx}} --- {{item.product}}
      </div>
    
      data:{
        items:[
         {product:"foo"},
         {product:"bar"}
        ]
      }

    结果:

    <div>
          0 --- foo
    </div>
    <div>
        1 --- bar
    </div>
  • 数组更新检测

    包含一组观察数组变异方法,用来触发试图更新:
    push(),pop(),shift(),unshift(),splice(),sort(),reverse()

    利用索引给数组赋值或者手动修改数组的长度,都不会被检测到更新
    替代方案:

     Vue.set(example1.items,indexOfItem,newValue)
     // 或者
     example1.items.splice(indexOfItem,1,newValue)
    
     // 改变数组的长度:
     example1.items.splice(newLength)

    对于对象中的属性添加或删除,也可以使用Vue.set方法,或者在定义的Vue实例内部,使用this.$set,this.$set只是全局Vue.set的别名

     Vue.set(object,key,value)

v-model 表单绑定

使用v-model在表单input<textarea>元素上创建双向数据绑定。

 <input v-model = "message" placeholer= "edit me">
 <p>Message is {{message}}</p>

这样input输入框中的值就与P标签中的内容绑定了,同样也适用textareacheckbox,
radio,select等表单。

实质上,v-model只是语法糖。

  <input v-model = "something"

对应的完整形式:

    <input
       v-bind:value="something"
       v-on:input="something = $event.target.value">

表单数组校验。
利用修饰符 .number进行数字校验,是最实用的方法,在v-model上添加number修饰符。

  <input v-model.number="age" type= "number" >

组件通信

组件可以用来扩展HTML,封装可重用的代码,所有的组件都是Vue的实例。

命名: 建议遵循W3C规则(小写,并且包含一个短杆)

组件组合:使用中最常见的是形成父子组件的关系,组件A在它的模板中使用了组件B,那么他们之间就需要通信。组件间通信的关系可以用下面的图示表明:
props-events

概括为: prop 向下传递,事件向上传递。

  • 利用Prop 传递数据,同时借助.sync,进行双向数据通信

    父组件的数据要通过Prop才能下发到子组件中。
    prop 属性命名:在使用camelCase(驼峰式命名)的prop需要装换成对应的kebab-case(短横线分隔式命名)。同时,也可以绑定动态的Prop传递给子组件。

    然后,子组件中prop值改变,是无法反应到父组件中的。在Vue1.x中使用.sync修饰符可以提供双向绑定,但是违背了单向数据流的思想,在2.0中就移除了,但在2.3.0中作为一种语法糖的形式引入了

      Vue.component('child',{
        props:['myMessage'],
        template:'<span @click="handleClick">{{myMessage}}</span>',
       methods:{
          handleClick:function(){
            this.$emit("update:myMessage","message from child")
          }
       }
      })
    
      <!--  在HTML 中使用时   .sync的语法糖  -->
      <child :my-message.sync="parentMsg"></child>
    

    点击子组件中的span,就可以改变父组件中prop绑定的parentMsg值。.sync语法也会被扩展成为

      <child :my-message="parentMsg" @update:myMessage = "val => parentMsg = val"

    里面 @update:myMessage 就是绑定了自定义事件,回过来看下上面父子通讯的规则 prop向下传递,事件向上传递,也非常符合。

  • 非父子组件通信

    官方推荐使用空的Vue实例作事件总线

          var bus = new Vue();
    
        // 在A 组件中触发了事件
         bus.$emit("change",1);
    
        // 在B 组件中监听事件
        bus.$on('change',function(id){})

状态管理

组件通信变得复杂时,就要考虑使用全局状态管理,Vue也提供了vuex状态管理库。

Vuex 是一个专门为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

当然,使用Vuex并不是首选,只有在构建中大型单页面应用时,考虑到全局的状态管理,自然就会想到Vuex。
下面这张图,表示状态管理“单向数据流”的理念
vuex

核心概念包括(简单计数器为例):

  • state: 作为单一状态树,唯一的数据源,并且每个应用仅仅包含一个store实例,一般通过计算属性获取某个状态。

    state:{
      count:0
    },
  • getter: 相当于store的计算属性,数据源发生变化时,返回经过处理后的值,

    
    getters:{
       getState:state=>{
         return state.count
       }
    },
    
  • mutation: 类似于事件,对应的回调函数到状态进行处理,必须通过store.commit的方式手动触发

    mutations:{
      increment:state => state.count++,
      decrement:state => state.count--
    },
    
  • actions: 利用commit提交mutation,可以执行异步操作,通过 store.dispatch方式触发

    // 模拟异步请求
    var delay = (timeout,cb) => new Promise(resolve => setTimeout(()=>{cb(); resolve("test incrementAsync")},timeout));
    
    actions:{
      incrementAsync({commit}) {
          return delay(600,function(){commit("increment")})
    
      },
    }
    
  • module: 当store对象比较庞大的时候,可以考虑将store分隔成模板。每个模块拥有自己的state、mutation、action、getter。很少情况下使用

把所有部分组合起来,就构成一个简单的计数器:

var delay = (timeout,cb) => new Promise(resolve => setTimeout(()=>{cb(); resolve("test incrementAsync")},timeout));
var store = new Vuex.Store({
  state:{
    count:0
  },
  getters:{
     getState:state=>{
       return state.count
     }
  },
  mutations:{
    increment:state => state.count++,
    decrement:state => state.count--
  },
  actions:{
    incrementAsync({commit}) {
        return delay(600,function(){commit("increment")})
    },
    decrementAsync({commit}) {
        return delay(600,function(){commit('decrement')});
    }
  }
})

然后组件中触发actions,就可以

store.dispatch('decrementAsync').then(() => {
// ...
})

在浏览器中,使用vue-devtool,试下时间旅行功能。
vue-devtool

页面路由

使用Vue.js创建单页面应用,就可以使用vue-router,目前版本是3.0.1,把组件映射到对应的路由,通过改变url来渲染不同的页面。官方中文文档

vue-router 默认hash模式,每次url只会改变#后面对应的值,页面就不会重新加载,并且也不需要服务器端作任何配置。

如果使用路由的history模式,url就会正常http://yoursite.com/user/id,只需要添加配置mode:'history',同时需要后端配置,不然页面重新刷新,会匹配不到任何资源。

不同模式下的服务器配置及生产环境部署,可以参考vue、react等单页面项目应该这样部署到服务器.

基础概念:

  • < router-link>

      <router-link to="/foo">Go to Foo </router-link>
      <router-link to= "/bar">Go to Bar</router-link>

    使用router-link 组件导航,通过传入to属性指定链接,相当于原生的a 标签。

  • < router-view>
    路由出口,路由匹配到的组件将渲染在这里

     <router-view></router-view>

    可以在上面添加一些过渡效果

      <transiton name="slide">
         <router-view></router-view>
      </transiton>
  • 初始化路由配置

    如果使用vue-cli脚手架构造项目,在init的时候,会出现选项提示用户安装路由

    vue-router

    确认后,自动生成src/router.js文件,相关路由配置文件就可以写在里面。

      // 首先引入不同的vue组件(默认安装了[vue-loader](https://vue-loader.vuejs.org/zh-cn/start/spec.html),每个.vue文件看成一个完整的组件)
      import components1 from "./page/xx.vue"
      import components2 from "./page/xx2.vue"
    
      // router 数组用来定义路由配置,非嵌套路由
      const routers = [
         {path:'/foo',component:components1},
         {path:'/bar',component:components2}
      ]
    
     // 最后抛出这个配置数组
      export default routes;

    注入到router配置参数里面

        const app = new Vue({
            router
       }).$mount("#app")

    存在嵌套路由的时候,需要使用 children配置,比如上面的components1组件内部包含自己的嵌套<router-view>,就可以使用嵌套路由。

      const routers = [
         { path:'/foo',
           component:components1,
           children:[
             // 当  /foo/detail 匹配成功后,component3 会被渲染在components1中 <router-view> 的位置
              {  path:'detail',
                  component:component3
              },
            // 如果 /foo 匹配成功,没有匹配子路由,默认就会渲染这个空的子路由。
              {
                path:'',
                component:component4
              }
           ]
         }
      ]
    
  • router 实例方法
    在Vue实例内部,可以通过this.$router获取实例对象,

    • router.push({path:'/user',params:{id:'123'}}) 跳转
    • router.replace(),与上面相同,不会添加新的记录。
    • router.go(-1),表示在路由记录中前进或者后退多少步。
    • 在html <template></template>中,可以通过 {{$route}},获取路由配置的相关信息,从而渲染DOM。比如: 通过

      <template v-for="(items,index) in $router.options.routes">
        <title>
           {{items.name}}
        </title>
      </template>
      

      就可以将路由配置信息与页面导航栏对应,列表渲染出导航栏,

    • watch方法中监听$route,可以动态配置组件,不同url复用同一组件

           watch:{
              `$route`:function(to,from) {
                  // 通过to,from 获取url信息
              }
           }
    • 导航守卫(路由钩子)

      通过注册一个全局路由钩子函数,在初始化const router = new VueRouter({})的时候,定义router.beforeEach((to,from,next)=> {...}),在每次进入目标路由之前触发。配合Vuex可以非常方便的进行权限管理

         router.beforeEach((to, from, next) => {
             if (store.getters.getisAuthority) {
               // 检查已经登录了,就继续跳转。
               next()
             }else if(to.fullPath === "/login"){
                 // 跳转到登录页面,则清空登录相关信息
                 clearCookie()
                 next()
             } else {
               next({
                   path:"/login"
               });
          }})

Vue-cli入门

Vue 提供一个官方命令行工具,可用于快速搭建大型单页面应用

目前已经发布到了V3.0.0-alpha.5

  npm install -g @vue/cli
  vue create my-project

基本用法,文档里面也比较清楚,参考这里,
通过下面几步,快速搭建项目基础结构。

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

(如果没有使用任何框架的基础上,也想快速搭建一个大型项目的目录结构,可以考虑yeoman快速生成一个新的项目)

基础配置:

  • 修改开发环境下port

    /config/index.js文件中, 手动修改 module.exports = { dev:{ port :8080}}

  • 配置代理

    /config/index.js目录下,(以代理3000端口上数据请求为例)

          dev: {
              proxyTable: {
                '/rest/*':{
                          target:'http://127.0.0.1:3000',
                          secure:false,
                          pathRewrite:{
                             '^/rest':''
                          }
                      }
              },
    
  • 生产环境关闭sourcemap

    /config/index.js目录下,build:{} 中的productionSourceMap改为false

  • 配置路径别名(alias)

    通常在项目中会看到诸如这样的 import Cookie from "@/util/cookie.js"的引入,@就是vue-cli中默认设置的alias

    /build/webpack.base.conf.js/文件中,resolve对象下添加属性,指向对应的路径

      resolve:{
         alias:{
           'page':path.resolve(__dirname,'../src/page')
         }
      }
  • 区分不同环境

    • 通过process.env.NODE_ENV值区分

      /build/webpack.dev.conf.js/build/webpack.prod.conf.js中,通过

       new webpack.DefinePlugin({
          'process.env': env
       }),

      创建了编译时可以配置的全局常量,用来区分开发/发布/测试环境,env对应的值,可以在/config/目录下的,*.dev.js文件下配置的。

      然后,在其它业务代码里面,直接使用这个全局变量,比如在 main.js里面:

           if(process.env.NODE_ENV === "development"){
               console.log("开发环境")
           }
    • 通过命令行区分不同环境

      同样使用上面的方法,添加一个全局变量,不同的是从命令行中获取参数。
      比如,打包时还区分 发布环境 和 预发环境,就可以修改如下,

      new webpack.DefinePlugin({
         'process.env': env,
         'VERSION':process.argv[2] == "pro"?'"pro"':'"sit"'
      }),
      

      在命令行中打包时,可以使用 npm run build --env sit,在业务代码中,通过全局变量VERSION,同样可以区分不同环境。

参考链接


不可能的是
2.3k 声望123 粉丝

stay hungry&&foolish