七周七种前端框架四:Vue.js 组件和组件通信

基本是按照官网的 Guide 全部梳理了一遍:http://vuejs.org/guide/index.html
这里我们以一个 Todo List 应用为例来把相关的只是都串起来,这篇里面的全部代码都在github上 https://github.com/lihongxun945/vue-todolist

Vue 实例

一个 Vue 应用是由一个 root vue instance 引导启动的,而 Vue instance 是这么创建的:

var vm = new Vue({
  // options
})

一个 instance 实际上就是 MVVM 中的一个 VM。 传入的配置对象中data里的所有属性都会被挂载到 instance上,而为了避免命名冲突,Vue 内置方法都会以 $ 开头的属性挂载到 instance 上。

instance 从创建到销毁会经历如下生命周期:

在初始化的时候大致经过三步:

  • 绑定数据监听,即对 data 的监听
  • 编译模板
  • 插入document或者替换对应dom

# Vue 基本语法

数据绑定

Vue 使用的是一种 类 mastache 语法。常用绑定语法分这么几类:

  • mastache 语法,比如 {{ data }} {{ data | filter}}
  • v-bind 绑定属性,比如 v-bind: href, v-bind:class
  • v-on 绑定事件, 比如 v-on:click, v-on:submit

其中 v-* 都是 directive

例子:

<div v-bind:class="[classA, isB ? classB : ‘‘]">

属性计算

Vue 支持一个很有意思的属性计算语法,可以指定一个属性由其他属性计算出来,这样就不用通过 $watch 来实现了:

var vm = new Vue({
  el: ‘#example‘,
  data: {
    a: 1
  },
  computed: {
    // a computed getter
    b: function () {
      // `this` points to the vm instance
      return this.a + 1
    }
  }
})

## 流程控制和列表相关的语法

包括 `v-if`, `v-show`, `v-else`, `v-for`

表单

双向数据绑定:

<input type="text" v-model="message" placeholder="edit me">

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

## 动画
动画的实现方式和 Angular 以及 React 都是一样的,都是通过添加和删除 class 来实现的。

# Component

组件的基本用法

Component 的定义包括两部分:

1 创建component类:

var Profile = Vue.extend({
  template: "<div> Lily </div>"
});

2 注册一个 tagname:

Vue.component("me-profile", Profile);

这样我们就可以通过 tagname 来使用这么组件了:

  <div id="todo">
    <my-profile></my-profile>
    <form v-on:submit="add" v-on:submit.prevent>
      <input type="text" v-model="input"/>
      <input type="submit" value=‘add‘ />
    </form>
     ...
</div>

Vue.component("me-profile", Profile); 属于全局注册,如果只是在某一个页面内使用,可以通过局部注册的方式:

