vue入门

1 篇文章 0 订阅

Vue.js 2.0 快速上手 - 基础篇

MarkZhai

1 年前

Vue 2.0 出来也有一段时间了,作为一个有志向的全面发展好青年,在征服 Vue 1.xReactReact Native 后,为了之后能更快迁移公司的项目到 Vue 2.x,于是决定先看看 Vue 2.0

鉴于部分读者可能不了解 Vue,先简单看看各种特性。

本文假设你有一定的 HTML 基础,并熟悉一种或以上编程语言(那就能看懂 JS 了)。

模板语法

Vue 提供了一堆数据绑定语法。

·        {{ text }} 文本插值

·        <div v-html="html"></div> HTML 输出

·        v-bind HTML 属性插值。如<buttonv-bind:disabled="someDynamicCondition">Button</button>

·        JavaScript 表达式。直接在 mustache、属性插值里面使用各种表达式(加减乘除、三元运算、方法调用等)。

·        过滤器(有点类似 Shell 命令中的管道,可以定义过滤器来对原始值进行变化)。

·        指令。之前提到的 v-bind 也是一种指定,其他包括 v-on: 系列(dom 事件的监听)、v-forv-model等。

Vue 实例

Vue 实例,实则也就是 ViewModel(数据 + 函数),都是通过构造函数 Vue 创建的:

var data = {a: 1 }

var vm = newVue({

  el: '#example',

  data: data,

  created: function () {

    // `this` 指向 vm 实例

    console.log('a is: ' + this.a)

  }

})

vm.$data ===data // -> true

vm.$el ===document.getElementById('example') // -> true

// $watch 是一个实例方法

vm.$watch('a',function (newVal, oldVal) {

  // 这个回调会在 `vm.a` 改变的时候触发

})

Vue 实例都有自己的生命周期,比如 created, mounted, updated 以及 destroyed。所有方法被 called 的时候,this 都指向所在的 Vue 实例。

Lifecycle 图如下:



计算属性和监听器

计算属性

其实就是一个需要计算的 getter

<divid="example">

  <p>Original message: "{{ message}}"</p>

  <p>Computed reversed message: "{{reversedMessage }}"</p>

</div>

var vm = newVue({

  el: '#example',

  data: {

    message: 'Hello'

  },

  computed: {

    // 一个 computed getter

    reversedMessage: function () {

      // `this` 指向 vm 实例

      returnthis.message.split('').reverse().join('')

    }

  }

})

</div>

和使用 method 的区别在于,计算属性根据它的依赖被缓存,即如果 message 没有被修改,下次 get 不会进行重复计算,而 method 则每次调用都会重新计算。这也意味着如 Date.now() 这样返回的计算属性会永远得不到更新。

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]

    }

  }

}

如此,当我们调用 vm.fullName = 'MarkZhai' 的时候,firstName lastName 都会被更新。

监听器

Vue watch 也可以用来做类似的事:

<divid="demo">{{ fullName }}</div>

