代码改变世界

Vue2 第二天学习

2017-05-02 23:25 龙恩0707 阅读(...) 评论(...) 编辑 收藏
个人小总结:1年多没有写博客,感觉很多知识点生疏了,虽然工作上能解决问题,但是当别人问到某个知识点的时候,还是迷迷糊糊的,所以坚持写博客是硬道理的,因为大脑不可能把所有的知识点记住,有可能某一天忘了,但是我们工作上还是会使用,只是理论忘了,所以写博客的好处是可以把之前的东西重新看一遍后会在大脑里面重新浮现起来,特别在面试的时候,别人问你的知识点的时候答不上来那种尴尬,但是平时经常使用到,只是说不出所以来的,因此写博客是最好的思路。

阅读目录

1.vue属性和方法

每个Vue实例都会代理其 data对象里所有的属性。
如下代码:

var data = {
  a: 1
};
var vm = new Vue({
 data: data
});
console.log(vm);
console.log(vm.a === data.a); // true

// 设置属性也会影响到原始数据
vm.a = 2;
console.log(data.a); // 2
// 反之
data.a = 3;
console.log(vm.a); // 3
//除了data属性,Vue实例还暴露了一些有用的实例属性与方法。这些属性与方法都有前缀$, 以便与代理的data属性区分。
var data = { a: 1 }
var vm = new Vue({
  el: '#container1',
  data: data
})
console.log(vm.$data === data) // true
console.log(vm.$el === document.getElementById('container1')) // true
data.a = 5;
// $watch 是一个实例方法
vm.$watch('a', function (newVal, oldVal) {
  // 这个回调将在 `vm.a`  改变后调用
  console.log(newVal);
  console.log(oldVal);
})

1-1. data 必须是函数
通过Vue构造器传入的各种选项大多数都可以在组件里用。 data 是一个例外,它必须是函数。 如下代码Vue 会停止,并在控制台会报错。

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      
      <component1></component1>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    var data = { counter: 0 };
    // 全局注册
    Vue.component('component1', {
      template: '<span>{{ message }}</span>',
      data: {
        message: 'hello'
      }
    });
    new Vue({
      el: '#container1'
    })
  </script>
</html>

data是函数解决该方案
代码如下:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <component1></component1>
      <component1></component1>
      <component1></component1>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    var data = { counter: 0 };
    // 全局注册
    Vue.component('component1', {
      template: '<button v-on:click="counter += 1">{{ counter }}</button>',
      // data是一个函数,vue不会报错,但是我们返回给每个组件的实列引用了同一个data对象
      data: function() {
        return data
      }
    });
    new Vue({
      el: '#container1'
    })
  </script>
</html>

查看效果

由于这三个组件共享了同一个 data , 因此增加一个 counter 会影响所有组件!这不对。我们可以通过为每个组件返回全新的 data 对象来解决这个问题:
代码如下:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <component1></component1>
      <component1></component1>
      <component1></component1>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    // 全局注册
    Vue.component('component1', {
      template: '<button v-on:click="counter += 1">{{ counter }}</button>',
      // data是一个函数,vue不会报错,但是我们返回给每个组件的实列引用了同一个data对象
      data: function() {
        return {
          counter: 0
        }
      }
    });
    new Vue({
      el: '#container1'
    })
  </script>
</html>

查看效果

现在每个 counter 都有它自己内部的状态了.

2.理解组件的通信。

一般情况父子组件是这样的关系,组件A在它的模板中使用了组件B,他们之间必然需要相互通信,父组件要给子组件传递数据,子组件需要将它内部发生的事情告知父组件,为了保证父子组件的解耦,可维护性及可重用性。在vue.js中,父组件通过props向下传递数据给子组件,子组件通过events给父组件发送消息。

2-1 使用props传递数据
不能在子组件的模板内直接引用父组件的数据,要让子组件使用父组件的数据,我们需要通过子组件的props选项。
如下代码:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <child message="hello!"></child>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    // 全局注册
    Vue.component('child', {
      // 声明props 
      props: ['message'],
      template: '<span>{{ message }}</span>'
    });
    new Vue({
      el: '#container1'
    })
  </script>
