首页 > 技术文章 > Vue学习总结

ajjoker 2020-03-24 21:09 原文

MVVM:view-viewmodel-model:view和model是不可以直接通信的,它们之间存在viewmodel这个中介,当用户操作view,viewmodel感知到变化,然后通知model发生相应改变,反之亦然。viewmodel向上与view进行双向数据绑定,向下与model通过接口请求进行数据交互,起到承上启下的作用

 

 

 

<div id="vue_det">

  <h1>site : {{site}}</h1>

  <h1>url : {{url}}</h1>

  <h1>{{details()}}</h1>

</div>

<script type="text/javascript">

  var vm = new Vue({

    el: '#vue_det',

    data: {

      site: "菜鸟教程",

      url: "www.runoob.com",

      alexa: "10000"

    },

    methods: {

      details: function() {

        return this.site + " - 学的不仅是技术,更是梦想!";

      }

    }

  })

</script>

每个Vue应用都需要通过实例化Vue来实现,如下:

var vm = new Vue({
  // 选项
})

在Vue构造器中有一个el参数,它是DOM元素中的id,在上面的实例中id为vue_det  

当一个Vue实例被创建时,它向Vue的响应式系统中加入了其data对象中能找到的所有的属性,当这些属性的值发生变化时,html视图也会产生相应的变化

在上面的实例中,vm.site与data.site相同,即vm.site === data.site为true,单独修改其中一个也会影响到另外一个,因为他们指向相同

除了数据属性,Vue实例还提供了一些有用的实例属性与方法,它们都有前缀$,以便与用户定义的属性区分开来,如:

  document.write(vm.$data === data) // true

  document.write(vm.$el === document.getElementById('vue_det')) // true

Vue生命周期

每个Vue实例在创建时都要经过一系列的初始化过程,这就是生命周期,在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段不断添加自己代码的机会

 

 

 Vue指令:

v-text:插值更新数据,无闪动问题,非双向绑定

  <span v-text="msg"></span>

    <span>{{msg}}</span>

  这两条语句用法相同,都是从data中获取msg的值

v-html:更新元素的innerHTML,内容按普通HTML插入,不会作为Vue模板进行编译

  <div v-html="html"></div>

  注意:在网页上动态渲染任意HTML是非常危险的,因为容易导致XSS攻击,所以只能在可信内容上使用v-html,永不用在用户提交的内容上

  在单文件组件里,scoped的样式不会应用在v-html内部,因为那部分HTML没有被Vue的模板编译器处理,可以通过额外的全局<style>元素实现

v-pre:跳过这个元素和它的子元素的编译过程,可以用来显示原始Mustache标签,跳过大量没有指令的节点会加快编译

  <span v-pre>{{ this will not be compiled }}</span>

v-cloak:解决{{message}}表达式存在的闪动问题,先隐藏,等替换好值之后再显示最终的值

  官方定义:这个指令保持在元素上直到关联实例结束编译

  <div v-cloak> {{ message }} </div>  //直到编译结束才会显示message的值

v-show:

  <h1 v-show="ok">Hello!</h1>

  根据条件展示元素,和v-if不同的是,如果v-if的值是false,则这个元素被销毁,不在DOM中,但是v-show的元素会从一开始就被渲染并始终保存在DOM中,只是简单的切换CSS的display属性

  v-if有更高的切换开销,而v-show有更高的初始渲染开销,因此,如果要非常频繁的切换,则使用v-show较好,如果在运行时条件不太可能改变,则v-if较好

v-if:实现条件渲染,Vue会根据表达式的值的真假来渲染元素。在切换时元素及它的数据绑定/组件被销毁并重建

  +v-else

  <div v-if="Math.random() > 0.5">

    Now you see me

  </div>

  <div v-else>

    Now you don't

  </div>

v-for:基于源数据多次渲染元素或模板块

  <div v-for="item in items">

    {{ item.text }}

  </div>

  一般来说,使用v-for的时候要带上它的特殊属性key,其中key可以根据具体情况绑定数据,每个key的数据应该是唯一的

  <div v-for="item in items" :key="item.id">

    {{ item.text }}

  </div>