var vm = newVue({

  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 = newVue({

  el: '#demo',

  data: {

    firstName: 'Foo',

    lastName: 'Bar'

  },

  computed: {

    fullName: function () {

      return this.firstName + ' ' +this.lastName

    }

  }

})

看上去好像简单了很多,那还要 Watcher 干啥呢。。。主要应用场景是异步或耗时操作:

<script>

varwatchExampleVM = new Vue({

  el: '#watch-example',

  data: {

    question: '',

    answer: 'I cannot give you an answer untilyou ask a question!'

  },

  watch: {

    // 只要 question 改变,这个函数就会执行

    question: function (newQuestion) {

      this.answer = 'Waiting for you to stoptyping...'

      this.getAnswer()

    }

  },

  methods: {

    // _.debounce is a function provided bylodash to limit how

    // often a particularly expensive operationcan be run.

    // In this case, we want to limit how oftenwe access

    // yesno.wtf/api, waiting until the userhas completely

    // finished typing before making the ajaxrequest. To learn

    // more about the _.debounce function (andits cousin

    // _.throttle), visit: Lodash Documentation

    getAnswer: _.debounce(

      function () {

        var vm = this

        if (this.question.indexOf('?') === -1){

          vm.answer = 'Questions usuallycontain a question mark. ;-)'

          return

        }

        vm.answer = 'Thinking...'

        axios.get('https://yesno.wtf/api')

          .then(function (response) {

            vm.answer =_.capitalize(response.data.answer)

          })

          .catch(function (error) {

            vm.answer = 'Error! Could not reachthe API. ' + error

          })

      },

      // 等待用户停止输入后的时间(毫秒)

      500

    )

  }

})

</script>

如此,使用 watch 让我们可以进行异步操作(访问 API),限制操作间隔,并设置中间状态直到获得了真正的答案。

除了使用 watch option,也可以用 vm.$watch API

Class Style 绑定

除了数据绑定,常见的还有 styleclass 的绑定(正如很久以前在 JQuery 中常用的)。

对象语法

我们可以传递一个对象给 v-bind:class 来动态切换 classes

<divclass="static"

     v-bind:class="{ active: isActive,'text-danger': hasError }">

</div>

对应的 active text-danger 则通过 data 传递过来。

我们也可直接通过 data class 传递过来

<div v-bind:class="classObject"></div>

data: {

  classObject: {

    active: true,

    'text-danger': false

  }

}

当然我们也能使用上面提到的 computed 来进行对应属性,如 active 的计算。

数组语法

可以直接传递一个数组给 v-bind:class:

<divv-bind:class="[activeClass, errorClass]">

data: {

  activeClass: 'active',

  errorClass: 'text-danger'

}

也可以写成

<divv-bind:class="[isActive ? activeClass : '', errorClass]">

<divv-bind:class="[{ active: isActive }, errorClass]">

绑定内联样式

class 差不多:

<divv-bind:style="{ color: activeColor, fontSize: fontSize + 'px'}"></div>

或者直接绑定到 style

<divv-bind:style="styleObject"></div>

 

data: {

  styleObject: {

    color: 'red',

    fontSize: '13px'

  }

}

类似的,也有数组绑定。

条件绑定

v-if

其实就是个标签啦

<h1v-if="ok">Yes</h1>

 

<h1v-if="ok">Yes</h1>

<h1v-else>No</h1>

因为 v-if 必须附加到一个单一 element 上,那如果我们想切换多个元素呢?可以使用 template 元素:

<templatev-if="ok">

  <h1>Title</h1>

  <p>Paragraph 1</p>

  <p>Paragraph 2</p>

</template>

v-show

也可以用 v-show 来做条件显示的逻辑,

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

区别在于

·        v-show 不支持 template v-else

·        v-if  lazy 的,不会渲染没有走到的条件。而 v-show 只是简单的基于 CSS 的切换。所以 v-show 的初始 render 代价较高。

·        由于 v-if 是真实的渲染,切换后原来的 dom 会被 destroyed,而新的 dom 会被重新创建。所以切换代价更高。

所以如果切换得较为频繁可以使用 v-show,如果在运行时不太会改变则可以使用 v-if

列表渲染

v-for

其实就是个循环标签啦:

<ulid="example-2">

  <li v-for="(item, index) initems">

    {{ parentMessage }} - {{ index }} - {{item.message }}

  </li>

</ul>

对应的 vm 实例:

var example2= new Vue({

  el: '#example-2',

  data: {

    parentMessage: 'Parent',

    items: [

      { message: 'Foo' },

      { message: 'Bar' }

    ]

  }

})

模板 v-for

v-if 类似,我们也能在 template 上使用 v-for:

<ul>

  <template v-for="item initems">

    <li>{{ item.msg }}</li>

    <liclass="divider"></li>

  </template>

</ul>

对象 v-for

也能使用 v-for 遍历对象的属性:

<ulid="repeat-object" class="demo">

  <li v-for="value in object">

    {{ value }}

  </li>

</ul>

new Vue({

  el: '#repeat-object',

  data: {

    object: {

      FirstName: 'John',

      LastName: 'Doe',

      Age: 30

    }

  }

})

看到 value,那肯定还有 key 了:

<divv-for="(value, key) in object">

  {{ key }} : {{ value }}

</div>

如果再加上 index:

<divv-for="(value, key, index) in object">

  {{ index }}. {{ key }} : {{ value }}

</div>

其他还有像是 v-for="n in 10" 这种用法,就不加上例子了。

组件 v-for

input 输出内容到 newTodoText,每次点击 enter 都会触发 addNewTodo,然后添加 item todos,触发新的 li 添加进去:

<divid="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:title="todo"

      v-on:remove="todos.splice(index,1)"

    ></li>

  </ul>

</div>

Vue.component('todo-item',{

  template: '\

    <li>\

      {{ title }}\

      <buttonv-on:click="$emit(\'remove\')">X</button>\

    </li>\

  ',

  props: ['title']

})

new Vue({

  el: '#todo-list-example',

  data: {

    newTodoText: '',

    todos: [

      'Do the dishes',

      'Take out the trash',

      'Mow the lawn'

    ]

  },

  methods: {

    addNewTodo: function () {

      this.todos.push(this.newTodoText)

      this.newTodoText = ''

    }

  }

})

key

vue 在更新被 v-for 渲染的列表时候,会使用就地 patch 的策略,而不是根据元素改变的顺序。我们可以提供 key 来做这个排序:

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

  <!-- content -->

</div>

如此,item 会根据 id 来做排序。

数组改变监测

替换方法(mutation

·        push()

·        pop()

·        shift()

·        unshift()

·        splice()

·        sort()

·        reverse()

这些方法会改变原来的 array,并自动触发 view 的更新。

替换 array

·        filter()

·        concat()

·        slice()

这几个方法会返回新的 array,如:

example1.items= example1.items.filter(function (item) {

  return item.message.match(/Foo/)

})

附加说明

如果

·        直接 set array 的值,如 vm.items[indexOfItem] = newValue

·        修改 array 的长度,如 vm.items.length = newLength

都是没法触发更新的,需要使用

Vue.set(example1.items,indexOfItem, newValue)

 

//Array.prototype.splice`

example1.items.splice(indexOfItem,1, newValue)

 

example1.items.splice(newLength)

过滤/排序

配合 computed 以及 filter,或者也可以使用 v-for 的条件渲染:

<liv-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-on 指令监听 DOM 的各种事件,如:

<divid="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

  }

})