</html>

结果在页面上会打印 hello。

查看效果

注意: HTML特性是不区分大小写的,所以当使用的不是字符串模板,camelCased(驼峰式) 命名的prop需要转换为相对应的 kebab-case(短横线隔开式)命名:
如下代码:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <!-- kebab-case in HTML-->
      <child my-message="hello!"></child>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    // 全局注册
    Vue.component('child', {
      // 声明props 
      props: ['myMessage'],
      template: '<span>{{ myMessage }}</span>'
    });
    new Vue({
      el: '#container1'
    })
  </script>
</html>

2-2 理解动态prop
在模板中,要动态地绑定父组件的数据到子模板的props,使用v-bind,每当父组件的数据变化时,该变化会传递给子组件。

<div id="container1">
  <input v-model='parentMsg' />
  <br />
  <!-- kebab-case in HTML-->
  <child v-bind:my-message="parentMsg"></child>
</div>

使用 v-bind 的缩写语法通常更简单:

<child :my-message="parentMsg"></child>

代码如下:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <input v-model='parentMsg' />
      <br />
      <!-- kebab-case in HTML-->
      <child v-bind:my-message="parentMsg"></child>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    new Vue({
      el: '#container1',
      data: {
        parentMsg: 'Message'
      },
      components: {
        child: {
          props: ['myMessage'],
          template: '<span>{{myMessage}}</span>'
        }
      }
    })
  </script>
</html>

查看效果

3.理解自定义事件

父组件使用props传递数据给子组件,但是如果子组件需要把数据传回去的话,就需要自定义事件了;
3-1 使用v-on绑定自定义事件
每个vue实例都实现了事件接口,即:
1. 使用 $on(eventName) 监听事件
2. 使用 $emit(eventName) 触发事件
注意: $on 和 $emit 不是 addEventListener 和 dispatchEvent的别名。且 父组件可以在使用组件的地方直接用 v-on 来监听子组件触发的事件。
不能用$on侦听子组件抛出的事件,而必须在模板里直接用v-on绑定,就像以下的例子:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <p> {{ total }} </p>
      <button-counter v-on:increment="incrementTotal"></button-counter>
      <button-counter v-on:increment="incrementTotal"></button-counter>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    Vue.component('button-counter', {
      template: '<button v-on:click="increment">{{ counter }}</button>',
      data: function() {
        return {
          counter: 0
        }
      },
      methods: {
        increment: function() {
          this.counter += 1;
          this.$emit('increment');
        }
      },
    })
    new Vue({
      el: '#container1',
      data: {
        total: 0
      },
      methods: {
        incrementTotal: function() {
          this.total += 1;
        }
      }
    })
  </script>
</html>

上面代码: 初始化时候 实例化设置 data: {total: 0}, 设置total为0, 子组件button-counter 默认为0, 当点击子组件的时候调用 increment方法,当前的counter自增1, 然后在子组件触发 $emit('increment')事件,当使用 v-on:increment 会监听到事件后,会调用父组件的incrementTotal方法,因此父组件也自增1.

查看效果

上面代码中 子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,至于父组件是否关心则与它无关。

4.理解使用自定义事件的表单输入组件

自定义事件可以用来创建自定义的表单输入组件,使用v-modal来进行数据双向绑定。比如如下代码:

<input v-modal="something" />

上面的代码是下面的语法糖;如下代码:

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

因此在创建组件中时,相当于下面的简写;如下代码:

<custom-input v-bind:value="something" v-on:input="something=arguments[0]"></custom-input>

所以要让组件的v-model 生效,必须满足下面的条件:
1. 接受一个value属性。
2. 在有新的value时触发input事件。