v-on:绑定事件监听器,表达式可以是一个方法名(methods中)

  <button @click="doThis"></button>

  事件修饰符:

    .stop:阻止单击事件冒泡

    .prevent:提交事件不再重载页面,比如 submit 加了这个,就不会提交了。

    .capture:使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理

    .self:只当事件在该元素本身(而不是子元素)触发时触发回调。在传递的父子事件中,加了这个,无论先点哪个,都先执行这个。

    .{ keyCode | KeyAlias }:只当事件是从特定键触发时才触发回调

    .native:监听组件根元素的原生事件

    .once:事件将只会触发一次

    .left:只当点击鼠标左键时才触发

    .right:只当点击鼠标右键时才触发

    .middle:只当点击鼠标中键时才触发

    .passive:告诉浏览器不想阻止事件的默认行为

    如:<input @keyup.enter="onEnter">

    <button @click.stop.prevent="doThis"></button>

    使用修饰符时,顺序很重要,相应的代码会以相应的顺序执行

v-bind:属性绑定,动态地绑定一个或多个特性,如class和style,当数据源变化时,v-bind绑定的标签对应的数会变化,但不是同步变化,即异步;标签对应的数发生变化不会影响数据源,即不是双向绑定

  用法:v-bind属性名=“数据名”

  <img :src="imageSrc">

  对象语法:用{}和键值对的形式,其中red是style里的类名,即.red,isRed通过在data中定义布尔值,来决定red样式的显示与否

  <div :class="{ red: isRed }"></div>

  数组语法:用[]形式,其中activeClass和errorClass在data中以键值对activeClass:active来定义

  <div :class="[ activeClass, errorClass ]"></div>

  简化:<div :class="objClass"></div>,其中objClass可以为数组也可以为对象

  objClass:[ 'active', 'error' ] 或者objClass:{ active: true, error: true},再分别使用数组和对象的api来操作对应元素

  即使还有class,如<div class="base" :class="objClass"></div>,则base类和objClass会一起生效

  内联样式:

  对象语法

  <div :style="{ fontSize: size + 'px' }"></div>,其中size在data中定义

  数组语法:

  也都可以通过把数据都放在data中定义一个对象或者数组的方式来简化,提高可读性

v-model:在表单控件或者组件上实现双向绑定

   <div id="app">

      <input v-model="somebody">
      <p>hello {{somebody}}</p>
  </div>

  修饰符:.lazy:在默认情况下,v-model在input事件中同步输入框的值与数据,但是可以添加一个修饰符lazy,从而转变为在change事件中同步

      .number:自动将用户的输入值转为Number类型

      .trim:自动过滤用户输入的首尾空格

  v-model本质上是v-bind和v-on的共同作用,v-bind绑定数据源,v-on绑定标签改变函数,如input输入框中this.msg = event.target.value来实现数据的双向绑定,或者msg = $event.target.value

v-slot:提供具名插槽或需要接收prop的插槽

Vue计算属性

<div id="app">

  <p>原始字符串: {{ message }}</p>

  <p>计算后反转字符串: {{ reversedMessage }}</p>

</div>

<script> var vm = new Vue({

  el: '#app',

  data: { message: 'Runoob!' },

  computed: { // 计算属性的 getter

    reversedMessage: function () { // `this` 指向 vm 实例

      return this.message.split('').reverse().join('') } } })   //计算属性一定要有返回值

</script>

  在上例中,vm.reversedMessage始终依赖于vm.message,即他们的值始终同步

  在vue中methods和computed效果相同,不同为:调用的时候,如果属于methods,调用方法名的同时要加上(),

  还有一个不同是:computed是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值,即只有需要计算的数据发生变化时,才重新进行computed中的计算,否则从缓存中取数据,而使用methods,在重新渲染的时候,函数总会重新调用执行,即methods是实时的

  计算属性默认只有getter,不过在需要时也可以提供一个setter,即也可以传递参数

  computed: {

    fullName: {

      // getter,即把值返回出去

      get: function () { return this.firstName + ' ' + this.lastName },

      // setter,即把参数获取回来

      set: function (newValue) {

        var names = newValue.split(' ')

        this.firstName = names[0]

        this.lastName = names[names.length - 1]

  } } }

  现在再运行vm.fullName = 'John Doe'时,setter会被调用,vm.firstName和vm.lastName也会被相应的更新

