精华内容
下载资源
问答
  • Vue

    2020-01-15 16:52:59
    Vue 之前做项目是前后端都会做的,主要用的是Vue,定期来温故而知新一下。 在官网的基础上整理了一下,并总结了一些常见的知识点。 1、Vue是什么 是一套基于构建用户界面的渐进式框架; 自底向上逐层应用; ...

    目录

    Vue

    之前做项目是前后端都会做的,主要用的是Vue,定期来温故而知新一下。
    在官网的基础上整理了一下,并总结了一些常见的知识点。

    基础

    Vue是什么

    是一套基于构建用户界面的渐进式框架;
    自底向上逐层应用;
    vue的核心库只关注图层;
    完全能够单页应用提供驱动;

    安装

    npm install vue
    

    介绍

    声明式渲染

    Vue的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统。

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

    指令:带有前缀v-,表示是Vue提供的特殊特性。

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

    条件与循环

    v-if、v-else、v-for

    <div id="app-3">
       <p v-if="seen">现在你看到我了</p>
       <p v-else>看不到我了</p>
       
        <ol>
        <li v-for="todo in todos">
          {{ todo.text }}
        </li>
      </ol>
      
    </div>
    
    var app3 = new Vue({
      el: '#app-3',
      data: {
        seen: true,
        todos: [
          { text: '学习 JavaScript' },
          { text: '学习 Vue' },
          { text: '整个牛项目' }
        ]
      }
    })
    
    // 现在你看到我了
    // 1. 学习 JavaScript
    // 2. 学习 Vue
    // 3. 整个牛项目
    
    

    处理用户输入

    v-on指令添加一个事件监听器。
    v-model实现表单输入和应用状态之间的双向绑定。

    <div id="app-5">
    
      <p>{{ message }}</p>
      <button v-on:click="reverseMessage">反转消息</button>
    	
     <input v-model="message">
     
    </div>
    
    var app5 = new Vue({
      el: '#app-5',
      data: {
        message: 'Hello Vue.js!'
      },
      methods: {
        reverseMessage: function () {
          this.message = this.message.split('').reverse().join('')
        }
      }
    })
    
    // Hello Vue!
    
    

    组件化应用构建

    在Vue里,一个组件本质上是一个拥有预定义选项的一个Vue实例。

    // 定义名为 todo-item 的新组件
    Vue.component('todo-item', {
      template: '<li>这是个待办项</li>'
    })
    
    var app = new Vue(...)
    
    <ol>
      <!-- 创建一个 todo-item 组件的实例 -->
      <todo-item></todo-item>
    </ol>
    
    // 通过从父作用域将数据传到子组件
    Vue.component('todo-item', {
      // todo-item 组件现在接受一个
      // "prop",类似于一个自定义特性。
      // 这个 prop 名为 todo。
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
    
    <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>
    
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { id: 0, text: '蔬菜' },
          { id: 1, text: '奶酪' },
          { id: 2, text: '随便其它什么人吃的东西' }
        ]
      }
    })
    

    Vue实例

    1. 创建一个Vue实例;
    2. vue实例创建后,会将data对象中的所有属性加入到Vue的响应式系统中,当属性的值发生改变时,视图将会产生‘响应’,匹配更新后的值;
    
    var data = { a: 1 }  数据对象:初始值
    
    // Object.freeze(obj) 会阻止修改现有的属性,意味着响应系统无法再追踪变化。
    
    var vm = new Vue({
      // 选项
      data: data
    })
    
    

    实例生命周期钩子

    在这里插入图片描述在这里插入图片描述

    beforeCreate

    新对象诞生。
    在对象初始化之前执行。

    created

    创建具有默认特性的对象。

    beforeMount

    对象在DOM中适合形状。
    检查是否有任何模板可用于要在DOM中呈现的对象。
    没有找到,会将HTML视为模板。

    mounted

    已安装。DOM已准备就绪并放置在页面内。
    它将数据放入模板并创建可呈现元素。

    beforeUpdate

    更改已完成,但尚未准备好更新DOM。

    updated

    更新,在DOM中呈现的更改。

    beforeDestroy

    对象准备销毁。

    destoryed

    销毁。对象停止并从内存中删除。

    模板语法

    插值

    文本

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

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

    使用v-once指令,也能一次性的插值,当数据改变时,插值处的内容不会更新。

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

    为了输出真正的HTML,需要使用v-html指令。

    Attribute

    Mustache语法不能作用在Html attribute上,,应使用v-bind指令。

    <div v-bind:id="dynamicId"></div>
    
    使用JavaScript表达式
    {{ number + 1 }}
    
    {{ ok ? 'YES' : 'NO' }}
    
    {{ message.split('').reverse().join('') }}
    
    <div v-bind:id="'list-' + id"></div>
    

    指令

    DIrectives是带有v-前缀的特殊attribute。
    指令的职责是,当表达式的值改变时,将其产生的连带影响,相应式的作用于DOM。

    参数

    一些指令接收一个参数,在指令名称之后以冒号表示。

    <a v-bind:href="url">...</a>
    在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定。
    

    v-on也可以用于监听DOM事件。

    <a v-on:click="doSomething">...</a>
    
    动态参数
    <a v-bind:[attributeName]="url"> ... </a>
    
    <a v-on:[eventName]="doSomething"> ... </a>
    

    动态参数的值的约束:
    动态参数预期会求出一个字符串,异常为null,null值会被显性的用于移除绑定。

    对动态参数表达式的约束:
    空格、引号放在Html attribute名里是无效的。

    修饰符

    修饰符以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

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

    v-前缀用来识别模板中Vue特殊的attribute。

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

    计算属性和侦听器

    计算属性

    计算属性缓存VS方法

    可以将同一函数定义为一个方法而不是一个计算属性。
    计算属性是基于它们的响应式依赖进行缓存的。

    计算属性VS侦听属性

    当一些数据随着其它数据的变动而改变时,更好的做法是使用计算属性。

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

    计算属性默认只有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]
        }
      }
    }
    

    侦听器

    watch选项来响应数据的变化,当需要再数据变化时执行异步或开销较大的操作比较有用。

    Class与Style

    绑定HTML Class

    对象语法

    传给v-bind:class一个对象(可以传入更多的属性),以动态切换class

    <div v-bind:class="{ active: isActive }"></div>
    
    <div
      class="static"
      v-bind:class="{ active: isActive, 'text-danger': hasError }"
    ></div>
    
    
    数组语法
    <div v-bind:class="[activeClass, errorClass]"></div>
    
    三元表达式
    <div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
    
    
    用在组件上
    1. 声明组件
    Vue.component('my-component', {
      template: '<p class="foo bar">Hi</p>'
    })
    
    2. 给组件添加class
    <my-component class="baz boo"></my-component>
    
    3. HTML被渲染为
    <p class="foo bar baz boo">Hi</p>
     
    

    绑定内联样式

    对象语法

    v-bind:style

    <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'
      }
    }
    
    数组语法
    <div v-bind:style="[baseStyles, overridingStyles]"></div>
    
    自动添加前缀

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

    多重值

    2.3.0后可以为style绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值。

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

    条件渲染

    v-if

    必须添加到一个元素上

    <div v-if="type === 'A'">
      A
    </div>
    <div v-else-if="type === 'B'">
      B
    </div>
    <div v-else-if="type === 'C'">
      C
    </div>
    <div v-else>
      Not A/B/C
    </div>
    
    用key管理可复用的元素

    v-show

    带有v-show的元素始终会被渲染并保留在DOM中。

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

    v-if VS v-show

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

    v-if是惰性的:如果初始渲染时条件为假,则什么也不做,直到条件第一次为真时,才会开始渲染条件块。

    v-show则是不管初始条件是什么,元素总是会被渲染,并且只是简单的基于CSS进行切换。

    一般来说,v-if有更高的切换开销,v-show有更高的初始渲染开销。
    如果频繁切换,使用v-show较好;
    如果运行时条件很少改变,使用v-if较好。

    v-if与v-for一起使用

    一起使用时,v-for会有更高的优先级。

    列表渲染

    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
    <div v-for="item of items"></div>
    

    维护状态

    Vue正在更新使用v-for渲染的元素列表,默认使用“就地更新”策略。
    如果数据项的顺序被改变,Vue将不会移动DOM元素来皮牌数据项的顺序,而是就地更新每个元素,并确保他们在每个索引位置正确渲染。

    数组更新监测

    变异方法

    Vue将被侦听的数组的变异方法进行了包裹,所以它们也会触发视图更新:

    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
    替换数组

    非变异方法则不会改变原始数组,总是返回一个新的数组,可使用新数组替换旧数组,如:filter() concat() slice()。

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

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

    • 当你利用索引直接设置一个数组项时

    • 当修改数组的长度时

      var vm = new Vue({
        data: {
          items: ['a', 'b', 'c']
        }
      })
      vm.items[1] = 'x' // 不是响应性的
      vm.items.length = 2 // 不是响应性的
      

    通过以下方式可以实现和vm.items[indexOfItem] = newValue相同的效果,同时也将在响应式系统内触发状态更新

    // Vue.set
    Vue.set(vm.items, indexOfItem, newValue)
    vm.$set(vm.items, indexOfItem, newValue)
    
    // Array.prototype.splice
    vm.items.splice(indexOfItem, 1, newValue)
    vm.items.splice(newLength)
    
    

    对象变更检测注意事项

    由于JavaScript的限制,vue不能检测对象属性的添加或删除。

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

    对于已经创建的实例,Vue不允许动态添加根级别的响应式属性。
    可以通过Vue.set(object, propertyName, value)方法向嵌套对象添加响应式的属性。

    var vm = new Vue({
      data: {
        userProfile: {
          name: 'Anika'
        }
      }
    })
    
    Vue.set(vm.userProfile, 'age', 27)
    vm.$set(vm.userProfile, 'age', 27)
    
    

    如果为已有对象赋值多个新属性,应该用两个对象的属性创建一个新的对象

    不要这样:
    Object.assign(vm.userProfile, {
      age: 27,
      favoriteColor: 'Vue Green'
    })
    
    应该这样:
    vm.userProfile = Object.assign({}, vm.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
        })
      }
    }
    
    计算属性不适用时,使用方法:
    <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-if一起使用

    v-for的优先级比v-if高

    在组件上使用v-for

    2.2.0+版本里,v-for使用时,key是必须的。

    事件处理

    监听事件

    v-on指令监听DOM事件

    事件处理方法

    v-on:click接收一个需要调用的方法名

    内联处理器中的方法
    <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)
        }
      }
    })
    

    事件修饰符

    使用修饰符时,顺序很重要;
    相应的代码会以同样的顺序产生。

    • .stop
    • .prevent
    • .capture
    • .self
    • .once
    • .passive
    <!-- 阻止单击事件继续传播 -->
    <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>
    
    阻止所有的点击
    v-on:click.prevent.self 
    
    阻止对元素自身的点击
    v-on:click.self.prevent
    
    2.1.4新增
    <!-- 点击事件将只会触发一次 -->
    <a v-on:click.once="doThis"></a>
    
    2.3.0新增
    <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
    <!-- 而不会等待 `onScroll` 完成  -->
    <!-- 这其中包含 `event.preventDefault()` 的情况 -->
    <div v-on:scroll.passive="onScroll">...</div>
    【不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,
    同时浏览器可能会向你展示一个警告。
    请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。】
    
    

    按键修饰符

    v-on监听键盘事件时添加按键修饰符

    <!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
    <input v-on:keyup.enter="submit">
    
    .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

    会限制处理函数仅响应特定的鼠标按钮。

    为什么在Html中监听事件?

    • 扫一眼html模板便能轻松定位在JavaScript代码里对应的方法
    • 因为你无需在JavaScript里手动绑定事件,你的ViewModel代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于测试
    • 当一个ViewModel被销毁时,所有的事件处理器都会自动被删除,无需担心如何清理它们

    表单输入绑定

    基础用法

    v-model负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

    v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。

    v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:

    • text、textarea元素使用value属性和input属性;
    • checkbox、radio使用checked属性和change事件;
    • select字段将value作为prop并将change作为事件;

    修饰符

    .lazy

    默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步,添加lazy修饰,从而转变为使用change事件进行同步

    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg" >
    
    .number

    自动将用户的输入值转为数值类型

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

    自动过滤用户输入的首尾空白字符

    <input v-model.trim="msg">
    

    组件基础

    基本示例

    组件是可复用的Vue实例,且带有一个名字

    // 定义一个名为 button-counter 的新组件
    Vue.component('button-counter', {
      data: function () {
        return {
          count: 0
        }
      },
      template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    })
    
    

    组件的复用

    可以复用任意多次

    <div id="components-demo">
      <button-counter></button-counter>
      <button-counter></button-counter>
      <button-counter></button-counter>
    </div>
    
    data必须是一个函数

    一个组件的data必须是一个函数

    data: function () {
      return {
        count: 0
      }
    }
    

    组件的组织

    组件的注册类型:

    • 全局注册:可以用在其被注册之后的任何新创建的vue根实例,也包括其组件树中的所有子组件的模板中;
    • 局部注册

    通过Prop向子组件传递数据

    单个根元素

    每个组件必须只有一个根元素。

    监听子组件事件

    $emit
    $event

    在组件上使用v-model
    <input v-model="searchText">
    
    等价于:
    <input
      v-bind:value="searchText"
      v-on:input="searchText = $event.target.value"
    >
    
    Vue.component('custom-input', {
      props: ['value'],
      template: `
        <input
          v-bind:value="value"
          v-on:input="$emit('input', $event.target.value)"
        >
      `
    })
    <custom-input v-model="searchText"></custom-input>
    
    
    通过插槽分发内容

    :向一个组件传递内容

    Vue.component('alert-box', {
      template: `
        <div class="demo-alert-box">
          <strong>Error!</strong>
          <slot></slot>
        </div>
      `
    })
    
    动态组件

    在不同组件之间进行动态切换,vue的元素加一个特殊的is特性来实现

    <!-- 组件会在 `currentTabComponent` 改变时改变 -->
    <component v-bind:is="currentTabComponent"></component>
    

    深入了解组件

    组件注册

    组件名

    字母全小写,且包含一个连字符。

    组件名大小写

    **使用kebab-case
    短横线分割命名

    Vue.component('my-component-name', { /* ... */ })
    

    使用PascalCase**
    首字母大写命名

    Vue.component('MyComponentName', { /* ... */ })
    

    全局注册

    全局注册组件之后,可以用在任何新创建的Vue根实例的模板中。

    局部注册

    局部注册的组件在其子组件中不可用。

    模块系统

    import/require

    在模块系统中局部注册
    import ComponentA from './ComponentA'
    import ComponentC from './ComponentC'
    
    export default {
      components: {
        ComponentA,
        ComponentC
      },
      // ...
    }
    
    基础组件的自动化全局注册

    全局注册的行为必须在根Vue实例创建之前发生。

    Prop

    Prop的大小写

    驼峰命名法的prop名需要使用其等价的短横线命名。

    Vue.component('blog-post', {
      // 在 JavaScript 中是 camelCase 的
      props: ['postTitle'],
      template: '<h3>{{ postTitle }}</h3>'
    })
    
    <!-- 在 HTML 中是 kebab-case 的 -->
    <blog-post post-title="hello!"></blog-post>
    

    Prop类型

    props: {
      title: String,
      likes: Number,
      isPublished: Boolean,
      commentIds: Array,
      author: Object,
      callback: Function,
      contactsPromise: Promise // or any other constructor
    }
    

    传递静态或动态Prop

    v-bind动态赋值

    <!-- 动态赋予一个变量的值 -->
    <blog-post v-bind:title="post.title"></blog-post>
    
    <!-- 动态赋予一个复杂表达式的值 -->
    <blog-post
      v-bind:title="post.title + ' by ' + post.author.name"
    ></blog-post>
    
    传入一个数字
    传入一个布尔值
    传入一个数组
    传入一个对象
    传入一个对象的所有属性

    单向数据流

    改变prop:

    • 这个prop用来传递一个初始值,这个子组件接下来希望将其作为一个本地的prop数据来使用。
    props: ['initialCounter'],
    data: function () {
      return {
        counter: this.initialCounter
      }
    }
    
    • 这个prop以一种原始的值传入且需要进行转换
    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    

    Prop验证

    Vue.component('my-component', {
      props: {
        // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
        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
          }
        }
      }
    })
    
    类型检查

    type类型:

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol

    非Prop的Attribute

    一个非prop的attribute是指传向一个组件,但该组件并没有相应prop定义的attribute。

    替换/合并已有的Attribute

    从外部提供给组件的值会替换掉组件内部设置好的值。
    class与style attribute会使值合并起来。

    禁用Attribute继承

    如果不希望组件的根元素继承attribute,可以在组件的选项中设置inheritAttrs: false,

    Vue.component('my-component', {
      inheritAttrs: false,
      // ...
    })
    

    配合实例$attrs属性使用,可以手动决定这些attribute会被赋予哪个元素。

    Vue.component('base-input', {
      inheritAttrs: false,
      props: ['label', 'value'],
      template: `
        <label>
          {{ label }}
          <input
            v-bind="$attrs"
            v-bind:value="value"
            v-on:input="$emit('input', $event.target.value)"
          >
        </label>
      `
    })
    

    inheritAttrs: false选项不会影响style和class的绑定。

    该模式允许你在使用基础组件的时候更像是使用原始的HTML元素,而不会担心哪个元素是根元素。

    <base-input
      v-model="username"
      required
      placeholder="Enter your username"
    ></base-input>
    

    自定义事件

    事件名

    事件名不存在任何自动化大小写转换。
    推荐使用kebab-case事件名。

    自定义组件的v-model

    一个组件上的v-model默认会利用名为value的prop和名为input的事件。
    但是单选框和复选框等输入控件可能会将value特性用于不同目的。
    model可以避免这样的冲突

    Vue.component('base-checkbox', {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: Boolean
      },
      template: `
        <input
          type="checkbox"
          v-bind:checked="checked"
          v-on:change="$emit('change', $event.target.checked)"
        >
      `
    })
    
    lovingVue值将会传入这个名为checked的prop
    <base-checkbox v-model="lovingVue"></base-checkbox>
    

    将原生事件绑定到组件

    v-on.native修饰符

    <base-input v-on:focus.native="onFocus"></base-input>
    

    有时候这种情况不是很好的,比如.native监听器将静默失败

    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
    

    解决这个问题,Vue提供了一个$listeners属性,是一个对象,里面包含了作用在这个组件上的所有监听器

    Vue.component('base-input', {
      inheritAttrs: false,
      props: ['label', 'value'],
      computed: {
        inputListeners: function () {
          var vm = this
          // `Object.assign` 将所有的对象合并为一个新对象
          return Object.assign({},
            // 我们从父级添加所有的监听器
            this.$listeners,
            // 然后我们添加自定义监听器,
            // 或覆写一些监听器的行为
            {
              // 这里确保组件配合 `v-model` 的工作
              input: function (event) {
                vm.$emit('input', event.target.value)
              }
            }
          )
        }
      },
      template: `
        <label>
          {{ label }}
          <input
            v-bind="$attrs"
            v-bind:value="value"
            v-on="inputListeners"
          >
        </label>
      `
    })
    

    .sync修饰符

    双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
    通过.sync修饰符:

    <text-document v-bind:title.sync="doc.title"></text-document>
    

    带有.sync修饰符的v-bind不能和表达式一起使用

    当用一个对象同时设置多个prop的时候,也可以将.sync修饰符和v-bind配合使用。

    doc对象中的每个属性都作为一个独立的prop传进去,然后各自添加用于更新的v-on监听器。
    <text-document v-bind.sync="doc"></text-document>
    

    将v-bind.sync用在一个字面量的对象上,如:v-bind.sync=”{ title: doc.title }”是无法工作的,因为要考虑很多边缘因素。

    插槽

    插槽内容

    Vue实现了一套内容分发的API,这套API设计将元素作为承载分发内容的出口。

    合成组件
    <navigation-link url="/profile">
      Your Profile
    </navigation-link>
    
    <navigation-link>模板中可能写成:
    <a
      v-bind:href="url"
      class="nav-link"
    >
      <slot></slot>
    </a>
    
    当组件渲染的时候,<slot></slot>就会替换为“Your Profile”,插槽内可以包含任何模板代码
    如果<navigation-link>没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会抛弃。
    
    
    编译作用域

    父级模板里的所有内容都是在父级作用域中编译的;
    子模板里的所有内容都是在子作用域中编译的。

    <navigation-link url="/profile">
      Clicking here will send you to: {{ url }}
      <!--
      这里的 `url` 会是 undefined,因为 "/profile" 是
      _传递给_ <navigation-link> 的而不是
      在 <navigation-link> 组件*内部*定义的。
      -->
    </navigation-link>
    
    后备内容

    为一个插槽设置具体的后备(默认的)内容是很有用的,它只会在没有提供内容的时候被渲染。

    <button type="submit">
      <slot>Submit</slot>
    </button>
    
    具名插槽

    v-slot只能添加在上

    <base-layout>
      <template v-slot:header>
        <h1>Here might be a page title</h1>
      </template>
    
      <template v-slot:default>
        <p>A paragraph for the main content.</p>
        <p>And another one.</p>
      </template>
    
      <template v-slot:footer>
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    
    作用域插槽

    绑定在元素上的特性被称为插槽prop。
    将包含所有插槽prop的对象命名为slotProps。
    在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽prop的名字。

    <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </current-user>
    
    独占默认插槽的缩写语法

    当被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板来使用。
    未指明的内容对应默认插槽,不带参数的v-slot被假定对应默认插槽。

    <current-user v-slot:default="slotProps">
      {{ slotProps.user.firstName }}
    </current-user>
    

    默认插槽的缩写语法不能和具名插槽混用,会导致作用域不明确
    只要出现多个插槽,始终为所有的插槽使用完整的基于语法

    <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    
      <template v-slot:other="otherSlotProps">
        ...
      </template>
    </current-user>
    
    解构插槽Prop

    作用域插槽的内部工作原理是将你的插槽内容包含在一个传入单个参数的函数里
    可以使用ES5解构来传入具体的插槽prop

    <current-user v-slot="{ user }">
      {{ user.firstName }}
    </current-user>
    
    <current-user v-slot="{ user: person }">
      {{ person.firstName }}
    </current-user>
    
    

    动态插槽名

    动态指令参数也可以来定义动态的插槽名

    <base-layout>
      <template v-slot:[dynamicSlotName]>
        ...
      </template>
    </base-layout>
    

    具名插槽的缩写

    v-slot缩写,把参数之前的所有内容替换为#

    <base-layout>
      <template #header>
        <h1>Here might be a page title</h1>
      </template>
    
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    
      <template #footer>
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    

    其他示例

    插槽prop允许我们将插槽转换为可复用的模板,这些模板可以基于输入的prop渲染出不同的内容。

    动态组件&异步组件

    在动态组件上使用keep-alive

    在多标签的界面中使用is特性来切换不同的组件。

    <!-- 失活的组件将会被缓存!-->
    <keep-alive>
      <component v-bind:is="currentTabComponent"></component>
    </keep-alive>
    

    要求被切换到的组件都有自己的名字,不论是通过组件的name选项还是局部/全局注册。

    异步组件

    Vue允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。
    Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

    Vue.component('async-example', function (resolve, reject) {
      setTimeout(function () {
        // 向 `resolve` 回调传递组件定义
        resolve({
          template: '<div>I am async!</div>'
        })
      }, 1000)
    })
    
    Vue.component('async-webpack-example', function (resolve) {
      // 这个特殊的 `require` 语法将会告诉 webpack
      // 自动将你的构建代码切割成多个包,这些包
      // 会通过 Ajax 请求加载
      require(['./my-async-component'], resolve)
    })
    
    

    当使用局部注册的时候,也可以直接提供一个返回Promise的函数。

    new Vue({
      // ...
      components: {
        'my-component': () => import('./my-async-component')
      }
    })
    
    处理加载状态
    const AsyncComponent = () => ({
      // 需要加载的组件 (应该是一个 `Promise` 对象)
      component: import('./MyComponent.vue'),
      // 异步组件加载时使用的组件
      loading: LoadingComponent,
      // 加载失败时使用的组件
      error: ErrorComponent,
      // 展示加载时组件的延时时间。默认值是 200 (毫秒)
      delay: 200,
      // 如果提供了超时时间且组件加载也超时了,
      // 则使用加载失败时使用的组件。默认值是:`Infinity`
      timeout: 3000
    })
    

    处理边界情况

    访问元素&组件

    访问根实例

    在每个new Vue实例的子组件中,其根实例可以通过$root属性进行访问。

    // Vue 根实例
    new Vue({
      data: {
        foo: 1
      },
      computed: {
        bar: function () { /* ... */ }
      },
      methods: {
        baz: function () { /* ... */ }
      }
    })
    
    所有的子组件都可以将这个实例作为一个全局的store来访问或使用。
    // 获取根组件的数据
    this.$root.foo
    
    // 写入根组件的数据
    this.$root.foo = 2
    
    // 访问根组件的计算属性
    this.$root.bar
    
    // 调用根组件的方法
    this.$root.baz()
    
    访问父级组件实例

    $parent属性可以用来从一个子组件访问父组件的实例。

    访问子组件实例或子元素

    ref特性为这个子组件赋予一个ID引用。

    <base-input ref="usernameInput"></base-input>
    通过this.$refs.usernameInput 访问<base-input>实例
    
    使用一个类似的ref提供对内部这个指定元素的访问
    <input ref="input">
    
    通过父级组件定义方法
    methods: {
      // 用来从父级组件聚焦输入框
      focus: function () {
        this.$refs.input.focus()
      }
    }
    这样就允许父级组件通过下面的代码聚焦 <base-input> 里的输入框:
    this.$refs.usernameInput.focus()
    
    

    ref和v-for一起使用的时候,得到的引用将会是一个包含了对应数据源的这些子组件的数组。

    r e f s 只 会 在 组 件 渲 染 完 成 之 后 生 效 , 并 且 他 们 不 是 响 应 式 的 。 仅 用 于 一 个 直 接 操 作 子 组 件 的 “ 逃 生 舱 ” , 尽 量 避 免 在 模 板 或 计 算 属 性 中 访 问 refs只会在组件渲染完成之后生效,并且他们不是响应式的。仅用于一个直接操作子组件的“逃生舱”, 尽量避免在模板或计算属性中访问 refs访refs.

    依赖注入

    provide选项允许我们指定我们想要提供给后代组件的数据/方法。

    <google-map>
      <google-map-region v-bind:shape="cityBoundaries">
        <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
      </google-map-region>
    </google-map>
    
    provide: function () {
      return {
        getMap: this.getMap
      }
    }
    
    

    任何后代组件中,可以使用inject选项来接收指定的我们想要添加在这个实例上的属性。

    inject: ['getMap']
    

    依赖注入使我们不用担心我们可能会改变/移除一些子组件依赖的东西,
    同时这些组件之间的接口是始终明确定义的,就和props一样。

    负面影响:它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
    同时所提供的属性是非响应式的。

    程序化的事件侦听器

    • 通过 $on(eventName, eventHandler) 侦听一个事件
    • 通过 $once(eventName,eventHandler) 一次性侦听一个事件
    • 通过 $off(eventName, eventHandler) 停止侦听一个事件

    循环引用

    递归组件

    组件是可以在它们自己的模板中调用自身的,通过name选项。
    确保递归调用是条件性的

    name: 'unique-name-of-my-component'
    
    组件之间的循环引用

    如果需要构建一个文件目录树,像访达或资源管理器那样,可以有一个组件

    <p>
      <span>{{ folder.name }}</span>
      <tree-folder-contents :children="folder.children"/>
    </p>
    

    模板

    <ul>
      <li v-for="child in children">
        <tree-folder v-if="child.children" :folder="child"/>
        <span v-else>{{ child.name }}</span>
      </li>
    </ul>
    

    模板定义的替代品

    内联模板

    当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。

    内联模板需要定义在Vue所属的DOM元素内。

    <my-component inline-template>
      <div>
        <p>These are compiled as the component's own template.</p>
        <p>Not parent's transclusion content.</p>
      </div>
    </my-component>
    
    x-template

    x-template模板需要定义在Vue所属的DOM元素外。

    <script type="text/x-template" id="hello-world-template">
      <p>Hello hello hello</p>
    </script>
    
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })
    
    

    控制更新

    强制更新

    $forceUpdate

    通过v-once创建低开销的静态组件

    在根元素中添加v-once特性确保这些内容只计算一次然后缓存起来。

    Vue.component('terms-of-service', {
      template: `
        <div v-once>
          <h1>Terms of Service</h1>
          ... a lot of static content ...
        </div>
      `
    })
    

    过渡&动画

    进入/离开&列表过渡

    单元素/组件的过渡

    Vue提供了transition的封装组件,下列情形,可以给任何元素和组件添加进入/离开过渡:

    • 条件渲染v-if
    • 条件展示v-show
    • 动态组件
    • 组件根节点
    <div id="demo">
      <button v-on:click="show = !show">
        Toggle
      </button>
      <transition name="fade">
        <p v-if="show">hello</p>
      </transition>
    </div>
    
    new Vue({
      el: '#demo',
      data: {
        show: true
      }
    })
    
    .fade-enter-active, .fade-leave-active {
      transition: opacity .5s;
    }
    .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
      opacity: 0;
    }
    
    
    过渡的类名

    在进入/离开的过渡中,会有6个class切换。

    1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入后的下一帧移除;
    2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
    3. v-enter-to:2.1.8版以上,定义进入过渡的结束状态。在元素被插入之后下一帧生效,在过渡/动画完成之后移除。
    4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
    5. v-leave-active:定义来开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
    6. v-leave-to:2.1.8版以上,定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效,在过渡/动画完成之后移除。
      在这里插入图片描述

    对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则 v- 是这些类名的默认前缀。如果你使用了 ,那么 v-enter 会替换为 my-transition-enter。

    CSS过渡
    <div id="example-1">
      <button @click="show = !show">
        Toggle render
      </button>
      <transition name="slide-fade">
        <p v-if="show">hello</p>
      </transition>
    </div>
    
    new Vue({
      el: '#example-1',
      data: {
        show: true
      }
    })
    
    /* 可以设置不同的进入和离开动画 */
    /* 设置持续时间和动画函数 */
    .slide-fade-enter-active {
      transition: all .3s ease;
    }
    .slide-fade-leave-active {
      transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }
    .slide-fade-enter, .slide-fade-leave-to
    /* .slide-fade-leave-active for below version 2.1.8 */ {
      transform: translateX(10px);
      opacity: 0;
    }
    
    
    CSS动画

    和css过渡的区别是,在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发时删除。

    自定义过渡的类名

    通过以下特性来自定义过渡的类名:

    • enter-class
    • enter-active-class
    • enter-to-class
    • leave-class
    • leave-active-class
    • leave-to-class

    优先级高于普通的类名

    同时使用过渡和动画

    使用type特性并设置animation或transition来明确声明你需要Vue监听的类型。

    显性的过渡持续时间

    默认情况下,Vue会等待其在过渡效果的根元素的第一个transitioned或animationed事件。

    组件上的duration属性定制一个显性的过渡持续时间(以毫秒计)

    <transition :duration="1000">...</transition>
    
    定制进入和移出时间
    <transition :duration="{ enter: 500, leave: 800 }">...</transition>
    
    
    JavaScript钩子
    <transition
      v-on:before-enter="beforeEnter"
      v-on:enter="enter"
      v-on:after-enter="afterEnter"
      v-on:enter-cancelled="enterCancelled"
    
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave"
      v-on:leave-cancelled="leaveCancelled"
    >
      <!-- ... -->
    </transition>
    
    // ...
    methods: {
      // --------
      // 进入中
      // --------
    
      beforeEnter: function (el) {
        // ...
      },
      // 当与 CSS 结合使用时
      // 回调函数 done 是可选的
      enter: function (el, done) {
        // ...
        done()
      },
      afterEnter: function (el) {
        // ...
      },
      enterCancelled: function (el) {
        // ...
      },
    
      // --------
      // 离开时
      // --------
    
      beforeLeave: function (el) {
        // ...
      },
      // 当与 CSS 结合使用时
      // 回调函数 done 是可选的
      leave: function (el, done) {
        // ...
        done()
      },
      afterLeave: function (el) {
        // ...
      },
      // leaveCancelled 只用于 v-show 中
      leaveCancelled: function (el) {
        // ...
      }
    }
    
    

    当只用JavaScript过渡的时候,在enter和leave中必须使用done进行回调,否则,它们将被同步调用,过渡会立即完成。

    对于禁用JavaScript过渡的元素添加v-bind:css=“false”,Vue会跳过Css的检测,可以避免过程中Css的影响。

    初始渲染的过渡

    通过appear特性设置节点在初始渲染的过渡

    <transition appear>
      <!-- ... -->
    </transition>
    
    自定义css类名
    <transition
      appear
      appear-class="custom-appear-class"
      appear-to-class="custom-appear-to-class" (2.1.8+)
      appear-active-class="custom-appear-active-class"
    >
      <!-- ... -->
    </transition>
    
    自定义JavaScript钩子
    <transition
      appear
      v-on:before-appear="customBeforeAppearHook"
      v-on:appear="customAppearHook"
      v-on:after-appear="customAfterAppearHook"
      v-on:appear-cancelled="customAppearCancelledHook"
    >
      <!-- ... -->
    </transition>
    
    

    多个元素的过渡

    多个组件的过渡,对于原生标签可以使用v-if/v-else。

    <transition>
      <table v-if="items.length > 0">
        <!-- ... -->
      </table>
      <p v-else>Sorry, no items found.</p>
    </transition>
    

    当有相同标签名的元素切换时,需要通过key特性设置唯一的值来标记以让Vue区分它们,
    否则Vue为了效率只会替换相同标签内部的内容。

    <transition>
      <button v-if="isEditing" key="save">
        Save
      </button>
      <button v-else key="edit">
        Edit
      </button>
    </transition>
    
    <transition>
      <button v-bind:key="isEditing">
        {{ isEditing ? 'Save' : 'Edit' }}
      </button>
    </transition>
    
    <transition>
      <button v-bind:key="docState">
        {{ buttonMessage }}
      </button>
    </transition>
    // ...
    computed: {
      buttonMessage: function () {
        switch (this.docState) {
          case 'saved': return 'Edit'
          case 'edited': return 'Save'
          case 'editing': return 'Cancel'
        }
      }
    }
    
    过渡模式
    • in-out:新元素先进行过渡,完成之后当前元素过渡离开;
    • out-in:当前元素先进行过渡,完成之后新元素过渡进入;
    <transition name="fade" mode="out-in">
      <!-- ... the buttons ... -->
    </transition>
    

    多个组件的过渡

    不需要使用key,而是使用动态组件。

    <transition name="component-fade" mode="out-in">
      <component v-bind:is="view"></component>
    </transition>
    
    new Vue({
      el: '#transition-components-demo',
      data: {
        view: 'v-a'
      },
      components: {
        'v-a': {
          template: '<div>Component A</div>'
        },
        'v-b': {
          template: '<div>Component B</div>'
        }
      }
    })
    
    .component-fade-enter-active, .component-fade-leave-active {
      transition: opacity .3s ease;
    }
    .component-fade-enter, .component-fade-leave-to
    /* .component-fade-leave-active for below version 2.1.8 */ {
      opacity: 0;
    }
    
    

    列表过渡

    渲染整个列表,使用组件,特点:

    • 不同于,它会以一个真实元素呈现:默认为一个,也可以通过tag特性更换为其他元素;
    • 过渡模式不可用,因为我们不再相互切换特有的元素;
    • 内部元素总是需要提供唯一的key属性值;
    • css过渡的类将应用在内部的元素中,而不是这个组/容器本身。
    列表的进入/离开过渡
    列表的排序过渡

    组件不仅可以可以进入和离开动画,还可以改变定位。
    v-move特性会在元素的改变定位的过程中应用,对于设置过渡的切换时机和过渡曲线非常有用。

    Vue使用一个叫FLP简单的动画队列,使用transforms将元素从之前的位置平滑过渡新的位置。
    FLP过渡的元素不能设置为display:inline,可以设置为display:inline-block或放flex中。

    列表的交错过渡

    通过data属性与JavaScript通信,就可以实现列表的交错过渡。

    可复用的过渡

    过渡可以通过Vue的组件系统实现复用。
    要创建一个可复用过渡组件,需要将或作为根组件,然后将子组件放置在其中就可以 。

    使用template
    Vue.component('my-special-transition', {
      template: '\
        <transition\
          name="very-special-transition"\
          mode="out-in"\
          v-on:before-enter="beforeEnter"\
          v-on:after-enter="afterEnter"\
        >\
          <slot></slot>\
        </transition>\
      ',
      methods: {
        beforeEnter: function (el) {
          // ...
        },
        afterEnter: function (el) {
          // ...
        }
      }
    })
    
    函数式组件:
    Vue.component('my-special-transition', {
      functional: true,
      render: function (createElement, context) {
        var data = {
          props: {
            name: 'very-special-transition',
            mode: 'out-in'
          },
          on: {
            beforeEnter: function (el) {
              // ...
            },
            afterEnter: function (el) {
              // ...
            }
          }
        }
        return createElement('transition', data, context.children)
      }
    })
    

    动态过渡

    动态过渡最基本的是通过name特性来绑定动态值。

    <transition v-bind:name="transitionName">
      <!-- ... -->
    </transition>
    

    创建动态过渡的最终方案是通过接受props来动态修改之前的过渡。

    状态过渡

    状态动画与侦听器

    通过侦听器我们能监听到任何数值属性的数值更新。

    动态状态过渡

    数据背后状态过渡会实时更新。

    把过渡放到组件里

    管理太多的状态过渡会很快的增加Vue实例或组件的复杂性,很多的动画可以提取到专用的子组件。

    赋予设计以生命

    可复用性&组合

    混入

    基础

    混入mixin提供了一种灵活的方式来分发Vue组件中的可复用功能。
    一个混入对象可以包含任意组件选项。
    当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

    // 定义一个混入对象
    var myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin!')
        }
      }
    }
    
    // 定义一个使用混入对象的组件
    var Component = Vue.extend({
      mixins: [myMixin]
    })
    
    var component = new Component() // => "hello from mixin!"
    

    选项合并

    当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。

    数据对象在内部会进行递归合并,发生冲突时以组件数据优先

    var mixin = {
      data: function () {
        return {
          message: 'hello',
          foo: 'abc'
        }
      }
    }
    
    new Vue({
      mixins: [mixin],
      data: function () {
        return {
          message: 'goodbye',
          bar: 'def'
        }
      },
      created: function () {
        console.log(this.$data)
        // => { message: "goodbye", foo: "abc", bar: "def" }
      }
    })
    

    同名钩子函数将合为为一个数组,因此都将被调用。
    混入对象的钩子将在组件自身钩子之前调用。

    var mixin = {
      created: function () {
        console.log('混入对象的钩子被调用')
      }
    }
    
    new Vue({
      mixins: [mixin],
      created: function () {
        console.log('组件钩子被调用')
      }
    })
    
    // => "混入对象的钩子被调用"
    // => "组件钩子被调用"
    

    值为对象的选项,如:methods、components、directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

    var mixin = {
      methods: {
        foo: function () {
          console.log('foo')
        },
        conflicting: function () {
          console.log('from mixin')
        }
      }
    }
    
    var vm = new Vue({
      mixins: [mixin],
      methods: {
        bar: function () {
          console.log('bar')
        },
        conflicting: function () {
          console.log('from self')
        }
      }
    })
    
    vm.foo() // => "foo"
    vm.bar() // => "bar"
    vm.conflicting() // => "from self"
    

    全局混入

    一旦全局混入,将影响每一个之后创建的Vue实例。

    / 为自定义的选项 'myOption' 注入一个处理器。
    Vue.mixin({
      created: function () {
        var myOption = this.$options.myOption
        if (myOption) {
          console.log(myOption)
        }
      }
    })
    
    new Vue({
      myOption: 'hello!'
    })
    // => "hello!"
    

    自定义选项合并策略

    自定义选项将使用默认策略,即简单的覆盖已有值。
    如果想让自定义选项以自定义逻辑合并,可以向Vue.config.optionMergeStrategies添加一个函数。

    Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
      // 返回合并后的值
    }
    

    对于多数值为对象的选项,可以使用与methods相同的合并策略

    var strategies = Vue.config.optionMergeStrategies
    strategies.myOption = strategies.methods
    

    自定义指令

    简介

    Vue允许注册自定义指令

    // 注册一个全局自定义指令 `v-focus`
    Vue.directive('focus', {
      // 当被绑定的元素插入到 DOM 中时……
      inserted: function (el) {
        // 聚焦元素
        el.focus()
      }
    })
    
    注册局部指令
    directives: {
      focus: {
        // 指令的定义
        inserted: function (el) {
          el.focus()
        }
      }
    }
    
    使用
    <input v-focus>
    

    钩子函数

    一个指令定义对象可以提供如下几个钩子函数:

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置;
    • inserted:被绑定元素插入父节点时调用(仅保证父节点存在,不一定已被插入文档中);
    • update:所在组件的VNode更新时调用,但可能发生在其子VNode更新之前。指令的值可能发生变化也可能没有。
    • componentUpdated:指令所在组件的VNode及其子VNode全部更新后调用。
    • unbind:只调用一次,指令与元素解绑时调用。

    钩子函数参数

    • el:指令所绑定的元素,可以用来直接操作 DOM
    • binding:一个对象,包含以下属性:
      - name:指令名,不包括 v- 前缀。
      - value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2
      - oldValue:指令绑定的前一个值,仅在 update 和componentUpdated 钩子中可用。无论值是否改变都可用。
      - expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
      - arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
      - modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
    • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
    • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

    【除了el,其它参数都是只读的,不可修改】

    <div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
    
    Vue.directive('demo', {
      bind: function (el, binding, vnode) {
        var s = JSON.stringify
        el.innerHTML =
          'name: '       + s(binding.name) + '<br>' +
          'value: '      + s(binding.value) + '<br>' +
          'expression: ' + s(binding.expression) + '<br>' +
          'argument: '   + s(binding.arg) + '<br>' +
          'modifiers: '  + s(binding.modifiers) + '<br>' +
          'vnode keys: ' + Object.keys(vnode).join(', ')
      }
    })
    
    new Vue({
      el: '#hook-arguments-example',
      data: {
        message: 'hello!'
      }
    })
    
    
    动态指令参数

    指令的参数是可以动态的。

    函数简写

    在bind和update时触发相同行为,而不关心其它钩子

    Vue.directive('color-swatch', function (el, binding) {
      el.style.backgroundColor = binding.value
    })
    

    对象字面量

    如果需要传入多个值

    <div v-demo="{ color: 'white', text: 'hello!' }"></div>
    
    Vue.directive('demo', function (el, binding) {
      console.log(binding.value.color) // => "white"
      console.log(binding.value.text)  // => "hello!"
    })
    
    

    渲染函数&JSX

    基础

    render函数渲染

    Vue.component('anchored-heading', {
      render: function (createElement) {
        return createElement(
          'h' + this.level,   // 标签名称
          this.$slots.default // 子节点数组
        )
      },
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    })
    

    节点、树以及虚拟DOM

    每个元素都是一个节点,每段文字也是一个节点,注释也是节点
    一个节点就是页面的一部分,每个节点都可以有孩子节点。

    你只需要告诉Vue你希望页面上的HTML是什么,这可以是在一个模板里:

    <h1>{{ blogTitle }}</h1>
    
    或render函数里:
    render: function (createElement) {
      return createElement('h1', this.blogTitle)
    }
    
    虚拟DOM

    Vue通过建立一个虚拟的DOM来追踪自己要如何改变真实DOM。

    return createElement('h1', this.blogTitle)
    
    

    返回的并不是一个实际的DOM,可能是createNodeDescription,因为它所包含的信息
    会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息,
    这样的节点就是“虚拟节点”,VNode。
    “虚拟DOM”是我们对由Vue组件树建立起来的整个VNode树的称呼。

    createElement参数

    // @returns {VNode}
    createElement(
      // {String | Object | Function}
      // 一个 HTML 标签名、组件选项对象,或者
      // resolve 了上述任何一种的一个 async 函数。必填项。
      'div',
    
      // {Object}
      // 一个与模板中属性对应的数据对象。可选。
      {
        // (详情见下一节)
      },
    
      // {String | Array}
      // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
      // 也可以使用字符串来生成“文本虚拟节点”。可选。
      [
        '先写一些文字',
        createElement('h1', '一则头条'),
        createElement(MyComponent, {
          props: {
            someProp: 'foobar'
          }
        })
      ]
    )
    
    深入数据对象
    {
      // 与 `v-bind:class` 的 API 相同,
      // 接受一个字符串、对象或字符串和对象组成的数组
      'class': {
        foo: true,
        bar: false
      },
      // 与 `v-bind:style` 的 API 相同,
      // 接受一个字符串、对象,或对象组成的数组
      style: {
        color: 'red',
        fontSize: '14px'
      },
      // 普通的 HTML attribute
      attrs: {
        id: 'foo'
      },
      // 组件 prop
      props: {
        myProp: 'bar'
      },
      // DOM 属性
      domProps: {
        innerHTML: 'baz'
      },
      // 事件监听器在 `on` 属性内,
      // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
      // 需要在处理函数中手动检查 keyCode。
      on: {
        click: this.clickHandler
      },
      // 仅用于组件,用于监听原生事件,而不是组件内部使用
      // `vm.$emit` 触发的事件。
      nativeOn: {
        click: this.nativeClickHandler
      },
      // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
      // 赋值,因为 Vue 已经自动为你进行了同步。
      directives: [
        {
          name: 'my-custom-directive',
          value: '2',
          expression: '1 + 1',
          arg: 'foo',
          modifiers: {
            bar: true
          }
        }
      ],
      // 作用域插槽的格式为
      // { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => createElement('span', props.text)
      },
      // 如果组件是其它组件的子组件,需为插槽指定名称
      slot: 'name-of-slot',
      // 其它特殊顶层属性
      key: 'myKey',
      ref: 'myRef',
      // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
      // 那么 `$refs.myRef` 会变成一个数组。
      refInFor: true
    }
    
    约束

    VNode必须唯一:
    组件树中的所有VNode必须是唯一的。
    如果需要重复很多次的元素/组件,可以使用工厂函数来实现。

    使用JavaScript代替模板功能

    v-if和v-for
    <ul v-if="items.length">
      <li v-for="item in items">{{ item.name }}</li>
    </ul>
    <p v-else>No items found.</p>
    
    等于:
    props: ['items'],
    render: function (createElement) {
      if (this.items.length) {
        return createElement('ul', this.items.map(function (item) {
          return createElement('li', item.name)
        }))
      } else {
        return createElement('p', 'No items found.')
      }
    }
    
    v-model

    渲染函数没有与v-model的直接对应–需要自己实现响应的逻辑

    props: ['value'],
    render: function (createElement) {
      var self = this
      return createElement('input', {
        domProps: {
          value: self.value
        },
        on: {
          input: function (event) {
            self.$emit('input', event.target.value)
          }
        }
      })
    }
    
    事件&按键修饰符

    对于.passive, .capture, .once这些事件修饰符,Vue提供了响应的前缀可以用于on

    修饰符前缀
    .passive&
    .capture!
    .once~
    .capture.once或.once.capture~!
    on: {
      '!click': this.doThisInCapturingMode,
      '~keyup': this.doThisOnce,
      '~!mouseover': this.doThisOnceInCapturingMode
    }
    

    对于所有其它的修饰符,私有前缀都不是必须的,可以在事件处理函数中使用事件方法。

    插槽

    通过this.$slots访问静态插槽的内容,每个插槽都是一个VNode数组。

    render: function (createElement) {
      // `<div><slot></slot></div>`
      return createElement('div', this.$slots.default)
    }
    

    也可以通过this.$scopedSlots访问作用域插槽,每个作用域插槽都是一个返回若干VNode的函数。

    props: ['message'],
    render: function (createElement) {
      // `<div><slot :text="message"></slot></div>`
      return createElement('div', [
        this.$scopedSlots.default({
          text: this.message
        })
      ])
    }
    

    用渲染函数向子组件中传递作用域插槽,可以利用VNode数据对象中的scopedSlots字段。

    render: function (createElement) {
      return createElement('div', [
        createElement('child', {
          // 在数据对象中传递 `scopedSlots`
          // 格式为 { name: props => VNode | Array<VNode> }
          scopedSlots: {
            default: function (props) {
              return createElement('span', props.text)
            }
          }
        })
      ])
    }
    

    JSX

    Babel插件,用于在Vue中使用JSX语法,它可以让我们回到更接近于模板的语法上。

    import AnchoredHeading from './AnchoredHeading.vue'
    
    new Vue({
      el: '#demo',
      render: function (h) {
        return (
          <AnchoredHeading level={1}>
            <span>Hello</span> world!
          </AnchoredHeading>
        )
      }
    })
    将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。
    我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 
    自动注入 const h = this.$createElement,这样你就可以去掉 (h) 参数了。
    
    

    函数式组件

    一个函数式组件应该像这样:

    Vue.component('my-component', {
      functional: true,
      // Props 是可选的
      props: {
        // ...
      },
      // 为了弥补缺少的实例
      // 提供第二个参数作为上下文
      render: function (createElement, context) {
        // ...
      }
    })
    

    2.3.0之前版本,如果一个函数式组件想要接受prop,props选项时必须的。
    2.3.0或以上的版本,可以省略props,所有组件的attribute都会自动隐式解析为prop
    当使用函数式组件时,该引用会是HTMLElement,因为他们是无状态的也是无实例的。

    组件需要的一切都是通过context参数传递的,包括以下字段:

    • props:提供所有 prop 的对象
    • children: VNode 子节点的数组
    • slots: 一个函数,返回了包含所有插槽的对象
    • scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
    • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
    • parent:对父组件的引用
    • listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
    • injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。

    因为函数式组件只是函数,所以渲染开销也低很多。

    在作为包装组件时,也非常有用,如:

    • 程序化地在多个组件中选择一个来代为渲染;
    • 在将children、props、data传递给子组件之前操作他们;
    向子元素或子组件传递attribute和事件

    在普通组件中,没有被定义为 prop 的 attribute 会自动添加到组件的根元素上,将已有的同名 attribute 进行替换或与其进行智能合并。
    然而函数式组件要求你显式定义该行为:

    Vue.component('my-functional-button', {
      functional: true,
      render: function (createElement, context) {
        // 完全透传任何 attribute、事件监听器、子节点等。
        return createElement('button', context.data, context.children)
      }
    })
    
    slots()和children对比

    插件

    插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

    • 添加全局方法或者属性。如: vue-custom-element
    • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
    • 通过全局混入来添加一些组件选项。如 vue-router
    • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现
    • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
    使用插件

    通过全局方法Vue.use()使用插件,在调用new Vue之前完成

    // 调用 `MyPlugin.install(Vue)`
    Vue.use(MyPlugin)
    
    new Vue({
      // ...组件选项
    })
    

    Vue.use会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。

    开发插件

    Vue的插件应该暴露一个install方法,这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象。

    过滤器

    Vue可以自定义过滤器,用于文本格式化。

    过滤器用在两个地方:
    双花括号插值和v-bind表达式

    过滤器应该添加在JavaScript表达式的尾部,由“管道”符号指示

    <!-- 在双花括号中 -->
    {{ message | capitalize }}
    
    <!-- 在 `v-bind` 中 -->
    <div v-bind:id="rawId | formatId"></div>
    

    或在一个组件的选项中定义本地的过滤器:

    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
    

    或在创建Vue实例之前全局定义过滤器:

    Vue.filter('capitalize', function (value) {
      if (!value) return ''
      value = value.toString()
      return value.charAt(0).toUpperCase() + value.slice(1)
    })
    
    new Vue({
      // ...
    })
    

    当全局过滤器和局部过滤器重名时,会采用局部过滤器。
    过滤器总接收表达式的值作为第一个参数。
    过滤器可以串联:

    {{ message | filterA | filterB }}
    filterA 被定义为接收单个参数的过滤器函数,
    表达式 message 的值将作为参数传入到函数中。
    然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,
    将 filterA 的结果传递到 filterB 中。
    
    

    过滤器是JavaScript函数,因此可以接收参数

    {{ message | filterA('arg1', arg2) }}
    
    filterA 被定义为接收三个参数的过滤器函数。
    其中 message 的值作为第一个参数,
    普通字符串 'arg1' 作为第二个参数,
    表达式 arg2 的值作为第三个参数
    
    

    TypeScript支持

    推荐配置

    // tsconfig.json
    {
      "compilerOptions": {
        // 与 Vue 的浏览器支持保持一致
        "target": "es5",
        // 这可以对 `this` 上的数据属性进行更严格的推断
        "strict": true,
        // 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
        "module": "es2015",
        "moduleResolution": "node"
      }
    }
    

    开发工具链

    工程创建

    # 1. 如果没有安装 Vue CLI 就先安装
    npm install --global @vue/cli
    
    # 2. 创建一个新工程,并选择 "Manually select features (手动选择特性)" 选项
    vue create my-project-name
    

    基础用法

    import Vue from 'vue'
    const Component = Vue.extend({
      // 类型推断已启用
    })
    
    const Component = {
      // 这里不会有类型推断,
      // 因为TypeScript不能确认这是Vue组件的选项
    }
    

    基于类的Vue组件

    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    // @Component 修饰符注明了此类为一个 Vue 组件
    @Component({
      // 所有的组件选项都可以放在这里
      template: '<button @click="onClick">Click!</button>'
    })
    export default class MyComponent extends Vue {
      // 初始数据可以直接声明为实例的属性
      message: string = 'Hello!'
    
      // 组件方法也可以直接声明为实例的方法
      onClick (): void {
        window.alert(this.message)
      }
    }
    

    增加类型以配合插件使用

    // 1. 确保在声明补充的类型之前导入 'vue'
    import Vue from 'vue'
    
    // 2. 定制一个文件,设置你想要补充的类型
    //    在 types/vue.d.ts 里 Vue 有构造函数类型
    declare module 'vue/types/vue' {
    // 3. 声明为 Vue 补充的东西
      interface Vue {
        $myProperty: string
      }
    }
    // ComponentOptions 声明于 types/options.d.ts 之中
    declare module 'vue/types/options' {
      interface ComponentOptions<V extends Vue> {
        myOption?: string
      }
    }
    

    标注返回值

    Vue的声明文件天生就有循环性,Ts可能在推断某个方法的类型的时候存在困难。
    因此,可能需要在render或computed里的方法上标注返回值。

    import Vue, { VNode } from 'vue'
    
    const Component = Vue.extend({
      data () {
        return {
          msg: 'Hello'
        }
      },
      methods: {
        // 需要标注有 `this` 参与运算的返回值类型
        greet (): string {
          return this.msg + ' world'
        }
      },
      computed: {
        // 需要标注
        greeting(): string {
          return this.greet() + '!'
        }
      },
      // `createElement` 是可推导的,但是 `render` 需要返回值类型
      render (createElement): VNode {
        return createElement('div', this.greeting)
      }
    })
    

    生成环境部署

    开启生产环境模式

    不使用构建工具

    使用构建工具

    webpack
    在webpack4+中,使用mode选项

    module.exports = {
      mode: 'production'
    }
    

    在webpack3及更低版本,使用DefinePlugin:

    var webpack = require('webpack')
    
    module.exports = {
      // ...
      plugins: [
        // ...
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify('production')
        })
      ]
    }
    

    模板预编译

    当使用DOM内模板或JavaScript内的字符串模板时,模板会在运行时被编译为渲染函数。
    预编译模板最简单的方式就是使用单文件组件—相关的构建设置会自动把预编译处理好,
    所以构建好的代码已经包含了编译出来的渲染函数,而不是原始的模板字符串。

    使用webpack,可以用vue-template-loader。

    提取组件CSS

    webpack+vue-loader

    跟踪运行时错误

    Vue.config.errorHandler

    路由

    官方路由

    vue-router

    从零开始简单的路由

    const NotFound = { template: '<p>Page not found</p>' }
    const Home = { template: '<p>home page</p>' }
    const About = { template: '<p>about page</p>' }
    
    const routes = {
      '/': Home,
      '/about': About
    }
    
    new Vue({
      el: '#app',
      data: {
        currentRoute: window.location.pathname
      },
      computed: {
        ViewComponent () {
          return routes[this.currentRoute] || NotFound
        }
      },
      render (h) { return h(this.ViewComponent) }
    })
    

    状态管理

    类Flux状态管理的官方实现

    简单状态管理起步使用

    store模式
    所有store中state的改变,都放置在store自身的action中去管理。
    这种集中式状态管理能够被更容易理解哪种类型的mutation将会发生,以及他们是如何被触发。

    每个实例/组件仍然可以拥有和管理自己的私有状态。

    不应该在action中替换原始的状态对象,组件和store需要引用同一个共享对象,mutation才能够被观察。

    安全

    报告安全漏洞

    Vue的安全措施

    HTML

    不论使用模板还是渲染函数,内部都会被自动转义,避免了脚本注入。

    Attribute绑定

    动态attribute绑定也会自动被转义

    潜在危险

    注入HTML

    Vue会自动转义Html内容,以避免向应用意外注入可执行的Html。然某些情况下,你清楚这些html是安全的,可以显示渲染Html内容:

    1. 使用模板
    <div v-html="userProvidedHtml"></div>
    
    2. 使用渲染函数
    h('div', {
      domProps: {
        innerHTML: this.userProvidedHtml
      }
    })
    
    3. 使用基于JSX的渲染函数
    <div domPropsInnerHTML={this.userProvidedHtml}></div>
    
    
    注入URL

    类似:

    <a v-bind:href="userProvidedUrl">
      click me
    </a>
    
    

    如果未对该URL进行过滤以防止通过JavaScript:来执行JavaScript,会有潜在安全问题。

    注入样式

    类似:

    <a
      v-bind:href="sanitizedUrl"
      v-bind:style="userProvidedStyles"
    >
      click me
    </a>
    

    sanitizedUrl假如已被过滤,是一个真实的url且没有JavaScript,但通过userProvidedStyles,恶意用户扔可以提供CSS来进行“点击诈骗”。

    推荐只允许在一个iframe沙盒内进行CSS的完全控制,或让用户通过一个样式绑定来控制,推荐使用对象语法且只允许用户提供特定的可以安全控制的property的值。

    <a
      v-bind:href="sanitizedUrl"
      v-bind:style="{
        color: userProvidedColor,
        background: userProvidedBackground
      }"
    >
      click me
    </a>
    
    注入JavaScript

    我们强烈不鼓励使用 Vue 渲染

    每个 HTML 元素都有接受 JavaScript 字符串作为其值的 attribute,如 onclick、onfocus 和 onmouseenter。将用户提供的 JavaScript 绑定到它们任意当中都是一个潜在的安全风险,因此应该避免。

    内在

    深入响应式原理

    每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。在这里插入图片描述

    检测变化的注意事项

    Vue无法检测到对象属性的添加或删除。
    由于Vue会在初始化实例时对属性执行getter/setter转化,所以属性必须在data对象上存在才能让Vue将它转化为响应式的。

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

    对于已经创建的实例,Vue不允许动态添加根级别的响应式属性,可以使用Vue.set(Object, propertyName, value)方法向嵌套对象添加响应式属性。
    也可以使用vm.$set实例方法,即全局this.set的别名
    对已有对象赋值多个新属性,使用Object.assign()或_.extend(),并将原对象与混合进去的对象一起创建一个新的对象。

    Vue.set(vm.someObject, 'b', 2)
    
    this.$set(this.someObject,'b',2)
    
    // 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
    this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
    
    

    声明响应式属性

    Vue不允许动态添加跟级别响应式属性,需要再初始化实例前声明所有根级别响应式属性,哪怕是空值

    var vm = new Vue({
      data: {
        // 声明 message 为一个空值字符串
        message: ''
      },
      template: '<div>{{ message }}</div>'
    })
    // 之后设置 `message`
    vm.message = 'Hello!'
    
    

    异步更新队列

    Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
    如果同一个 watcher 被多次触发,只会被推入到队列中一次。

    展开全文
  • Vue.js 客户端模板注入漏洞

    千次阅读 2021-01-29 21:18:19
    漏洞详情 由于程序员代码编写不当,将导致用户输入的数据,可以改变客户端模版的执行逻辑,从而造成XSS漏洞漏洞影响 漏洞分析 漏洞编写模板如下: const express = require('express'); const helmet = ...

    漏洞详情

    由于程序员代码编写不当,将导致用户输入的数据,可以改变客户端模版的执行逻辑,从而造成XSS漏洞。

    漏洞影响

    漏洞分析

    漏洞编写模板如下:

    const express = require('express');
    const helmet = require('helmet');
    const escapeHTML = require('escape-html');
    const app = express();
    app.use(helmet());
    app.get('/', (req, res) => {
      res.set('Content-Type', 'text/html');
      const name = req.query.name
      res.status(200).send(`<!DOCTYPE html>
    <html>
    <body>
      <div id="app">
        <h1>Hello ?name=${escapeHTML(name)}</h1>
      </div>
      <footer>
      <a href="https://github.com/azu/vue-client-side-template-injection-example">Source Code</a>
      </footer>
      <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
      <script>
          new Vue({
            el: '#app'
          });
      </script>
    </body>
    </html>`);
    });
    module.exports = app;

    漏洞利用

    POC:

    hxxp://host/?name={{this.constructor.constructor('alert(document.domain)')()}}

    image.png

    参考:

    https://github.com/azu/vue-client-side-template-injection-example

     

     

    展开全文
  • Object props 通过 Vue2 的设计漏洞——对 object 和 array 内的值变更不做监测,我们直接将 props 传入 object 即可。 父组件 App.vue : <template> <div id="app"> <Child :obj="obj">Child> div> template> ...

    前言

    很多情况下,我们更需要传入子组件 props 的值(也就是父组件中的值)是一个双向的,也就是说子组件更改时,父组件也伴随更改。

    特别是当子组件使用了某些第三方 UI 组件库的时候,在子组件内进行了 v-model 双向绑定,而该值又需父组件传入 props 进行依赖,于是当第三方组件事件被触发导致 v-model 值发生改变,就产生了冲突,因为此时父级传入的 props 不可更改。

    下面简单说明三种解决的方法。

    v-bind:sync

    这是官网推荐的方法,当子组件需要更改父级传入的 props 时,调用 this.$emit() 即可。

    example

    父组件 App.vue

    <template>
      <div id="app">
        <Child :text.sync="text"></Child>
      </div>
    </template>
    
    <script>
    import Child from "@/components/Child";
    export default {
      name: "App",
      components: {
        Child
      },
      data() {
        return {
          text: 123
        };
      }
    };
    </script>
    

    子组件 Child.vue

    <template>
      <div>
        {{ text }}
      </div>
    </template>
    
    <script>
    export default {
      props: {
        text: {
          type: [Number, String]
        }
      },
      mounted() {
        // × 当使用 .sync 时不能对父组件传来的 props 直接赋值
        // this.text = "111";
        
        // √ 需要使用 this.$emit 进行更新父组件传入的 props
        this.$emit("update:text", "111");
      }
    };
    </script>
    

    官网关于 .sync 的更多介绍:.sync 修饰符

    缺点

    正如前言所说,假如我们使用了第三方的组件库,他的 v-model 改变是不受控的,类似与下面这种情况:

    <template>
      <div>
        <input type="text" v-model="text" />
      </div>
    </template>
    

    此时我们父组件传入的 text 会被 v-model 自动更改导致报错(不能直接更改),这是一个很大的麻烦。

    注:虽然组件库都有 v-model 的值被更改事件,但这还需要引入其他变量写很多逻辑,令人难以理解,这不是我们期望的。

    Object props

    通过 Vue2 的设计漏洞——对 object 和 array 内的值变更不做监测,我们直接将 props 传入 object 即可。

    父组件 App.vue

    <template>
      <div id="app">
        <Child :obj="obj"></Child>
      </div>
    </template>
    
    <script>
    import Child from "@/components/Child";
    export default {
      name: "App",
      components: {
        Child
      },
      data() {
        return {
          obj: { key: "value" }
        };
      }
    };
    </script>
    

    子组件 Child.vue

    <template>
      <div>
        <!-- 不会报错,正常使用 -->
        <input type="text" v-model="obj.key" />
        {{ obj }}
      </div>
    </template>
    
    <script>
    export default {
      props: {
        obj: {
          type: Object
        }
      },
      mounted() {
      	// 同步支持设定新属性
      	this.obj.key2 = 'value2'
      	// 异步必须使用 Vue.$set
        setTimeout(() => {
          this.$set(this.obj, "key3", "value3");
        }, 3000);
      }
    };
    </script>
    

    我们需要绑定的就是 object 的某个值,当 object 内的某个键值被改变,不会产生报错,即使在 v-model 双向绑定下也可正常使用。

    特别需要注意的是,当进行异步操作时需要使用 $set() 方法,否则不会触发视图更新。

    Array props

    同上 object props 所说,array 的某一项值改变也不会被 Vue2 监测到。

    父组件 App.vue

    <template>
      <div id="app">
        <Child :array="array"></Child>
      </div>
    </template>
    
    <script>
    import Child from "@/components/Child";
    export default {
      name: "App",
      components: {
        Child
      },
      data() {
        return {
          array: [1, 2, 3]
        };
      }
    };
    </script>
    

    子组件 Child.vue

    <template>
      <div>
        <input type="text" v-model="array[0]" />
        {{ array }}
      </div>
    </template>
    
    <script>
    export default {
      props: {
        array: {
          type: Array
        }
      },
      mounted() {
       	// 同步支持设定新属性
      	this.array[1] = 'value2'
      	// 异步必须使用 Vue.$set
        setTimeout(() => {
          this.$set(this.array, 2, "value3");
        }, 3000);
      }
    };
    </script>
    

    总结

    综上来看,在父子组件的数据流中,建议将可能会双向同步更新的数据放入一个 object 内,务必注意当异步时需要使用 $set() 方法。

    展开全文
  • 还是没有修复漏洞,百度了很久都没有我这种情况,我也看了npm audit,也是要说需要手动安装漏洞 ![图片说明](https://img-ask.csdn.net/upload/202003/08/1583674637_935897.jpg) 图没有截完下面接着 ![图片...
  • vue实践中的常见知识漏洞001

    千次阅读 2018-07-13 13:45:21
    本文主要总结了vue实际开发项目当中应该如何解决一些实际的开发问题,可能你认为很简单,但短时间内也许你并没解决思路的。 建议阅读时间:15-25min 更多精彩内容请关注我掘金主页或者 达摩兵的空间博客 常见技术...

    前言

    本文主要总结了vue实际开发项目当中应该如何解决一些实际的开发问题,可能你认为很简单,但短时间内也许你并没解决思路的。

    建议阅读时间:15-25min


    更多精彩内容请关注我掘金主页或者 达摩兵的空间博客

    常见技术解答

    for循环中针对ui样式的特征性样式或者事件

    • 针对ui有特定的数据字段进行判断(也叫数据模型方法)

    这种书数据的要求比较高,且要求你能够找到比较好的对应关系,需要针对class进行特征性的组件渲染。当你需要改变时改变数据即可重新渲染达到改变样式的目的。


    <li v-for="item of list" :key="item.id" :class="item.status?'color':''" @click="changeColor(item.id)">{{item.name}}</li>
    return {
    list:[
    {id:1,status:true,name:1111},
    {id:2,status:true,name:222}]
    }
    methods:{
        changeColor(id){
            this.list.map((item)=>{
                if(item.id==id){
                item.status=!item.status;
                }
                return item;
            })
        }
    }
    

    • 传入对应的参数以及事件源,可以进行相应的判断改变class

    特点更加灵活,也可以根据需要传入你需要传入的item属性参数进行与class的匹配判断,不用改变接口返回的数据结构。


    <li v-for="item of list" :key="item.id"  @click="changeColor($event)">{{item.name}}</li>
    return {
    list:[
    {id:1,name:1111},
    {id:2,name:222}]
    }
    changeColor(e){
    			let el=e.target;
                if(el.classList.contains("color")){
                    el.classList.remove("color")
                }else{
                    el.classList.add("color")
                }
    }

    计算属性方法的使用

    问题描述:如果你的计算属性依赖于data的部分,而你的data对应的字段在data里没有申明,只是在请求接口时进行申明赋值,那么当接口请求时,虽然数据发生了变化,但是计算属性的值不会发生更新。

    解决方案 :需要你在data里申明你计算属性依赖的字段,哪怕是空或者null

    事件执行顺序问题

    问题描述 :定义了输入框blur,再按钮点击事件问题,其中默认click的话,执行顺序是先执行blur再执行click.如果你需要场景在点击的时候不执行blur的事件

    解决方案:

    1 常规方案 : 需要吧点击事件变成@mousedown.prevent ,前者会让点击优于blur执行,后者会阻止blur执行

    2 el-input并不生效,可以用计时器延迟执行 将失去焦点的事件计时器延迟执行,然后点击事件里清除定时器,也是可以只执行点击事件逻辑的

    路由参数变化组件不更新

    问题描述 :路由参数变化,但是组件没有对应的更新,主要是因为一般获取参数写在了created路由钩子函数中,路由参数变化的时候,这个生命周期不会重新执行。

    解决方案:watch监听router


     watch: {
     // 方法1
      '$route' (to, from) { //监听路由是否变化
        if(this.$route.params.articleId){// 判断条件1  判断传递值的变化
          //获取文章数据
        }
      }
      //方法2
      '$route'(to, from) {
        if (to.path == "/page") {    /// 判断条件2  监听路由名 监听你从什么路由跳转过来的
           this.message = this.$route.query.msg     
        }
      }
    }
    

    异步函数中使用this无法指向vue实例对象

    问题描述 : 在定时器或者其他异步函数中使用传统的func导致this指向不到vue实例,主要原因是因为this指向的问题,详细的可以参考我的《神奇的this》这篇文章。

    解决方案 :用箭头函数或者指定变量赋值为this(其他一些不能用箭头函数的地方自己也要注意)

    定时器在组件销毁后还在执行

    问题描述 :一些耗费性能的计时器或者动画在组件销毁之后还是执行的,导致性能变低。

    解决方案 :在销毁组件的生命周期中销毁定时器或者一些动画的js



    //组件销毁前执行的钩子函数,跟其他生命周期钩子函数的用法相同。
    beforeDestroy(){
         //我通常是把setInterval()定时器赋值给this实例,然后就可以像下面这么停止。
        clearInterval(this.intervalId);
    },
    

    组件名与引入时大小写不一致导致报错

    问题描述:


    This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
    Use equal casing. Compare these module identifiers:
    

    解决方案 :需要严格对应组件的大小写,避免低级错误

    动态添加的dom没有样式

    问题描述:作为常识我们知道style中的样式都会追加scoped,这样针对模板dom中的样式就可以生效,但其生效后的最终样式并不是我们写的样式名,而是编码后的,所以我们在js中拼接上的dom结构样式并不会生效。

    解决思路: 1 当添加的部分样式不会太多,而且是动态加载的,可以将其设置为非scopred的

    2 将添加dom部分用的样式放到非scoped样式标签中

    3 将添加的部分,如果有必要,可以另外写一个页面拆分的vue组件

    拓展 : 项目中引入的其他ui框架的样式,如果你想覆盖修改,也是需要不加scoped的,如果你想整个项目覆盖,就可以在src/styles下定义customer-element.scss 这样的来重写覆盖样式。

    vue中直接修改数据,页面视图不更新

    问题描述 :在常规理解中,视图与数据是双向绑定的,但是有时候修改data的数组或者对象值,视图不会更新 。



      data() { // data数据
            return {
              arr: [1,2,3],
              obj:{
                  a: 1,
                  b: 2
              }
            };
          },
       // 数据更新 数组视图不更新
        this.arr[0] = 'OBKoro1';
        this.arr.length = 1;
        console.log(arr);// ['OBKoro1'];
        // 数据更新 对象视图不更新
        this.obj.c = 'OBKoro1';
        delete this.obj.a;
        console.log(obj);  // {b:2,c:'OBKoro1'}
    

    解决方案 :由于js的限制,Vue 不能检测以上数组的变动,以及对象的添加/删除,很多人会因为像上面这样操作,出现视图没有更新的问题。

    1 this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)


    this.$set(this.arr, 0, "OBKoro1"); // 改变数组
    this.$set(this.obj, "c", "OBKoro1"); // 改变对象

    2 数组原生方法触发视图更新: splice()、 push()、pop()、shift()、unshift()、sort()、reverse() 推荐使用splice方法会比较好自定义,因为slice可以在数组的任何位置进行删除/添加操作

    3 替换数组 比方说:你想遍历这个数组/对象,对每个元素进行处理,然后触发视图更新。



      // 文档中的栗子: filter遍历数组,返回一个新数组,用新数组替换旧数组
        example1.items = example1.items.filter(function (item) {
          return item.message.match(/Foo/)
        })
    

    需要无脑重复某内容


     <div v-for="n in 5">
            <span>这里会被渲染5次,渲染模板{{n}}</span>
         </div>

    babel-plugin-transform-runtime使用

    • 出现问题:这个插件可以兼容并转化大部分的es6语法,但是部分语法也是不能转化或者存在具体问题的。
    1. 异步加载组件时,会产生 polyfill 代码冗余
    2. 不支持对全局函数与实例方法的 polyfill。不支持全局函数(如:Promise、Set、Map),Set 跟 Map 这两种数据结构应该大家用的也不多,影响较小。但是 Promise 影响可能就比较大了。不支持实例方法(如:'abc'.include('b')、['1', '2', '3'].find((n) => n 等等),这个限制几乎废掉了大部分字符串和一半左右数组的新特性。 而两个问题的原因均归因于 babel-plugin-transform-runtime 采用了沙箱机制来编译我们的代码(即:不修改宿主环境的内置对象)。由于异步组件最终会被编译为一个单独的文件,所以即使多个组件中使用了同一个新特性(例如:Object.keys()),那么在每个编译后的文件中都会有一份该新特性的 polyfill 拷贝。如果项目较小可以考虑不使用异步加载,但是首屏的压力会比较大。
    • 解决方案:一般情况下 babel-plugin-transform-runtime 能满足大部分的需求,当不满足需求时,推荐使用完整的 babel-polyfill。
      • 首先,从项目中移除 babel-plugin-transform-runtime,卸载该依赖: npm un babel-plugin-transform-runtime -D
      • 接着修改 babel 配置文件


    // .babelrc
    {
      //...
      "plugins": [
        // - "transform-runtime"
      ]
      //...
    }

    • 然后,安装 babel-polyfill 依赖: npm i babel-polyfill -D
    • 最后,在入口文件中导入

    // src/main.js
    import 'babel-polyfill'

    ES6 import 引用问题

    在 ES6 中,模块系统的导入与导出采用的是引用导出与导入(非简单数据类型),也就是说,如果在一个模块中定义了一个对象并导出,在其他模块中导入使用时,导入的其实是一个变量引用(指针),如果修改了对象中的属性,会影响到其他模块的使用。

    通常情况下,系统体量不大时,我们可以使用 JSON.parse(JSON.stringify(str)) 简单粗暴地来生成一个全新的深度拷贝的 数据对象。不过当组件较多、数据对象复用程度较高时,很明显会产生性能问题,这时我们可以考虑使用 Immutable.js。

    鉴于这个原因,进行复杂数据类型的导出时,需要注意多个组件导入同一个数据对象时修改数据后可能产生的问题。

    此外,模块定义变量或函数时即便使用 let 而不是 const,在导入使用时都会变成只读,不能重新赋值,效果等同于用 const 声明。

    动态懒加载组件

    背景:在webpack的新特性中支持组件的懒加载,也就是说我们可以在加载到该路由的时候再把这部分脚本进行加载,同时这个在项目进行打包的时候,对应的文件也会被单独打包,对于首屏优化以及其他页面的资源加载优化都是非常好的。这也要求我们在每个页面组件使用组件的时候尽量按需引入,提升体验。

    问题场景:那么我们需要解决的问题是: 0 webpack是静态解析路径的,直接传入变量并不可行 1 每次都写一串加载组件的代码很不方便,是否可以支持写成一个加载组件的方法 2 是否支持区分生产和开发环境,因为开发环境使用懒加载会导致热更新,导致更新变慢,所以开发环境使用全量默认加载,生产环境使用懒加载

    解决方案如下 : 1 webpack的路径使用变量拼接,必须预先给出一个相对路径,然后把具体的组件路径在传入

    2 用一个箭头函数,将需要传入的组件名或者相对路径传入

    3 用process.env.NODE_ENV确定使用哪种加载方式

    代码如下: 在原来的router/index.js中,定义一个加载组件的_import方法。



    // router/index.js 
    const _import = require('./_import_' + process.env.NODE_ENV)
    
    //使用时
     {
          path: '/',
          name: 'HelloWorld',
          component: _import('HelloWorld')
        },
        
    // router/_import_development.js
    module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
    
    // router/_import_production.js 如果你加载的vue不是这个路径 请自定义哦
    module.exports = file => () => import('@/views/' + file + '.vue')
    

    vue中的data必须为函数

    场景 :vue入门的人可能在页面单独引入vue的时候,直接使用data为对象类型的,并没有问题,但是在spa应用中,如果组件中的data为对象类型就会报错。

    解决方案 :data换为函数,返回对象类型的键值对。

    拓展 :你可能知道要这样做,这里稍微科普下原因,主要是因为根组件只会用一次,所以可以用对象,而子组件可能在一个应用中被多次使用,为了避免多个组件使用同一数据互相影响,所以讲data约定为了返回函数类型,返回需要的对象,以此保证子组件在数据渲染的时候不会互相影响。

    有父子标签关系的自定义组件渲染失败

    场景 :在自定义组件的时候,很多时候需要将ul下的li标签,table下的tr\td标签进行封装为自定义组件,但直接使用自定义组件会导致其最终生成的位置不是我们想要的。其标签会渲染到tbody标签以外。



    Vue.component("row",{
      template:'<tr><td>{{content}}</td></tr>',
      data(){
        return {
          content:'this is a row'
        }
      },
    })
    

    解决方案 :原因是因为html会进行标签解析,tbody下的标签必须为tr,其他的同理。那么我们可以将其子标签设置为原来的标签类型,然后用is="selfComponent" 来解决这个问题。

    <tr is="row"></tr>

    拓展:

    • 不要将渲染vue的容器元素定位到html或者body上,否则提示:Do not mount Vue to <html> or <body> - mount to normal elements instead.
    • 确保有在vue新建实例的时候将el属性绑定到一个html模板的标签上

    ref使用

    场景 :虽然vue不建议直接操作dom,但是在复杂的场景中,我们需要进行dom的操作,这时候就可以借助ref实现。比如下面我们举一个简单的例子,通过ref获取dom节点,拿到其内容。

    解决方案



    <div @click="handleClick" ref="hello">hello world
      </div>
       handleClick(){
          console.log(this.$refs.hello)
        }

    • 拓展案例 :实现计数器加和 场景 :假设我们有两个计数器组件的实例,现在需要用ref的方案得到两个计数器的加和。

    代码如下:


     <counter ref="one" @change="handleChange"></counter>
      <counter ref="two" @change="handleChange"></counter>
      <span>{{total}}</span>
      Vue.component("counter",{
      template:"<div @click='change'>{{number}}</div>",
      data(){
      return {
       number:0}  
    	},
       methods:{
           change(){
      		this.number++;
             this.$emit("change")
    		}       
          }
    })
    //app父组件方法
      handleChange(){
          this.total=this.$refs.one.number+this.$refs.two.number
        },
    

    • 拓展认知 : this.$refs.name中如果是原生标签,拿到的是原生标签的节点,如果是组件,拿到的是组件的引用。

    • vueRefDemo使用

    参考文档




    原文发布时间为:2018年06月07日
    原文作者: RobinsonZhang
    本文来源:  掘金  如需转载请联系原作者

    展开全文
  • 执行npm install 出现如下提醒 added 253 packages from 162 contributors and audited 1117 packages in 42.157s found 5 vulnerabilities (1 low, 4 high) run npm audit fix to fix them, or npm audit for ......
  • Vue项目绿盟RSAS漏扫,检测到目标站点存在javascript框架库漏洞 出现问题可能的原因有: jquery的版本过低。cnpm install jquery 使用jsencrypt进行加密或加签。 在node_modules文件夹中找到jsencrypt相关的文件...
  • 0x01 漏洞背景2020 年 06 月 08 日,360CERT监测到 IBM官方发布了 WebSphere远程代码执行 的风险通告,该漏洞编号为 CVE-2020-4450,漏洞等级:高危。WebSphere Application Server 是一款由IBM 公司开发的高性能的...
  • 聚焦源代码安全,网罗国内外最新资讯!编译:奇安信代码卫士本周二,美国网络安全和基础设施安全局 (CISA) 发布安全公告称,飞利浦 Vue 医疗产品受15个漏洞影响。CISA 指出,其中...
  • Vue.js服务器端模板XSS... 请注意,此漏洞既不特定于PHP,也不特定于Vue.js。 如果您的应用程序将服务器端渲染与客户端渲染混合在一起,则可能会受到攻击。 我建议您运行该演示,尝试利用它,然后尝试对其进行修复。
  • found 84 vulnerabilities (65 low, 7 moderate, 11 high, 1 critical) run `npm audit fix` to fix them, or `npm audit` for ...vue使用时提示有漏洞,那么就是直接按照后面提示的命令npm audit fix 就可以解决 ...
  • Raspberry PI IoT安全漏洞安全课程最终大学项目 考虑到物联网设备的增加,这项工作提出了对物联网设备和可行解决方案的弱点评估。 此安全性IoT项目使用带有Raspberry Pi的DHT11传感器,显示VueJS PWA中的实时温度。 ...
  • VUE 02

    2021-05-31 19:59:59
    1.JS与vue的双向数据绑定 <body> <h2>js完成双向数据绑定</h2> <div id='app1'> <input type="text" id='input' name=""> <p id='p1'></p> <p id='p2'><...
  • VUE学习笔记

    2021-07-30 14:34:06
    关于vue框架的一个学习笔记 一、遇见VUE 1、认识vue (1)、为什么要学习vue? 前端的必备技能,其融合了react的虚拟Dom及angular的组件化开发。 (2)、什么是vue?(阅读官方文档) a、Vue是一个渐进式的框架 ——...
  • 具体步骤如下: 1.vue-cli2.x.x 版本卸载 ...注意:3.0 级以上版本 Vue CLI 的包名称由 vue-cli 改成了 @vue/cli 3.检测vue版本(注意:-V, “V”大写) vue -V 4.构建项目(注意:“vuepr...
  • 创建Vue项目Vue-CLI创建项目命令拉取旧的模板安装中出现的警告运行项目安装vant模块 Vue-CLI 使用vue脚手架,需要先全局安装vue脚手架 npm install -g @vue/cli 创建项目命令 vue create [options] <app-name>...
  • 具体步骤如下: 1、安装nodeJS环境 ... 注意:根据自己电脑系统(32位或者64位)下载对应的安装包即可,然后傻瓜式安装就好咯 2、全局安装淘宝镜像 npm install -g cnpm --...3、全局安装vue-cli (npm)cnpm in...
  • Vue 小结

    2020-04-02 02:07:06
    Vue.js 为例。此次讲解围绕以下几个方面展开: MV* 框架模式 Vue.js 的概述 Vue MVVM 的实现 Vue 与 React 的对比 有 Vue 基础如何快速上手 Weex MV* 框架模式 历史 最早期的 Web 开发是洪荒时代,开发...
  • vue模板语法

    2019-07-20 11:45:52
    模板语法 Vue.js使用基于HTML的模板语法,允许您以声明方式将呈现的DOM绑定到...结合反应系统,Vue能够智能地计算出重新渲染的最小数量的组件,并在应用程序状态发生变化时应用最少量的DOM操作。 如果您熟悉Virtual...
  • 第一个vue-cli项目 vue-cli提供一个脚手架,用于快速生成一个vue项目模板 预先定义好的目录结构以及基础代码,好比创建maven项目可以选择创建骨架项目,能提高开发效率 需要环境 Node.js:https://nodejs.org/en/ ...
  • vue基础

    2019-10-08 11:43:01
    1.当一个 Vue 实例被创建时,它将data对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有...
  • 使用vue+el构建表格 介绍 (Introduction) Good documentation is a critical part of a successful project, but a full documentation system may be more involved than you project requires. In this case, ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,612
精华内容 1,444
关键字:

vue漏洞

vue 订阅