除了直接写 JS 语句,也可以直接在 v-on 中调用 methods 中定义的事件,还可以进行传参:

<divid="example-3">

  <buttonv-on:click="say('hi')">Say hi</button>

  <button v-on:click="say('what')">Saywhat</button>

</div>

new Vue({

  el: '#example-3',

  methods: {

    say: function (message) {

      alert(message)

    }

  }

})

我们可能也希望直接把 event 给传递到方法中(比如在方法里 preventDefault 或者 stopPropagation),也很 easy,直接使用特殊的 $event 变量就行了。

事件修饰符

除了像上面这样,在 method 里面对 event 进行操作,我们还可以使用事件修饰符(Event Modifier):

·        .stop

·        .prevent

·        .capture

·        .self

使用如:

<!-- theclick event's propagation will be stopped -->

<av-on:click.stop="doThis"></a>

<!-- thesubmit event will no longer reload the page -->

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

<!--modifiers can be chained -->

<av-on:click.stop.prevent="doThat"></a>

<!-- justthe modifier -->

<formv-on:submit.prevent></form>

<!-- usecapture mode when adding the event listener -->

<divv-on:click.capture="doThis">...</div>

<!-- onlytrigger handler if event.target is the element itself -->

<!-- i.e.not from a child element -->

<divv-on:click.self="doThat">...</div>

Key 修饰符

通用的有使用 keyCode :

<inputv-on:keyup.13="submit">

其他 alias 别名有

·        enter

·        tab

·        delete (captures both “Delete” and “Backspace” keys)

·        esc

·        space

·        up

·        down

·        left

·        right

我们也可以自己通过全局的 config 定义其他别名,如:

// enablev-on:keyup.f1

Vue.config.keyCodes.f1= 112

表单输入绑定

基本使用

text

<inputv-model="message" placeholder="edit me">

<p>Messageis: {{ message }}</p>

如此,用户的输入会直接反映到 data 中的 message,然后更新到

多行的用 textarea 替换 input 就行了。

Checkbox

单个的:

<inputtype="checkbox" id="checkbox"v-model="checked">

<labelfor="checkbox">{{ checked }}</label>

多个的则可以绑到一个 array :

<inputtype="checkbox" id="jack" value="Jack"v-model="checkedNames">

<labelfor="jack">Jack</label>

<inputtype="checkbox" id="john" value="John"v-model="checkedNames">

<labelfor="john">John</label>

<inputtype="checkbox" id="mike" value="Mike"v-model="checkedNames">

<labelfor="mike">Mike</label>

<br>

<span>Checkednames: {{ checkedNames }}</span>

Radio

<inputtype="radio" id="one" value="One"v-model="picked">

<labelfor="one">One</label>

<br>

<inputtype="radio" id="two" value="Two"v-model="picked">

<labelfor="two">Two</label>

<br>

<span>Picked:{{ picked }}</span>

Select

<selectv-model="selected">

  <option>A</option>

  <option>B</option>

  <option>C</option>

</select>

<span>Selected:{{ selected }}</span>

 Selected: C

多选的在 select 后面加个 multiple,然后对应的会绑定到数组。

还可以结合 v-for 进行动态渲染:

<selectv-model="selected">

  <option v-for="option inoptions" v-bind:value="option.value">

    {{ option.text }}

  </option>

</select>

<span>Selected:{{ selected }}</span>

new Vue({

  el: '...',

  data: {

    selected: 'A',

    options: [

      { text: 'One', value: 'A' },

      { text: 'Two', value: 'B' },

      { text: 'Three', value: 'C' }

    ]

  }

})

值绑定

默认地,像上面这样,最后 v-model 绑定到的对象,其值会是一个静态字符串(或者 true/false),有时候我们想要将其值绑定到一个动态属性,就可以使用 v-bind 来达到目的。

比如对于 input:

<input

  type="checkbox"

  v-model="toggle"

  v-bind:true-value="a"

  v-bind:false-value="b">

// whenchecked:

vm.toggle=== vm.a

// whenunchecked:

vm.toggle=== vm.b

甚至对象:

<selectv-model="selected">

  <!-- inline object literal -->

  <option v-bind:value="{ number: 123}">123</option>

</select>

// whenselected:

typeofvm.selected // -> 'object'

vm.selected.number// -> 123

修饰符

.lazy

默认地,v-model 在每次 input 事件后都会同步输入到数据。加上 lazy 修饰符后就会在 change 事件后才同步:

<inputv-model.lazy="msg" >

.number

会自动把输入转为 number:

<inputv-model.number="age" type="number">

这还是挺有用的,因为就算限制了 input type number,元素的 value 仍然会返回 string

.trim

好像不用多说了?大家都懂吧。

<inputv-model.trim="msg">

组件

现代的前端框架,通常都是组件化的了。整个应用的搭建,其实就是组件的拼接。自然 Vue 也不会忘了这个。

使用组件

注册

注册一个全局组件,只需要 Vue.component(tagName, options) 即可,如:

Vue.component('my-component',{

  // options

})

实际渲染出来的 dom 则定义在 template option 中,如:

// 注册

Vue.component('my-component',{

  template: '<div>A customcomponent!</div>'

})

// 创建一个根实例

new Vue({

  el: '#example'

})

局部注册

局部注册只需要放在 Vue 实例中:

var Child ={

  template: '<div>A customcomponent!</div>'

}

new Vue({

  // ...

  components: {

    // <my-component> 只在父亲的模板里可用

    'my-component': Child

  }

})

使用则像:

<divid="example">

  <my-component></my-component>

</div>

Dom 模板解析限制

当使用 Dom 作为模板(比如使用 el 选项来使用已有内容加载元素),将会受到一些因为 HTML 工作原理而导致的限制,因为 Vue 只能在浏览器解析后才获取模板数据并进行处理。比如