Vue监听属性(watch)

数据一旦发生变化就通知侦听器所绑定的方法

应用场景:数据变化时执行异步或开销较大的操作,通过在watch中的函数来实现

watch: {

  firstName: function(val) {

    this.fullName = val + ' ' + this.lastName

  }

}

其中,函数名firstName要和被监听的数据名保持一致,val是firstName最新的值(首先要记得把相应的数据绑定到标签上)

Vue过滤器

格式化数据

全局过滤器:位置在var vm=new Vue()上面

Vue.filter( '过滤器名称', function(val) {   //val是过滤器所在位置的数据的值

  //过滤器业务逻辑,通过return返回值

})

使用:<div>{{msg | upper}}</div>

<div>{{msg | upper | lower }}</div>   //可级联使用

<div :id="id | formatId"></div>

也可以传递参数

Vue.filter( 'format', function(val,arg1) {   //val是过滤器所在位置的数据的值

  //过滤器业务逻辑,通过return返回值

})

使用:

<div>{{date | format(`yyyy-MM-dd`)}}</div> //参数从第二个开始,第一个是要过滤的值

局部过滤器:位置在var vm =new Vue()里,和data,methods等平级

filters: {

  '过滤器名称': function(){ }

}

Vue组件

一个组件其实相当于vue的一个实例,只是不需要new实例化而已。因此,和new Vue一个实例一样,也可以定义data(必须是一个函数)、methods、computed、等等

data:function(){return{
    count:0}}                  //此处的函数其实是一个闭包的环境,即每个组件都有自己的私有变量

子组件与父组件:

将某段代码封装成一个组件,而这个组件又在另外一个组件中引入,那么引入该封装组件的组件叫做父组件,被引入的组件叫做子组件

例如:当创建一个组件时,那个组件名就是子组件,而当在这个组件下创建一个Vue实例时,el需要绑定div,那么这个div就是父组件

全局组件:它们在注册之后可以用在任何新创建的Vue根实例(new Vue)的模板中,在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以互相使用,每个组件都是可重用的,他们各自的数据互不影响

注册:Vue.component(tagName,options),其中tagName为组件名,options为配置对象

注册后,可以使用如下方法来调用:<tagName></tagName>

<div id="app">

  //调用该全局组件

  <runoob></runoob> </div>

<script>

// 注册

Vue.component('runoob', {

  //这个子组件的一个模板,页面的组件标签会被子组件里的模板替换,可以理解成占位符

  template: '<h1>自定义组件!</h1>'     //这里的template要求必须有一个根元素,例如,如果想放两个按钮:template: '<button></button><button></button>',这样是不对的,应该是

                   template: '<div><button></button><button></button></div>',同根元素div来包裹它们,而且此处最好用模板字符串(反引号)来写,看起来更加直观

                    

                  template: `
                          <div>
                              <button>3</button>
                              <button>5</button>
                          </div>
                          `

})

// 创建根实例

new Vue({

  el: '#app'

})

</script>

 局部组件:全局组件往往是不够理想的,在webpack中,全局注册造成了用户下载的JavaScript的无谓增加

在这种情况下,可以通过一个普通的JavaScript对象来定义组件:

var ComponentA = { /* ... */ }

var ComponentB = { /* ... */ }

var ComponentC = { /* ... */ }

然后在components选项中定义想要使用的组件:

new Vue({

  el: '#app',

  components: {

    'component-a': ComponentA,

    'component-b': ComponentB

} })

对于components对象中的每个属性来说,其属性名就是自定义元素的名字(冒号前),其属性值就是这个组件的选项对象(冒号后),使用方法和全局组件相同

局部组件只能在注册它的父组件中使用