var vm = new Vue({
  el: "#todo",
  components: {
    "my-profile": Profile
  },
  ...
}

其中因为我们的 Vue 实例是绑定在 todo 元素上的,所以如果把 my-profile 放在这个元素外面是无效的,只有放在这个里面才会被 Vue 的这个实例引导初始化。

注意事项:

Vue 构造函数可以传的参数基本都可以用在 Vue.extend 上,但是对 eldata 两个参数需要注意,为了避免不同实例间共享同一个对象,总是要通过 function 返回一个新的对象比较靠谱:

var MyComponent = Vue.extend({
  data: function () {
    return { a: 1 }
  }
})

因为参数都一样,其实他们俩就是同一个东西,不过一个是组件,一个是用来引导Vue启动的。

模板注意事项

因为 Vue 就是原生的DOM,所以有些自定义标签可能不符合DOM标准,比如想在 table 中自定义一个 tr,如果直接插入 my-component 不符合规范,所以应该这样写:

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

Props 传递数据

在 Vue 中每个组件都是独立的,不能也不应该直接访问父类的data。所以我们通过 props 来向子组件传递数据,是不是和 React 的方式很像?

不同于 React,在 Vue 中子组件需要先声明自己的 props 才行:

var Profile = Vue.extend({
  props: ["name"],
  template: `
    <h2>{{name}}‘s Todo List</h2>
    <h4>{{name}} is a good girl</h4>
    `
});

然后我们可以在使用 Profile 的时候这样传递参数:

<my-profile name=‘Lily‘></my-profile>

这种是通过字面量传递参数,所以传递的值一定是字符串。还有一种方式是动态传参,通过 v-bind 来传递参数,可以双向绑定数据或者传非字符串参数:

<my-profile v-bind:name=‘input‘></my-profile>

v-bind 如果是一个字符串,则是绑定父组件的data中对应的字段,比如上面就是双向绑定了 input 的值。如果是一个数字则就是绑定了一个数字。

Vue 还可以显式指定单向还是双向的数据绑定:

<!-- default, one-way-down binding -->
<child :msg="parentMsg"></child>

<!-- explicit two-way binding -->
<child :msg.sync="parentMsg"></child>

<!-- explicit one-time binding -->
<child :msg.once="parentMsg"></child>

Props 校验

一个好的组件总是应该先验证参数是否正确,另外可能还需要设置一些参数的默认值:

var Profile = Vue.extend({
    input: {
      type: String
    }
});

父子组件通信

上面讲到的 props 其实就是父组件向子组件传递消息的一种方式。

在子组件中有一个 this.$parentthis.$root 可以用来方法父组件和根实例。不过,现在我们应该避免这么做。因为组件本身就是为了封装独立的逻辑,如果又去直接访问父组件的数据就破坏了组件的封装性。

所以我们应该还是应该通过父组件向子组件传递 props 的方式来通信。

当然 props 其实只能做回调。在 React 中就探讨过这个问题,React 的做法就是通过 props 来做,传一个回调函数给子组件。其实我不是很喜欢这种把回调函数传来传去的方式,我更喜欢的是事件的方式。Vue 中子组件可以通过通过事件和父组件进行通信的。向父组件发消息是通过 this.$dispatch,而向子组件发送消息是通过 this.$boardcast,这里都是向所有的父亲和孩子发送消息,但是一旦执行一个回调之后就会停止,除非这个回调函数显式返回了 true

我们把之前的Todo List拆成不同的组件来实现,这样可以体验下如何进行组件的双向通信,我们拆分出两个组件,分别是 ListForm

Form 负责处理用户输入,并在提交表单的时候向父组件发送一个 add 消息,代码如下:

var Form = Vue.extend({
  props: {
    username: {
      type: String,
      default: "Unnamed"
    }
  },
  data: function() {
    return {
      input: "",
    };
  },
  template: `
    <h1>{{username}}‘s Todo List</h1>
    <form v-on:submit="add" v-on:submit.prevent>
      <input type="text" v-model="input"/>
      <input type="submit" value=‘add‘ />
    </form>
    `,
  methods: {
    add: function() {
      this.$dispatch("add", this.input); //这里就是向父组件发送消息
      this.input = "";
    }
  }
});

List 只负责展示列表和处理用户勾选操作,它接收到 add 消息之后会在自己上添加一个条目:

var List = Vue.extend({
  template: `
    <ul>
      <li v-for=‘todo in list‘>
        <label v-bind:class="{ done : todo.done }" >
          <input type="checkbox" v-model="todo.done"/>
          {{todo.title}}
        </label>
      </li>
    </ul>`,
  props: {
    initList: {
      type: Array
    }
  },
  data: function() {
    return {
      list: []
    }
  },
  events: {
    add: function(input) {
      if(!input) return false;
      this.list.unshift({
        title: input,
        done: false
      });
    }
  }
});

然后,因为这是两个组件,当然需要一个 Vue 实例来引导启动,我们的实例如下:

var vm = new Vue({
  el: "#todo",
  components: {
    "todo-form": Form,
    "todo-list": List
  },
  events: {
    add: function(input) {
      this.$broadcast("add", input);
    }
  }
});

注意,其实 FormList 在逻辑上是平级的组件,所以他们没有父子关系,他们共同都是 vm 的孩子。这里 vm 接到 Form 的消息之后会转发给 List

html 代码就更简单了:

  <div id="todo">
    <todo-form username=‘Lily‘></todo-form>
    <todo-list></todo-list>
  </div>

Slot

通过 Slot 可以实现把父组件渲染出来的HTML插入到子组件中,目前还不清楚什么时候会需要这样做,而且这么做对子组件的侵入性太大。

动态切换组件

这个功能感觉有点多余,感觉很多情况下我们应该是通过逻辑代码来实现切换,而不是通过Vue内置的动态组件来切换。不过用来实现一个类似 tab 切换的功能还是很方便的。

我们这里给 Todo List 增加一个 about 页面。那么首先我们需要把 vm 改成一个组件,这个组件叫 Todo,它就是整个 Todo 页面:

var Todo = Vue.extend({
  template: `
  <div id="todo">
    <todo-form username=‘Lily‘></todo-form>
    <todo-list></todo-list>
    <slot>not show</slot>
  </div>
  `,
  components: {
    "todo-form": Form,
    "todo-list": List
  },
  events: {
    add: function(input) {
      this.$broadcast("add", input);
    }
  }
});

其实改动就第一行。

然后我们需要创建一个 About 组件:

var About = Vue.extend({
  template: `
  <div id="about">
    <p>About Todo List V0.1.0</p>
    <p>Content here</p>
  </div>`
});

接下来是重点了,我们要创建一个实例 vm,这vm要负责切换这两个页面:

var vm = new Vue({
  el: "body",
  data: {
    currentView: "todo"
  },
  components: {
    "todo": Todo,
    "about": About
  }
});

这里我们定义了一个 currentView 字段,当然可以是任意名称,然后通过特殊的 component 标签来进行组件切换:

<component :is="currentView"></component>
  <ul>
    <li><label><input type="radio" name=‘page‘ value=‘todo‘ v-model=‘currentView‘> Home</label></li>
    <li><label><input type="radio" name=‘page‘ value=‘about‘ v-model=‘currentView‘> About</label></li>
  </ul>

上面的代码有两处需要注意:

  • 通过 component 这个特殊标签,然后用 :is 属性来进行组件的切换。
  • radio 通过双向绑定来修改 currentView 字段,从而实现点击之后就可以进行切换。

数据绑定的实现原理

Vue 把双向绑定称作 reactive,可以翻译为响应式数据绑定。内部是通过 ES5 定义的 gettersetter 方法实现的,所以不支持 IE8 及以下浏览器,这种实现方式有两个容易犯错的地方:

  • 如果在 data 上直接添加和删除属性是无法被检测到的,一般删除是不会的,但是可能会动态添加,这个时候应该通过 vm.$set(“name”, value) 的方式来添加。
  • 无法检测到对象内部的变化,也就是只能检测 data 的属性变化,如果 data.a 是一个对象,那么 data.a.b = 1 这种变化是无法被检测到的。这种情况下应该创建一个新的对象并赋值给 data.a 就行了。

异步更新机制

Vue 对DOM的更新是异步的! 这个异步是在一个异步队列中进行的,不过这个异步队列会在当前的 Event Loop 中执行完,所以如果修改了 Data 立刻去DOM中做查询操作是不对的,这个时候DOM还没有更新,正确的做法是这样做:

vm.msg = ‘new message‘ // change data
vm.$el.textContent === ‘new message‘ // false
Vue.nextTick(function () {
  vm.$el.textContent === ‘new message‘ // true
})

或者这样:

vm.$nextTick(function () {
  this.$el.textContent === ‘new message‘ // true
})

花了半天时间才看完组件,下面应该去看一下另一个重点: Directive

时间: 2024-11-05 16:39:42

七周七种前端框架四:Vue.js 组件和组件通信的相关文章

一款简单而不失强大的前端框架——【Vue.js的详细入门教程①】

↓— Vue.js框架魅力 —↓ 前言   Vue.js 是一个构建数据驱动的 web 界面的渐进式框架.Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件.Vue 只关注视图层并且采用自底向上增量开发的设计. Vue.js作为一个后起的前端框架,借鉴了Angular .React等现代前端框架/库的诸多特点,取得了相当不错的成绩.Vue.js 自身不是一个全能框架——它只聚焦于视图层.因此它非常容易学习,非常容易与其它库或已有项目整合.另一方面,在与相关工具和支

《七周七数据库》读书分享

# 七周七数据库 读书分享会第一期 2017.02.12 <七周七数据库> - Eric Redmond ## 预备知识 ACID:原子 一致 隔离 持久 CAP原则:一致性 可用性 分区容错性,在分布式环境下,至多只能同时满足其二 "小明,你的数据库作业呢?""我可以今天提交一半的作业,或者明天提交全部的作业,但无法在今天提交全部的作业.""...小明你给我滚出去!" 小知识点: * 原子性:原子性代表中间状态的透明性,想象从a账户

七周七Web开发框架——互动出版网

这篇是计算机类的优质预售推荐>>>><七周七Web开发框架> 延续畅销书七周七语言 七周七数据库的体例和风格 学习最流行的Web开发框架:Sinatra.CanJS.AngularJS.Ring.Webmachine.Yesod和Immutant 内容简介 本书带领读者认识和学习7种影响现代Web应用并改娈了Web开发方式的框架,以期给Web开发者带来启发和思考. 本书延续了同系列的畅销书<七周七语言>.<七周七数据库>的体例和风格.全书共8章,

《七周七语言:理解多种编程范型》のIo课后习题答案

哎,因为上周忙着写OAuth2.0服务端框架草稿 ,耽误了一周学习Io Language了. 本篇习题解答是接着 <七周七语言:理解多种编程范型>のRuby课后习题答案 Io是原型语言,类似于JavaScript,并不区别类和对象,所有的东东都是对象,对象的数据结构就是由键值表来维护的(在Io中就是所谓的槽),通过各种消息传递给对象来实现打印输出,复制对象等功能.因为语法非常简单(也木有语法糖),所以你可以尽情构建自己的库和功能. 第一天: 1. 对1+1求值,然后对1+"one&q

《七周七语言:理解多种编程范型》のruby课后习题答案

本系列是<七周七语言>的课后习题答案.这本书不拘泥于语法细节,而是横向比较各种编程语言(非热门)之间的编程范式. 是本对编程觉悟能有所帮助的好书,这里就不多做介绍了,感兴趣的同学不妨去看一下. 不得不说,Ruby的风格很黑客. 1. 打印字符串"Hello, world." puts "Hello, world." 2. 在字符串“Hello, Ruby.”中,找出"Ruby."所在下标. puts "Hello, Ruby

七周七数据库读书笔记(1)

七周七数据库读书笔记(1) 看到别人推荐买了这本书,决定以后每读一本书都开始写读书笔记 这本书的第二章开始部分简单的回顾了关系数据库的CRUD,这里主要讲一下数据库中INNER JOIN, LEFT JOIN和RIGHT JOIN的区别.这块很多初学数据库的人会搞不清楚而很多Blog写得又比较深反而不易理解. 先谈谈INNER JOIN INNER JOIN 我们一般解释为内联,通常来说INNER在SQL中可以被省略.简单来说就是通过两个表的某列将两个表相连.这里举例来说有假设数据库中有两张表(

《七周七语言》学习笔记——Ruby——第一天:找个保姆

感觉学了东西很快就会忘记,不会留下什么,趁这次看<七周七语言>,把其中觉得有用的东西记录下来,方便以后自己查阅,这篇应该就是记录的开始了. Ruby是一门面向对象的.解释型的.动态类型的脚本语言.面向对象,说明了这门语言具有封装.继承.多态这些特性:解释型,意味着它由解释器而不是编译器来执行:动态类型,即类型是在运行时绑定而非编译时绑定:脚本语言,则说明了它很短!易上手!并且只在调用的时候解释执行. 编程模型:一切皆对象,从一个数到bool值,都被定义为对象,都有自己的方法 判断结构:块形式和

转: Vue.js——60分钟组件快速入门(上篇)

转自: http://www.cnblogs.com/keepfool/p/5625583.html Vue.js——60分钟组件快速入门(上篇) 组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HTML元素,封装可重用的HTML代码,我们可以将组件看作自定义的HTML元素. 本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或

第一周技术周报-前端框架演变

前言 在本周接触了一个关于"前端框架演变"的话题.今天我们就来聊聊,它到底是怎么进行演变的. 前端框架演变 web1.0时代 此时前端展示数据比较简单,通常是由后台使用模板引擎直接渲染的.在这个时期都还未产生"前端工程师",更别谈前端的发展能有多快了.这个时代作者经历的并不多,比较也已经过去了很多年了,这里就不深究. web2.0时代 前端展示趋于复杂,所以逐渐就开始前后端分离式架构.此时有一个关键词ugc: user generate content 用户生成内容