中将不能出现自定义组件,只能通过 is 特殊属性进行规避。

可以通过以下方法使用字符串模板,就不会有这些限制:

·        <script type="text/x-template">

·        JavaScript 内联模板字符串

·        .vue 组件

所以,尽量使用字符串模板(string templates)吧。

data 必须是函数

大部分被传进 Vue 构造函数的选项都能在组件内使用,除了一个特殊情况:data 必须是函数。

data:function () {

  return {

    counter: 0

  }

}

而不能是一个在 parent context var(会被多个组件实例共享)或者 object(控制台会报错)。

组合组件

组件通常会被一起使用,大部分情况下会有——关系,如组件A 在其模板中使用了组件B。如此,就不免会有相互间的通讯,父亲需要传递数据给儿子,而儿子则需要通知父亲其内部发生的某些事件。

然而,为了让组件能避免耦合从而提高复用性和可维护性,又需要使它们相对隔离。

Vue.js 中,这种——组件关系可以被总结为 props down, events up,即父组件通过 props 传递数据给子组件,而子组件通过 event 发消息给父组件。



熟悉 React 的话,你可能会想到 props state

props

通过 props 传递数据

每个组件都是相互隔离的,所以无法在子组件的 template 中引用父组件的数据。数据只能通过 props 传递。

比如我们可以这么注册子组件:

Vue.component('child',{

  // 申明 props

  props: ['message'],

  // data 一样,可以在 vm (this.message) template 中直接使用

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

})

然后如此传递 props:

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

camelCase vs. kebab-case

因为 HTML 属性的限制(大小写敏感),所以使用 non-string templates 时,camelCased 的属性必须使用对应的 kebab-case 版本:

Vue.component('child',{

  // camelCase in JavaScript

  props: ['myMessage'],

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

})

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

Again, ifyou’re using string templates, then this limitation does not apply.

所以都说了,用字符串模板吧。

动态 props

<div>

  <input v-model="parentMsg">

  <br>

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

</div>

如此,my-message 在父组件被改变的时候,都会传递更新到子组件。

字面量语法 vs 动态语法

当我们使用

<compsome-prop="1"></comp>

的时候,实际传递的是一个字符串,而不是 number 2,如果要传递 JavaScript number,则需要使用 v-bind

<compv-bind:some-prop="1"></comp>

单向数据流

所有的 props 都是单向往下的,父组件 property 更新会影响子组件的,反过来则不会。这样避免了子组件误更改父组件状态,以及应用数据流难以理解。

另外,每次父组件中对应属性发生改变,子组件中的所有 props 都会被更新为最新的值。所以在子组件中,不应该对 props 进行更改。

你可能会辩解说传进来的只是个初始值,或者是个需要计算才能得出真正要的格式的值,但对前者你应该使用本地 data 属性来引用初始值,后者则应该通过 computed 来做。

prop 检查

可以对组件接受的 props 定义要求,如:

Vue.component('example',{

  props: {

    // 基本类型检查 (`null` 表示接受任何类型)

    propA: Number,

    // 多种可能的类型

    propB: [String, Number],

    // 一个必须的 string

    propC: {

      type: String,

      required: true

    },

    // 一个带默认值的 number

    propD: {

      type: Number,

      default: 100

    },

    // 对象/数组的默认值须通过一个工厂方法返回

    propE: {

      type: Object,

      default: function () {

        return { message: 'hello' }

      }

    },

    // 自定义检验器函数

    propF: {

      validator: function (value) {

        return value > 10

      }

    }

  }

})

自定义事件

我们已经学习了父组件如何传递属性给子组件,那子组件怎么向上发送数据呢?答案就是自定义事件。

使用 v-on

所有 Vue 实例都实现了 Events 接口,即:

·        通过 $on(eventName) 监听事件

·        通过 $emit(eventName) 触发事件

如此,我们可以这样来传递事件(定义了 2 button,可以发送 increment 事件给父组件触发 incrementTotal)。

