典型 MVVM 前端框架 Vue

Posted 云前端

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了典型 MVVM 前端框架 Vue相关的知识,希望对你有一定的参考价值。

一、引入

    <script ></script>
    <script ></script>

二、简单指令

1、起步文本插值

  <div id="app">
      {{message}}
  </div>
  var app=new Vue({
     el: "#app",
     data: {
        message:"hello"
     }
  })

Vue.js 的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统: 现在数据和 DOM 已经被绑定在一起,所有的元素都是响应式的。

2、声明式渲染

  <div id="app-2">
    <span v-bind:title="message">
      鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
  </div>
  
  var app2=new Vue({
    el:'#app-2',
    data:{
      message:'页面加载于'+new Date().toLocaleString()
    }
  })

指令带有前缀 v-,以表示它们是 Vue 提供的特殊属性 它们会在渲染的 DOM 上应用特殊的响应式行为。简言之,这里该指令的作用是:“将这个元素节点的 title 属性和 Vue 实例message属性保持一致”。

3、条件与循环

  <div id="app-3">
    <p v-if="seen">
      现在你看到我了
    </p>
  </div>
  var app3=new Vue({
    el:'#app-3',
    data:{
      seen:true
    }
  })

绑定 DOM 文本到数据,也可以绑定 DOM 结构到数据

  <div id="app-4">
    <ol>
      <li v-for="todo in todos">
        {{todo.text}}}
      </li>
    </ol>
  </div>
  var app4=new Vue({
    el:"#app-4",
    data:{
      todos:[
        {text:"学习javascript"},
        {text:"学习Vue"},
        {text:"整个牛项目"}
      ]
    }
  })

v-for 指令可以绑定数组的数据来渲染一个项目列表

4、处理用户输入

  <div id="app-5">
    <p>{{message}}</p>
    <button v-on:click="reverseMessage">逆转消息</button>
  </div>

绑定 DOM 文本到数据,也可以绑定 DOM 结构到数据 没有触碰 DOM,DOM 操作都由 Vue 来处理,你编写的代码不需要关注底层逻辑。

  var app5=new Vue({
    el:"#app-5",
    data:{
      message:"Hello Vue.js"
    },
    methods:{
      reverseMessage:function(){
        this.message=this.message.split('').reverse().join('')
      }
    }
  })

v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定

<div id="app-6">
  <p>{{message}}</p>
  <input v-model="message">
</div>

三、组件化应用构建

1.组件系统:允许我们使用小型、独立和通常可复用的组件构建大型应用。(仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树)

  • 在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例,在 Vue 中注册组件很简单:

    // 定义名为 todo-item 的新组件
    Vue.component('todo-item', {
    template: '<li>这是个待办项</li>'
    })
  • 现在你可以用它构建另一个组件模板:

    <ol>
    <!-- 创建一个 todo-item 组件的实例 -->
    <todo-item></todo-item>
    </ol>
  • 但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷,我们应该能将数据从父作用域传到子组件。让我们来修改一下组件的定义,使之能够接受一个属性:

    Vue.component('todo-item', {
      // todo-item 组件现在接受一个
      // "prop",类似于一个自定义属性
      // 这个属性名为 todo。
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
  • 现在,我们可以使用 v-bind 指令将 todo 传到每一个重复的组件中:

    <div id="app-7">
      <ol>
        <!--
          现在我们为每个 todo-item 提供 todo 对象
          todo 对象是变量,即其内容可以是动态的。
          我们也需要为每个组件提供一个“key”,晚些时候我们会做个解释。
        -->
        <todo-item
          v-for="item in groceryList"
          v-bind:todo="item"
          v-bind:key="item.id">
        </todo-item>
      </ol>
    </div>
    Vue.component('todo-item', {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { id: 0, text: '蔬菜' },
          { id: 1, text: '奶酪' },
          { id: 2, text: '随便其他什么人吃的东西' }
        ]
      }
    })
  • 这只是一个假设的例子,但是我们已经设法将应用分割成了两个更小的单元,子单元通过 props 接口实现了与父单元很好的解耦。我们现在可以进一步为我们的 todo-item 组件实现更复杂的模板和逻辑的改进,而不会影响到父单元。

    使用了组件的应用模板是什么样的例子:

      <div id="app">
        <app-nav></app-nav>
        <app-view>
          <app-sidebar></app-sidebar>
          <app-content></app-content>
        </app-view>
      </div>