当然,无论是全局组件还是局部组件,都可以写成一个vue单文件组件放在component文件夹中,使用import导入它

父子组件之间传值

prop是子组件用来接收父组件传递过来的数据的一个自定义属性(一个数组),父组件的数据需要通过props把数据传给子组件,子组件需要显式地用props选项声明prop

当数组中有多个参数时,如果有某个参数没有被传递,则显示undefined

注意:要在子组件props中使用驼峰式写法,在父组件调用时使用短横线形式传参(因为DOM元素的属性不区分大小写,使用驼峰形式可能会出现同名),如menu-title和menuTitle,但是在字符串模板中(反引号内)可以都使用驼峰式

props属性值类型:String、Number(使用v-bind可以得到number类型,否则为string类型)、boolean(使用v-bind可以得到boolean类型,否则为string类型)、Array、Object

//父组件

<div id="app">

  <child message="hello!"></child>

</div>

<script>

// 注册,子组件

Vue.component('child', {

  // 声明props

  props: ['message'],

  // 同样也可以在 vm 实例中像 "this.message" 这样使用

  template: '<span>{{ message }}</span>'

})

// 创建根实例

new Vue({

  el: '#app'

})

</script>

动态prop:利用v-bind动态绑定props的值到父组件的数据中,每当父组件的数据变化时,该变化也会被传导给子组件

  <childv-bind:message="parentMsg"></child> 

prop是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来

父组件使用props传递数据给子组件,但如果子组件要把数据传递给父组件,则需要使用自定义事件

子组件通过自定义事件向父组件传递信息:

<button @click="$emit('enlarge-text')",5>扩大</button>   //其中enlarge-text为事件名,5为要传递的参数,事件通过$emit来触发,传给父组件

父组件监听子组件的事件

<button-counter @enlarge-text="handle($event)"></button-counter>    //通过@事件名来监听,通过$event来接收参数,其中handle定义为function(val) {}

兄弟组件之间传值,使用eventhub事件中心

事件中心:var hub = new Vue()     //全局下定义