<divid="counter-event-example">

  <p>{{ total }}</p>

  <button-counter v-on:increment="incrementTotal"></button-counter>

  <button-counterv-on:increment="incrementTotal"></button-counter>

</div>

Vue.component('button-counter',{

  template: '<buttonv-on:click="increment">{{ counter }}</button>',

  data: function () {

    return {

     counter: 0

    }

  },

  methods: {

    increment: function () {

      this.counter += 1

      this.$emit('increment')

    }

  },

})

new Vue({

  el: '#counter-event-example',

  data: {

    total: 0

  },

  methods: {

    incrementTotal: function () {

      this.total += 1

    }

  }

})

表单输入

有时候在组件中,我们会有输入,比如 input,其实 input 中的 v-model 就是:

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

的一个语法糖。

类似地,为了让组件支持 v-model,它必须:

·        接受 value prop

·        用新的值发送一个 input 事件

让我们来看看实践

<divid="v-model-example">

  <p>{{ message }}</p>

  <my-input

    label="Message"

    v-model="message"

  ></my-input>

</div>

Vue.component('my-input',{

  template: '\

    <div class="form-group">\

      <labelv-bind:for="randomId">{{ label }}:</label>\

      <input v-bind:id="randomId"v-bind:value="value" v-on:input="onInput">\

    </div>\

  ',

  props: ['value', 'label'],

  data: function () {

    return {

      randomId: 'input-' + Math.random()

    }

  },

  methods: {

    onInput: function (event) {

      this.$emit('input', event.target.value)

    }

  },

})

new Vue({

  el: '#v-model-example',

  data: {

    message: 'hello'

  }

})

这个接口不仅能被用在组件内的表单输入,还能被用在你自己发明的各种组件输入,像是:

<voice-recognizerv-model="question"></voice-recognizer>

<webcam-gesture-readerv-model="gesture"></webcam-gesture-reader>

<webcam-retinal-scannerv-model="retinalImage"></webcam-retinal-scanner>

非父子组件通讯