如下测试代码:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <currency-input v-model="price"></currency-input>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script>
   
  </script>
  <script type="text/javascript">
    Vue.component('currency-input', {
      template: '\
        <span>\
          $\
          <input\
            ref="input"\
            v-bind:value="value"\
            v-on:input="updateValue($event.target.value)"\
          >\
        </span>\
      ',
      props: ['value'],
      methods: {
        // 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制
        updateValue: function (value) {
          var formattedValue = value
            // 删除两侧的空格符
            .trim()
            // 保留 2 小数位
            .slice(0, value.indexOf('.') + 3)
          // 如果值不统一,手动覆盖以保持一致
          if (formattedValue !== value) {
            this.$refs.input.value = formattedValue
          }
          // 通过 input 事件发出数值
          this.$emit('input', Number(formattedValue))
        }
      }
    });
    new Vue({
      el: '#container1',
      data: {
        price: 0
      }
    })
  </script>
</html>

查看效果

5.单个slot 

<slot>标签中的任何内容都被视为 备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有插入的内容时才显示备用内容。
如果<slot>标签中有内容的话,就显示该内容。
比如 my-component 组件有如下代码:

<div class="content">
  <h2>this is a component</h2>
  <slot>如果没有分发内容,则显示slot中的内容</slot>
  <p>asdsadsdad</p>
</div>

父组件有如下代码:

<div id="container1">
  <my-component>
    <h1>Hello Vue.js</h1>
  </my-component>
  <my-component></my-component>
</div>

渲染后的结果为:

<div id="container1">
  <div class="content">
    <h2>this is a component</h2> 
    <h1>Hello Vue.js</h1> 
    <p>asdsadsdad</p>
  </div> 
  <div class="content">
    <h2>this is a component</h2> 
    如果没有分发内容,则显示slot中的内容 
    <p>asdsadsdad</p>
  </div>
</div>

所有测试实例代码如下:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>

    <div id="container1">
      <my-component>
        <h1>Hello Vue.js</h1>
      </my-component>
      <my-component></my-component>
    </div>

    <template id="myComponent">
      <div class="content">
        <h2>this is a component</h2>
        <slot>如果没有分发内容,则显示slot中的内容</slot>
        <p>asdsadsdad</p>
      </div>
    </template>
    
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    Vue.component('my-component', {
      template: '#myComponent'
    })
    new Vue({
      el: '#container1'
    })
  </script>
</html>

查看效果

6.具名slot 

<slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
如果没有默认的 slot ,这些找不到匹配的内容片段将被抛弃。
比如:假如有一个 my-component 组件,它的模板为:

<template id="myComponent">
  <div class='content'>
    <header>
      <slot name='header'></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name='footer'></slot>
    </footer>
  </div>
</template>

父组件的模板如下:

<div id="container1">
  <h1 slot="header">这里可能是一个页面标题</h1>
  <p>主要内容的一个段落</p>
  <p>另一个主要段落</p>
  <p slot='footer'>这里是底部信息</p>
</div>

页面渲染的结果如下:

<div id="container1">
  <h1>这里可能是一个页面标题</h1> 
  <p>主要内容的一个段落</p> 
  <p>另一个主要段落</p> 
  <p>这里是底部信息</p>
</div>

所有的代码如下:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>

    <div id="container1">
      <h1 slot="header">这里可能是一个页面标题</h1>
      <p>主要内容的一个段落</p>
      <p>另一个主要段落</p>
      <p slot='footer'>这里是底部信息</p>
    </div>

    <template id="myComponent">
      <div class='content'>
        <header>
          <slot name='header'></slot>
        </header>
        <main>
          <slot></slot>
        </main>
        <footer>
          <slot name='footer'></slot>
        </footer>
      </div>
    </template>

  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    Vue.component('my-component', {
      template: '#myComponent'
    })
    new Vue({
      el: '#container1'
    })
  </script>
</html>

查看效果