四、模板语法:

Vue.js 使用了基于 html 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,在应用状态改变时,Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。

插值

(1) 文本

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:

<span>Message: {{ msg }}</span>

Mustache 标签将会被替代为对应数据对象上 msg 属性的值。无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。

通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上所有的数据绑定

<span v-once>这个将不会改变: {{ msg }}</span>

(2) 原始 HTML

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:

<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

(3) 特性

Mustache 语法不能作用在 HTML 特性上,遇到这种情况应该使用 v-bind 指令:

<div v-bind:id="dynamicId"></div>

(4) 使用 JavaScript 表达式

迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持

{ number + 1 }}
    
{{ ok ? 'YES' : 'NO' }}
    
{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math  Date 。你不应该在模板表达式中试图访问用户定义的全局变量。

(5) 指令 (Directives) 是带有 v- 前缀的特殊属性。当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

<p v-if="seen">现在你看到我了</p>

这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除元素。

  • 参数 

    一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML 属性:

    <a v-bind:href="url">...</a>

    在这里 href 是参数,告知 v-bind 指令将该元素的 href 属性与表达式 url 的值绑定。

    另一个例子是 v-on 指令,它用于监听 DOM 事件:

    <a v-on:click="doSomething">...</a>
  • 修饰符
    修饰符 (Modifiers) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

    <form v-on:submit.prevent="onSubmit">...</form>

    缩写: 

    • v-bind 缩写

      <!-- 完整语法 -->
      <a v-bind:href="url">...</a>
      
      <!-- 缩写 -->
      <a :href="url">...</a>
    • v-on 缩写

      <!-- 完整语法 -->
      <a v-on:click="doSomething">...</a>
      <!-- 缩写 -->
      <a @click="doSomething">...</a>

      它们看起来可能与普通的 HTML 略有不同,但 : 与 @ 对于特性名来说都是合法字符,在所有支持 Vue.js 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。

五、计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
    {{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。

基础例子

<div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

var vm = new Vue({
    el: '#example',
    data: {
      message: 'Hello'
    },
    computed: {
      // 计算属性的 getter
      reversedMessage: function () {
        // `this` 指向 vm 实例
        return this.message.split('').reverse().join('')
      }
    }
})

结果:

Original message: “Hello”
Computed reversed message: “olleH”

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作属性 vm.reversedMessage  getter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

计算属性缓存 vs 方法

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。 

计算属性只有在它的相关依赖发生改变时才会重新求值。 这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
    now: function () {
      return Date.now()
    }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。我们为什么需要缓存?假设我们有一个性能开销比较大的的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。 当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。 然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})
..

现在再运行 vm.fullName = ‘John Doe’ 时,setter 会被调用,vm.firstName  vm.lastName 也会相应地被更新。

侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。 这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。

当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>api</title>
  <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
  <script ></script>
  <script ></script>
  <script ></script>
  <script ></script>
        
</head>
<body>
  <div id="watch-example">
    <p>
      Ask a yes/no question:
      <input v-model="question">
    </p>
    <p>{{ answer }}</p>
  </div>
</body>
<script>
  var watchExampleVM = new Vue({
    el: '#watch-example',
    data: {
      question: '',
      answer: 'I cannot give you an answer until you ask a question!'
    },
    watch: {
      // 如果 `question` 发生改变,这个函数就会运行
      question: function (newQuestion) {
        this.answer = 'Waiting for you to stop typing...'
        this.getAnswer()
      }
    },
    methods: {
      // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
      // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
      // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
      // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
      // 请参考:https://lodash.com/docs#debounce
      getAnswer: _.debounce(
        function () {
          if (this.question.indexOf('?') === -1) {
              this.answer = 'Questions usually contain a question mark. ;-)'
              return
          }
          this.answer = 'Thinking...'
          var vm = this
          axios.get('https://yesno.wtf/api')
            .then(function (response) {
                vm.answer = _.capitalize(response.data.answer)
            })
            .catch(function (error) {
                vm.answer = 'Error! Could not reach the API. ' + error
            })
        },
        // 这是我们为判定用户停止输入等待的毫秒数
        500
      )
    }
  })
</script>
</html>

在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

五、Class 与 Style 绑定

绑定 HTML Class

对象语法

1、我们可以传给 v-bind:class 一个对象,以动态地切换 class:

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 active 这个 class 存在与否将取决于数据属性 isActive  truthiness

2、你可以在对象中传入更多属性来动态切换多个 class。此外,v-bind:class指令也可以与普通的 class 属性共存。当有如下模板:

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

结果渲染为:

当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 “static active text-danger”。

3、绑定的数据对象不必内联定义在模板里:

<div v-bind:class="classObject"></div>
data: {
    classObject: {
      active: true,
      'text-danger': false
    }
}

4、渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:

  <div v-bind:class="classObject"></div>
  data: {
    isActive: true,
    error: null
  },
  computed: {
    classObject: function () {
      return {
        active: this.isActive && !this.error,
        'text-danger': this.error && this.error.type === 'fatal'
      }
    }
  }

数组语法

1、我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:

  <div v-bind:class="[activeClass, errorClass]"></div>
  data: {
    activeClass: 'active',
    errorClass: 'text-danger'
  }

渲染为:

<div class="active text-danger"></div>

2、如果你也想根据条件切换列表中的 class,可以用三元表达式:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
这样写将始终添加 errorClass,但是只有在 isActive 是 truthy[1] 时才添加 activeClass。

3、不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>
用在组件上

当在一个自定义组件上使用 class 属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。

例如,如果你声明了这个组件:

Vue.component('my-component', {
    template: '<p class="foo bar">Hi</p>'
})

然后在使用它的时候添加一些 class:

<my-component class="baz boo"></my-component>

HTML 将被渲染为:

<p class="foo bar baz boo">Hi</p>

绑定内联样式

对象语法

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名:

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

直接绑定到一个样式对象通常更好,这会让模板更清晰:

<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

同样的,对象语法常常结合返回对象的计算属性使用。

数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

自动添加前缀 

 v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。

多重值 

从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex 

六、条件渲染

v-if

<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

在 元素上使用 v-if 条件渲染分组 因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含<template>元素。

<template v-if="ok">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</template>

v-else

你可以使用 v-else 指令来表示 v-if 的“else 块”:

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

v-else-if

v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:类似于 v-else,v-else-if 也必须紧跟在带 v-if 或者 v-else-if的元素之后。用 key 管理可复用的元素 ##### Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:

<template v-if="loginType === 'username'">
    <label>Username</label>
    <input placeholder="Enter your username">
</template>
<template v-else>
    <label>Email</label>
    <input placeholder="Enter your email address">
</template>

那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素, 不会被替换掉——仅仅是替换了它的 placeholder

这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

v-show

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

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

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-if 与 v-for 一起使用

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。

七、列表渲染

 v-for 把一个数组对应为一组元素:

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

 v-for 块中,我们拥有对父作用域属性的完全访问权限。v-for 还支持一个可选的第二个参数为当前项的索引。

<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
var example2 = new Vue({
  el: '#example-2',
  data: {
    parentMessage: 'Parent',
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

你也可以用 of 替代 in 作为分隔符,因为它是最接近 JavaScript 迭代器的语法:

<div v-for="item of items"></div>

对象的 v-for

  • 你可以用 v-for 通过一个对象的属性来迭代。

    <ul id="v-for-object" class="demo">
        <li v-for="value in object">
          {{ value }}
        </li>
    </ul>
    new Vue({
    el: '#v-for-object',
    data: {
      object: {
        firstName: 'John',
        lastName: 'Doe',
        age: 30
      }
    }
    })
  • 你也可以提供第二个的参数为键名:

    <div v-for="(value, key) in object">
    {{ key }}: {{ value }}
    </div>
  • 第三个参数为索引:

    <div v-for="(value, key, index) in object">
    {{ index }}. {{ key }}: {{ value }}
    </div>`

在遍历对象时,是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。

key

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一的 id,它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值 (在这里使用简写):

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

数组更新检测

(1) 变异方法

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:

push() 
pop() 
shift() 
unshift() 
splice() 
sort() 
reverse() 

(2) 替换数组

变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如:filter(), concat()  slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

(3) 注意事项

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  • 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

  • 当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,你可以使用 splice:
example1.items.splice(newLength)

对象更改检测注意事项

(1) 还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
    data: {
      userProfile: {
        name: 'Anika'
      }
    }
})

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

this.$set(this.userProfile, 'age', 27)

(2) 有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

Object.assign(this.userProfile, {
    age: 27,
    favoriteColor: 'Vue Green'
})

你应该这样做:

this.userProfile = Object.assign({}, this.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

显示过滤/排序结果

有时,我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个 method方法:

<li v-for="n in even(numbers)">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

一段取值范围的 v-for

v-for 也可以取整数。在这种情况下,它将重复多次模板。

<div>
    <span v-for="n in 10">{{ n }} </span>
</div>
v-for on a

类似于 v-if,你也可以利用带有 v-for 的 渲染多个元素。比如:

<ul>
 <template v-for="item in items">
  <li>{{ item.msg }}</li>
  <li class="divider"></li>
 </template>
</ul>
v-for with v-if

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

上面的代码只传递了未 complete  todos。 而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 )上。如:

<ul v-if="todos.length">
    <li v-for="todo in todos">
      {{ todo }}
    </li>
</ul>
<p v-else>No todos left!</p>

一个组件的 v-for

在自定义组件里,你可以像任何普通元素一样用 v-for 

<my-component v-for="item in items" :key="item.id"></my-component>
2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要用 props 
  <my-component
    v-for="(item, index) in items"
    v-bind:item="item"
    v-bind:index="index"
    v-bind:key="item.id"
  ></my-component>

不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。 下面是一个简单的 todo list的完整例子:

<div id="todo-list-example">
    <input
      v-model="newTodoText"
      v-on:keyup.enter="addNewTodo"
      placeholder="Add a todo"
    >
<ul>
  <li
    is="todo-item"
    v-for="(todo, index) in todos"
    v-bind:key="todo.id"
    v-bind:title="todo.title"
    v-on:remove="todos.splice(index, 1)"
  ></li>
</ul>
</div>

注意这里的 is=”todo-item” 属性。这种做法在使用 DOM 模板时是十分必要的,因为在ul元素内只有li元素会被看作有效内容。这样做实现的效果与 相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。

Vue.component('todo-item', {
template: '\
  <li>\
    {{ title }}\
    <button v-on:click="$emit(\'remove\')">X</button>\
  </li>\
',
props: ['title']
})

new Vue({
el: '#todo-list-example',
data: {
  newTodoText: '',
  todos: [
    {
      id: 1,
      title: 'Do the dishes',
    },
    {
      id: 2,
      title: 'Take out the trash',
    },
    {
      id: 3,
      title: 'Mow the lawn'
    }
  ],
  nextTodoId: 4
},
methods: {
  addNewTodo: function () {
    this.todos.push({
      id: this.nextTodoId++,
      title: this.newTodoText
    })
    this.newTodoText = ''
  }
}
})

八、监听事件

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

<div id="example-1">
    <button v-on:click="counter += 1">Add 1</button>
    <p>The button above has been clicked {{ counter }} times.</p>
</div>

var example1 = new Vue({
el: '#example-1',
    data: {
      counter: 0
    }
})

事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

<div id="example-2">
    <!-- `greet` 是在下面定义的方法名 -->
    <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
  name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
  greet: function (event) {
    // `this` 在方法里指向当前 Vue 实例
    alert('Hello ' + this.name + '!')
    // `event` 是原生 DOM 事件
    if (event) {
      alert(event.target.tagName)
    }
  }
}
})

  // 也可以用 `JavaScript` 直接调用方法
  example2.greet() // => 'Hello Vue.js!'

内联处理器中的方法

(1)除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <button v-on:click="say('what')">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message) {
      alert(message)
    }
  }
})

(2)有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn: function (message, event) {
    // 现在我们可以访问原生事件对象
    if (event) event.preventDefault()
    alert(message)
  }
}

事件修饰符

在事件处理程序中调用 event.preventDefault()  event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js  v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

.stop
.prevent
.capture
.self
.once
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
    
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
    
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
    
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
    
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处处理,然后才交由内部元素自身进行处理 -->
<div v-on:click.capture="doThis">...</div>
    
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>     

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self 会阻止所有的点击,而 @click.self.prevent 只会阻止对元素自身的点击。

2.1.4 新增
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。 按键修饰符 在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:
<!-- 同上 -->
<input v-on:keyup.enter="submit">
    
<!-- 缩写语法 -->
<input @keyup.enter="submit">
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

自动匹配按键修饰符

  • 你也可直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符

<input @keyup.page-down="onPageDown">

在上面的例子中,处理函数仅在 $event.key === ‘PageDown’ 时被调用。 有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,它们的内置别名应该是首选。

系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

.ctrl
.alt
.shift
.meta

注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在MIT  Lisp机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta被标记为“META”或者“Meta”

<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发keyup.ctrl。而单单释放 ctrl 也不会触发事件。

.exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>

鼠标按钮修饰符

.left
.right
.middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

九、组件

组件 (Component)  Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。 所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。

使用组件

(1)全局注册

我们已经知道,可以通过以下方式创建一个 Vue 实例:

new Vue({
  el: '#some-element',
  // 选项
})

要注册一个全局组件,可以使用 Vue.component(tagName, options)。例如:

Vue.component('my-component', {
  // 选项
})

组件在注册之后,便可以作为自定义元素 <my-component></my-component> 在一个实例的模板中使用。注意确保在初始化根实例之前注册组件: 实例:

<div id="example">
  <my-component></my-component>
</div>
// 注册
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
  el: '#example'
})

渲染为:

<div id="example">
  <div>A custom component!</div>
</div>

(2)局部注册

你不必把每个组件都注册到全局。你可以通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件:

var Child = {
  template: '<div>A custom component!</div>'
}

new Vue({
  // ...
  components: {
    // <my-component> 将只在父组件模板中可用
    'my-component': Child
  }
})

这种封装也适用于其它可注册的 Vue 功能,比如指令。

(3)DOM 模板解析注意事项

当使用 DOM 作为模板时 (例如,使用 el 选项来把 Vue 实例挂载到一个已有内容的元素上),你会受到 HTML 本身的一些限制,因为 Vue 只有在浏览器解析、规范化模板之后才能获取其内容。尤其要注意,像<ul>、<ol>、<table>  <select>这样的元素里允许包含的元素有限制,而另一些像<li>、<tr>  <option>这样的元素只能出现在某些特定元素的内部。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
  <my-row>...</my-row>
</table>

自定义组件 会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 is 特性:

<table>
  <tr is="my-row"></tr>
</table>

在模块系统中局部注册

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
    
export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

Prop*

(1)使用 Prop 传递数据

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。 子组件要显式地用 props 选项声明它预期的数据:

Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 也可以在模板中使用
  // 同样也可以在 vm 实例中通过 this.message 来使用
  template: '<span>{{ message }}</span>'
})

然后我们可以这样向它传入一个普通字符串:

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

(2)camelCase vs. kebab-case

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

Vue.component('child', {
  // 在 JavaScript 中使用 camelCase
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})
<!-- 在 HTML 中使用 kebab-case -->
<child my-message="hello!"></child>

(3)动态 Prop

与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:

<div>
    <input v-model="parentMsg">
    <br>
    <child v-bind:my-message="parentMsg"></child>
</div>

你也可以使用 v-bind 的缩写语法:

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

单向数据流:

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

2. 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

prop验证:

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组且一定会从一个工厂函数返回默认值
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

Prop类型检查:String、Number、Boolean、Function、Object、Array、Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

你可以使用:

Vue.component('blog-post', {
  props: {
    author: Person
  }
})

来验证 author prop 的值是否是通过 new Person 创建的。

生命周期函数:

  beforeCreate(创建前),

  created(创建后),

  beforeMount(载入前),

  mounted(载入后),

  beforeUpdate(更新前),

  updated(更新后),

  beforeDestroy(销毁前),

  destroyed(销毁后)

不得不提的一个官方脚手架:vue-cli(需了解node jswebpack npm等)

命令行:

1.npm install -g vue-cli

2.vue init webpack my-project

3.cd my project

4.npm install

5.npm run dev

如果报错:Module build failed: Error: No parser and no file path given,couldn't infer a parser.

解决方法:

npm i prettier@~1.12.0

然后重新启动:

npm run dev

仅几行命令即快速构建了一个web单页应用,涵盖vue、vue-router、axios、webpack等.

官方技术文档:

  • vue.js官方文档及API

  • vue-cli

  • 路由管理器vue-router



(end)



----------------------------------------


转载请注明出处 


以上是关于典型 MVVM 前端框架 Vue的主要内容,如果未能解决你的问题,请参考以下文章

前端MVVM框架之“Vue.js入门篇”

好程序员web前端分享MVVM框架Vue实现原理

Vue.js前端MVVM框架实战篇

前端MVVM模式及其在Vue和React中的体现

前端的mvvm是啥意思

Vue-第一天vue.js与MVVM设计模式