有时候两个组件可能需要互相通讯,但却不是父子关系。在简单的场景下,你可以使用一个空的 Vue 实例来作为中央事件总线(event bus:

var bus =new Vue()

// 在组件 A 的方法中

bus.$emit('id-selected',1)

 

// 在组件 B created

bus.$on('id-selected',function (id) {

  // ...

})

在更复杂的场景下,你可能需要考虑使用状态管理模式,其实就是 Vuex 了(Vue Redux)。

使用 Slot 分发内容

在使用组件的时候,经常会像这样组合:

<app>

  <app-header></app-header>

  <app-footer></app-footer>

</app>

有两点需要注意的:

·        组件不知道在其挂载点内可能出现的的内容。这是由使用父组件所决定的。

·        组件很可能有它自己的模板。

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为内容分发。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草稿  Slot proposal,使用特殊的元素作为原始内容的插槽。

单个Slot

直接看例子吧:

<div>

  <h2>I'm the child title</h2>

  <slot>

    This will only be displayed if there is nocontent

    to be distributed.

  </slot>

</div>

父组件这样使用它:

<div>

  <h1>I'm the parent title</h1>

  <my-component>

    <p>This is some originalcontent</p>

    <p>This is some more originalcontent</p>

  </my-component>

</div>

最后渲染出来的结果是:

<div>

  <h1>I'm the parent title</h1>

  <div>

    <h2>I'm the child title</h2>

    <p>This is some originalcontent</p>

    <p>This is some more originalcontent</p>

  </div>

</div>

也就是外面的内容被插入到了slot里面。

具名 Slot

如果你需要多个 slot,也很简单:

<divclass="container">

  <header>

    <slot name="header"></slot>

  </header>

  <main>

    <slot></slot>

  </main>

  <footer>

    <slotname="footer"></slot>

  </footer>

</div>

父组件这么使用

<app-layout>

  <h1 slot="header">Here mightbe a page title</h1>

  <p>A paragraph for the maincontent.</p>

  <p>And another one.</p>

  <p slot="footer">Here's somecontact info</p>

</app-layout>

渲染出来的结果是:

<divclass="container">

  <header>

    <h1>Here might be a pagetitle</h1>

  </header>

  <main>

    <p>A paragraph for the maincontent.</p>

    <p>And another one.</p>

  </main>

  <footer>

    <p>Here's some contact info</p>

  </footer>

</div>

在设计需要组合到一起的组件时,内容分发 API 是非常有用的机制。

动态组件

你可以使用同一个挂载点,并动态地将其切换为其他 Component。只需要使用保留的元素并动态绑定到它的 it 属性:

var vm = newVue({

  el: '#example',

  data: {

    currentView: 'home'

  },

  components: {

    home: { /* ... */ },

    posts: { /* ... */ },

    archive: { /* ... */ }

  }

})

<componentv-bind:is="currentView">

  <!-- vm.currentView 改变后 component 类型也会发生改变 -->

</component>

如果你希望的话,也可以直接绑定到 component 对象:

var Home = {

  template: '<p>Welcome home!</p>'

}

var vm = newVue({

  el: '#example',

  data: {

    currentView: Home

  }

})

keep-alive

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:

<keep-alive>

  <component :is="currentView">

    <!-- 非活动组件将被缓存 -->

  </component>

</keep-alive>

更多详情可以看 API 文档

其他杂项

编写可复用组件

在写组件的时候,最好想好你是否会在某些其他地方再次用到它。对一些一次性的组件,紧密耦合是没有问题的,但对可复用的组件,就需要定义一个干净的 public 接口,让它上下文无关。

一个 Vue 组件的 API 3 者组成 —— props, events, 以及 slots:

·        Props 允许外部环境传递数据到组件内。

·        Events 允许组件触发外部环境的副效应(side effects)。

·        Slots 允许外部环境来插入内容到组件的视图结构内。

通过 v-bind v-on 的简写语法,template 可以干净简洁地传递意图:

<my-component

  :foo="baz"

  :bar="qux"

  @event-a="doThis"

  @event-b="doThat"

  <img slot="icon"src="...">

  <p slot="main-text">Hello!</p>

</my-component>

:foo v-bind:foo 的简写,@event-a 则是 v-on:event-a 的简写。

子组件引用

尽管我们有 props events,有时候你可能仍然需要在 JavaScript 中直接操作子组件。为此你必须使用 ref 分配一个 reference ID 给子组件。

<divid="parent">

  <user-profileref="profile"></user-profile>

</div>

var parent =new Vue({ el: '#parent' })

// 访问子组件实例

var child =parent.$refs.profile

ref v-for 一起使用的时候,你得到的 ref 将会是一个包含了从数据源镜像的数组或者对象。

$refs 只有在组件被渲染后才能获得,而且它不是响应式的。也就意味着只是一个直接子组件操作的逃生口 —— 你应该避免在模板或者 computed 属性中使用 $refs

异步组件

在大型应用中,我们需要把 app 分成一个个小块,只在真正需要的时候才去加载组件。为了简化这个,Vue 允许把组件定义为一个工厂方法,并异步去解析组件定义。Vue 仅仅会在组件真正需要被渲染的时候才会去触发该工厂方法,然后把结果缓存下来给以后的再渲染。如:

Vue.component('async-example',function (resolve, reject) {

  setTimeout(function () {

    resolve({

      template: '<div>I amasync!</div>'

    })

  }, 1000)

})

工厂方法接受一个 resolve 回调,会在从服务器获取到组件定义后被触发。也可以使用 reject(reason) 来指出加载失败了。这里的 setTimeout 只是用来做简单的演示,如何去获取 component 完全取决于你。一个推荐的方法是和 Webpack code-splitting 功能一块儿使用异步组件:

Vue.component('async-webpack-example',function (resolve) {

  // 这个特殊的 require 语法会让 Webpack 去自动把你的编译后代码分割成通过 Ajax 请求加载的 bundles

  require(['./my-async-component'], resolve)

})

也可以在 resolve 方法中返回一个 Promise,比如通过 Webpack 2 + ES2015 语法可以这么做:

Vue.component(

  'async-webpack-example',

  () =>System.import('./my-async-component')

)

然后 Browserify 不支持异步组件,拥抱 Webpack 吧。

组件命名规范

在注册的时候,使用是随意的:

components:{

  'kebab-cased-component': { /* ... */ },

  'camelCasedComponent': { /* ... */ },

  'TitleCasedComponent': { /* ... */ }

}

但是在 HTML 模板中,必须使用 kebab-case 的,也就是上面的第一种。但如果是字符串模板(string template)的话,则可以随意使用。如果你的组件不使用 slot 进行属性传递,甚至可以直接写成自闭的(也仅支持字符串模板,因为浏览器不支持自闭合的自定义元素)。

递归组件

组件可以在它自己的模板中递归自身。然而,他们只能通过 name 选项来这么做:

name:'stack-overflow',

template:'<div><stack-overflow></stack-overflow></div>'

像上面这样的组件会陷入 “max stack size exceeded” 错误,所以需要让递归变成条件性的(比如使用 v-if 指令,并最终返回一个 false)。当你在全局通过 Vue.component 注册一个组件的时候,一个全局的 ID 会被自动设置为组件的 name 选项。

内联模板

当子组件中存在 inline-template 这个特殊属性的时候,它会使用其内容作为模板,而不会把它当做分发内容。如此模板就变得更灵活了。

<my-componentinline-template>

  <p>These are compiled as thecomponent's own template.</p>

  <p>Not parent's transclusioncontent.</p>

</my-component>

但是 inline-template 让模板的作用域难以理解,并且不能缓存模板编译结果。最佳实践是通过 template option 在组件内部定义模板,或者在 .vue 文件中的模板元素中定义。

X-Templates

另一个在 script 元素内部定义模板的方法是通过 type text/x-template,然后通过 id 引用模板。像这样:

<scripttype="text/x-template" id="hello-world-template">

  <p>Hello hello hello</p>

</script>

Vue.component('hello-world',{

  template: '#hello-world-template'

})

在极小的应用或者大型模板的 demo 的时候可能会有用,其他情况下应该尽量避免。因为这样会把它和其他模板定义给隔离开。

v-once 定义简单的静态组件

Vue 里面渲染纯净的 HTML 元素是很快的,但有时候你可能需要一个包含了很多静态内容的组件。这种情况下,你可以通过在根元素加上 v-once 指令确保它只被评估了一次然后就被缓存下来了,像是这样:

Vue.component('terms-of-service',{

  template: '\

    <div v-once>\

      <h1>Terms of Service</h1>\

      ... 很多静态内容 ...\

    </div>\

  '

})

Vue 1.x TO 2.0

通过使用 vue-migration-helper 可以快速扫出需要替换的代码。

主要有以下几类:

·        官方依赖,比如 vue-resourcevue-router(这个升级完接口也改了不少,迁移向导)、vue-loadervue-hot-reload-apivue-invalidate(还在升级中)等。

·        第三方库。

·        UI组件库,比如 vux(目前计划是11月发布适配 2.0 的版本)、饿了么前端提供的那些(并没有给出更新计划)。

·        组件间通讯,不能再使用 dispatch,而需要使用全局 eventbus 或者 vuex

·        各种 API 废弃,见 issue 2873,像是 attachedactivated 这些生命周期 API 都被干掉了。

具体一点的话,像是

index

$index 现在必须使用 index 了(在 v-for 中显示声明)

filters

不能像以前那样到处用了,只在 {{ }} 中生效,转而用计算属性或者方法吧。

transition

transition 属性被废弃了。可以看看新的 Transitions 文档

vue router

加了全局和离开当前页面的钩子,router-linkrouter data,等等。

等等等等,要升级还是挺痛苦的。啊,对了 vuex 也升级到 2.0 了。更像 redux 了。心情很复杂

尾声

差不多也就是这样了。如果你是一个有一定经验并懂得基本 HTML CSS 的高级工程师,我相信几天你就能看完并上手它了,毕竟对比 React 那一整套东西,还是相对简单的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值