监听事件: // 在mounted函数中触发hub,因为在此时模板已经就绪

        mounted: function() {
            // 监听事件 
            hub.$on('tom-event', (val=> {
                this.num += val
            })
        }

  其中使用hub.$on()来监听事件,tom-event为自定义事件名,在这里使用箭头函数是因为箭头函数默认绑定外层this,val是接收的传递值

触发事件://触发兄弟组件的事件

                hub.$emit('jerry-event'2)

  使用hub.$emit()来触发事件,jerry-event是要触发的事件名,2为要传递的参数

销毁事件:hub.$off('tom-event')

  使用hub.$off()来销毁事件,其中tom-event为要销毁的事件名

Vue插槽

实现父组件向子组件传递内容

组件插槽

  使用:将自定义组件alert-box的<alert-box>错误</alert-box>中的红色内容传给<slot>默认内容</slot>显示,当红色内容为空时,显示默认内容

  template:`

        <div>
            <span>Bug:</span>
            <slot>默认</slot>
        </div>
        `
template标签作用:临时包裹信息,同时对在template标签中的多条内容进行操作

具名插槽:按照插槽的名字来匹配

 <slot name="header"></slot>                      具名匹配                  <h1 slot="header"></h1>

<slot></slot>                 默认匹配 ,匹配其他没有被具名匹配到的元素               <p>主要内容</p>

作用域插槽:实现父组件对子组件的内容进行加工处理

  子组件:根据父组件的条件来操作子组件的显示

  template: `

        <div>
            <li :key="item.id" v-for="item in list">
                <slot :info="item">{{item.name}}</slot>                     //其中属性info为自定义,默认语法规定
            </li>
        </div>
  父组件:通过template标签、slot-scope、自定义名称slotProps来得到item的引用,进而操作item
  <template slot-scope="slotProps">
            {{slotProps.info.name}}
        </template>
        `

Vue前端交互

接口调用方式:原生ajax、基于JQuery的ajax、fetch、axios

传统格式的URL:schema://host:port/path?query#fragment

  schema:协议,如http、https、ftp等

  host:域名或者IP地址

  port:端口,http默认端口80,可以省略

  path:路径,如/abc/a/b/c

  query:查询参数,如uname=list&age=12

  fragment:锚点(哈希Hash),用于定位页面的某个位置

Restful形式的URL

  HTTP请求方式:GET查询、POST添加、PUT修改、DELETE删除

  如http://www.123.com/books   GET          

  http://www.123.com/books   GET      /虽然URL一样,但请求不一样,所以也是不同的URL地址

Promise

回调地狱:因为作词异步调用的结果顺序是不确定的,如果想要确定顺序,则需要一层一层在上层异步语句中进行嵌套,嵌套的多了,就会对可读性和可维护性造成问题

回调地狱可以通过Promise来解决,从语法上讲,Promise是一个对象(构造函数,typeof(promise)为function),从它可以获得异步操作的消息

Promise基本用法:

  • 实例化Promise对象,构造函数中传递函数,该函数中用于处理异步任务
  • resolvereject两个参数(这两个参数都是方法,可以调用)用于处理成功和失败两种情况,并通过p.then获取处理结果,当then只有一个函数时,表示只处理正常的情况,不处理异常情况

setTimeout()方法:属于window对象

  setTimeout(function(){ alert("Hello"); }, 3000)  //3000毫秒,即3秒后弹出Hello

<script>
    var p = new Promise(function(resolve, reject) {
        // 这里用于实现异步任务
        setTimeout(function() {
            var flag = false
            if(flag) {
                // 正常情况
                resolve('hello')
            }else {
                // 异常情况
                reject('出错了')
            }
        }, 100)
    })
    p.then(function(data) {
        // 接收正常情况,hello传递给data
        console.log(data)
    },function(info) {
        // 接收异常情况,出错了传递给info
        console.log(info)
    })
</script>
Promise处理ajax请求:
<script>
    function queryData(url) {
        var p = new Promise(function(resolve, reject) {                //此处可以优化为return new Promise(function(resolve, reject) { 
            var xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4&&xhr.status == 200) {
                    // 处理正常的情况
                    resolve(xhr.responseText)
                }else {
                    // 处理异常的情况
                    reject("服务器错误!")
                }
            }
            xhr.open('get', url)
            xhr.send(null)
        })
        return p
    }
    // 发送多个ajax请求并保持顺序
    queryData('接口url,如http://localhost:3000/data')
        .then(function(data) {
            console.log(data)
            return queryData('接口地址1') // return的是这次接口调用的Promise实例对象
        })
        .then(function(data) {  // 这个then接收的是接口地址1的调用结果
            console.log(data)
            return queryData('接口地址2')
        })
        .then(function(data) {
            console.log(data)
        })
</script>

then参数中有两种返回值

  1.返回Promise实例对象,如上例,返回的该实例对象会调用下一个then

  2.返回普通值,如return ‘hello',此时返回的普通值会直接传递给下一个then,通过then参数中函数的参数接收该值。在这里其实是产生了一个默认的Promise对象,保证then的链式操作可以继续执行

Promise常用API(用法同p.then,如上例)

  常用的实例方法:

  p.then()得到异步任务的正常结果

  p.catch()获取异常信息,和p.then的第二个函数作用相同

  p.finally()成功与否都会执行,常用来产生提示信息或销毁资源

  常用的对象方法:

  Promise.all():并发处理多个异步任务,所有任务都完成才能得到结果

  Promise.race():并发处理多个异步任务,只要有一个任务完成就能得到结果

  接上例: 

  var p1 = queryData('接口地址1')
     var p2 = queryData('接口地址2')
        var p3 = queryData('接口地址3')
     Promise.all([p1,p2,p3].then(function(result){      //传入的是Promise数组,返回的也是结果的数组
          console.log(result)
     }))
     Promise.race([p1,p2,p3].then(function(result){    //只显示响应最快的结果,其他两个结果也会返回,但是客户端并不关心
          console.log(result)
     }))

 

推荐阅读