7.理解作用域插槽(2.1.0新增的) 

   在slot分发中,无论是单分发还是具名分发,都是父组件替换子组件的数据,或者没有替换,用子组件默认的数据。 但是通过设置作用域槽,就可以改变这种状况,让子组件可以在父组件进行分发时获取自己的数据,至于是什么数据,由子组件决定,这样就能解耦了。
作用域槽通过slot的一个自定义的属性,官方给出的DEMO是text,但也可以是其他,值为暴露的数据。 这个自定义属性已经存放在子组件的prop对象里了。等待着被父组件获取。
怎么获取呢? 在父组件的模板里,使用一个Vue自带的特殊组件<template> ,并在该组件上使用scope属性,值是一个临时的变量,存着的是由子组件传过来的prop对象,获得由子传过来的prop对象。这时候,父组件就可以访问子组件在自定义属性上暴露的数据了。
如下代码:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>
    <div id="container1">
      <parent-component></parent-component>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
    // 子组件
    Vue.component('child-component', {
      template: '<ul><slot name="child-ul" v-for="item in rets" v-bind:text="item.name"></slot></ul>',
      data: function() {
        return {
          rets: [
            {name: '我是苹果'},
            {name: '我是香蕉'},
            {name: '我是橘子'}
          ]
        }
      }
    });
    // 父组件
    Vue.component('parent-component', {
      template: '<child-component><template scope="props" slot="child-ul"><li>{{props.text}}</li></template></child-component>'
    })
    new Vue({
      el: '#container1'
    })
  </script>
</html>

页面渲染后的代码如下:

<div id="container1">
  <ul>
    <li>我是苹果</li>
    <li>我是香蕉</li>
    <li>我是橘子</li>
  </ul>
</div>

查看效果

8.理解动态组件

   通过使用保留的<component>元素,动态地绑定到它的 is 特性,我们可以让多个组件使用同一个挂载点,并动态的切换。
keep-alive: 如果把切换出去的组件留在内存中,可以保留它的状态或避免重新渲染,为此我们可以添加一个 keep-alive指令参数。
如下实现的tab切换代码:

<!DOCTYPE html>
<html>
  <body>
    <head>
      <title>演示Vue</title>
    </head>

    <h3>动态组件</h3>
    <template id="tab-01">
      <div>this is tab01</div>
    </template>
    <template id='tab-02'>
      <div>this is tab02</div>
    </template>
    <template id="tab-03">
      <div>this is tab03</div>
    </template>

    <div id="container1">
      <!-- 导航栏 -->
      <ul>
        <li>
          <a href="javascript:void(0)" @click="toggleTabs(tab01Text);">{{ tab01Text }}</a>
        </li>
        <li>
          <a href="javascript:void(0)" @click="toggleTabs(tab02Text);">{{ tab02Text }}</a>
        </li>
        <li>
          <a href="javascript:void(0)" @click="toggleTabs(tab03Text);">{{ tab03Text }}</a>
        </li>
      </ul>
      <!-- 点击导航后要切换的内容 -->
      <div class='content' style='height: 200px'>
        <!-- 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数 -->
        <keep-alive>
           <component :is="currentView"></component>
        </keep-alive>
      </div>
    </div>
  </body>
  <script src="./vue.js"></script>
  <script type="text/javascript">
     var tab01 = Vue.extend({
       template: '#tab-01'
     });
     var tab02 = Vue.extend({
       template: '#tab-02'
     });
     var tab03 = Vue.extend({
        template: '#tab-03'
     });
     // 新建vue实例
     var newVue = new Vue({
       el: '#container1',
       data: {
         tab01Text: "tab01",  // 菜单一
         tab02Text: "tab02",  // 菜单二
         tab03Text: "tab03",  // 菜单三
         currentView: "tab01" // 默认选中的导航栏
       },
       // 局部注册组件
       components: {
         tab01: tab01,
         tab02: tab02,
         tab03: tab03,
       },
       methods: {
         // 绑定tab的切换事件
         toggleTabs: function(tabText) {
           this.currentView = tabText;
         }
       }
     })
  </script>
</html>

查看效果