Vue 组件化开发

Vue 组件化介绍

  • 组件化是 Vue 的精髓, Vue 就是由一个一个的组件构成的

  • 组件化思想是笔者认为世界上最美丽的思想, 此时不学, 更待何时!!!

    • (别再跟我说面向对象思想了, 我没有对象~~)

创建组件构造器对象

  • 调用Vue.extend()创建的是一个组件构造器

  • 通常在创建组件构造器时, 传入 template 代表我们自定义组件的模板

  • 该模板就是在使用到组件的地方, 要显示的 HTML 代码

  • 事实上, 这种写法在 Vue2.x 的文档中几乎看不到了, 因为"语法糖"的存在

Vue.extend({
  template: "html 模板代码",

  components: {
    // 注册子组件
  },

  data() {
    return Object;
  },

  methods: {
    // 方法
  },

  props: [] || {} // 获得组件的属性值 数组 | 对象
});

注册组件

  • Vue.component("组件标签名", 构造器对象)

  • 调用Vue.component()是将组件构造器注册为一个组件, 并且给它起了一个组件的标签名称

  • 所以需要传递两个参数:

    1. 注册组件的标签名(在 HTML 中使用该组件的标签名称)
    2. 组件构造器对象

模板代码注意事项

  • 模板代码必须有且只有一个根标签(template 不算根标签)

全局组件

  • 通过 Vue.component 注册的组件即为全局组件

  • 在所有 Vue 实例和所有组件中都可以使用该组件

局部组件

  • 通过 Vue 实例在 options 中的 components 属性中注册的组件

  • 只能在该实例中使用该组件

父子组件

  • 在父构造器中使用 components 属性注册子组件

  • Vue 实例对象也可以看成一个组件, 即根结点 root

  • 子组件只能在直接父组件内使用

    • 若要在 A 组件下使用某个子组件(非 A 注册, 但被其他组件注册过的子组件), 需在 A 组件上注册该子组件
  • 即组件树中, 父结点只能访问直接子结点

    • 若要访问孙子结点, 需要在将孙子结点变成父结点的子结点, 或将孙子结点注册为全局组件

组件语法糖 (!!!推荐写法!!!)

注册全局组件

Vue.component("cpn", {
  template: `<div>hello world</div>`
});

组册局部组件

  • 该写法是将 template模板 代码直接写入 components(不推荐, 容易造成代码阅读困难)

  • 后面我们将介绍模板分离写法(最主流写法, 也使代码更容易阅读)

components: {
   "cpn": {
    template: `<div>hello world</div>`
  }
}

即省略构造器, 直接在构造器参数上填写构造器对象, 内部还是调用了 Vue.extend({...})

模版分离写法

script 标签写法

  • 在 body 中使用 script 标签写模板代码, 且用 id 进行绑定
<script type="text/x-template" id="cpn">
  <div>hello world</div>
</script>

注册全局组件

Vue.component("cpn", { template: "#cpn" });

注册局部组件

components: {
  "cpn": { template: "#cpn" }
}

template 标签写法

  • 该写法为最常用, 且最实用, 创建 .vue 来抽离组件将更有利于代码阅读和维护

  • 方案一: 在 body 中使用 template 标签写模版代码, 且用 id 进行绑定

<template id="cpn">
 <div>hello world</div>
</template>
  • 方案二(推荐写法): 你也可以创建 .vue 文件

    • 在该文件中写入 template 模版代码
    • 以及组件 JS 逻辑代码
    • CSS 样式代码
  • 且不需要绑定 id, 在注册时引入文件即可

  • 注意:

    • 在父组件中使用子组件时必须将驼峰式转换成用 - 链接的形式

以下为 HelloWorld.vue 文件的 template 实例代码

<template>
 <div>hello world{{ name }}</div>
</template>

<script>
  export default {
    data() {
      return {
        name: "zjh"
      }
    }
  }
</script>

以下为父组件 template 中使用子组件

<template>
 <hello-world></hello-world>
</template>
  
<script>
  // 以下为父组件注册, 路径可以不需要携带.vue后缀, 这件事情Vue以及帮我们做好了, 放心写即可
  import HelloWorld from "./HelloWorld"

  export default {
    components: {
      HelloWorld
    }
  }
</script>

注册全局组件

Vue.component("cpn", { template: "#cpn" });

注册局部组件

components: {
  "cpn": { template: "#cpn" }
}

组件的 data 数据

  • 组件不可以直接访问 Vue 实例或其他组件中的数据
    • 因为:
      • 组件是一个单独的功能模块的封装, 这个模块有属于自己的 HTML 模板, 也应该有属于自己的数据 data

组件内部属性 data

data() {
  return {
    message: "hello world"
  };
}
  • 注意:
    • 组件中的 data实例中的 data 有区别
    • 组件中的 data 必须是一个函数, 且函数返回一个对象, 变量声明在对象中
    • 因为:
      • 对象是引用类型, 组件可能会被多个实例同时引用
      • 如果 data 值为对象, 将导致多个实例共享一个对象, 其中一个组件改变 data 属性值, 其它实例也会受到影响
    • 上面解释了 data 不能为对象的原因, 这里我们简单说下data为函数的原因
      • data 为函数, 通过 return 返回对象的拷贝, 致使每个实例都有自己独立的对象, 实例之间可以互不影响的改变 data 属性值

组件 data 是函数, 且返回对象的原理

  • 在使用组件时, 因为 data 函数每次调用时会返回一个新的对象

  • 即每个组件的实例得到的 data 都是独立的, 所以组件在使用时的变量就没有连锁反应了

父子组件的通信

下传数据 (父组件传递数据给子组件)

props

  • props 在组件中, 使用选项 props 来声明需要从父级接收到的数据

  • 在父组件模版中, 如果有多个子组件模板, 那么父组件传递给子组件的属性只作用与该子组件的对应模板

    • 即父组件传递给子组件 A 模板, 那么就只有 A 组件有该属性, 子组件的其他模板并没有该属性
  • 注意:

    • Vue 的 v-bind 不支持小驼峰式(即传递失败), 所以一般使用 - 来作为单词分割符
  • 如果 props 属性要写小驼峰式, 那么 v-bind 必须要用 - 来链接单词从而转换成对应的小驼峰属性, 且使用变量时必须是 props 中定义的小驼峰式属性

  • 子组件不能修改 props 属性的值, 应该要让父组件来对 props 的值进行修改

    • 如果需要实现通过下传得到的数据来与子组件进行双向绑定(v-model), 需要使用 data 或 computed 来实现双向绑定
    • 详情参见 "父子组件案例"

props 字符串数组

以下代码为父组件向子组件传递数据

<template>
  <div id="app">
    <child :c-message="message"></child>
  </div>
</template>

<script>
  import Child from "./Child"

  export default {
    data() {
      return {
        message: "hello, world"
      }
    },
    components: {
      Child
    }
  }
</script>

以下代码为子组件接收并使用父组件传递的数据

<template>
  <div>{{ cMessage }}</div>
</template>

<script>
  // 以下代码为子组件接收父组件传递的数据
  export default {
    props: ["cMessage"]
  }
</script>

  • 字符串数组, 数组中的字符串就是传递时的名称, 通过 v-bind 获得父组件的数据

  • 为子组件添加属性, 属性值为父组件的变量值

props 对象

  • 对象可以设置传递时的类型, 也可以设置默认值

  • 类型:

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
    • null(匹配任何类型)
props: {
  // 1、类型限制
  cMovies: Arraym,
  cMessage: String

  // 2、提供默认值, 以及使用组件时属性是否为必传值
  cMessage: {
    type: String,
    default: "hello world",
    required: true
  },

  // 3、类型是Array | Object时, 默认值必须是一个函数
  cMovies: {
    type: Array,
    default() {
      return [];
    }
  },

  // 4、多个可能的类型
  cMessage: [String, Number],

  // 5、自定义验证函数
  cMessage: {
    validator(value) {
      // 这个值必须匹配下列字符串中的一个
      return ["success", "warning", "danger"].indexOf(value) !== -1;
    }
  }

  /* 6、自定义类型
     function Zjh() {
       name: "Sunny",
       age: 20
     }
  */
  cMessage: Zjh
}

上传数据 (子组件传递数据给父组件)

自定义事件 Vue.$emit 和 v-on:

  • 同 v-bind 一样不能写驼峰(如果使用Vue脚手架可以写驼峰式)

  • 流程:

    • 子组件的浏览器默认事件触发后
    • 通过 this.$emit("自定义对象名称", 数据)发送事件
      • 自定义事件, 将该自定义事件发送到父组件上

以下代码为子组件模板代码

<template>
  <button @click="btnClick">button</button>
</template>

<script>
  export default {
    data() {
      return {
        message: "hello world"
      };
    },
    methods: {
      btnClick() {
        this.$emit("itemChild", this.message);
      }
    }
  }
</script>
  • 在父组件中, 通过 v-on 来监听子组件的自定义事件

  • 此时的 cpnClick 省略括号, 则 Vue 会自动把数据传入该事件方法中

    • 因为不是浏览器默认事件, 所以没有 event 对象
  • 此时在父组件的 methods 中实现监听该事件的方法即可获得子传父的数据

以下代码为父组件模板代码

<template>
  <div id="app">
    <child @itemChild="cpnChild"></child>
    {{ message }}
  </div>
</template>

<script>
  import Child from "./Child"

  export default {
    data() {
      message: ""
    },
    methods: {
      cpnChild(message) {
        this.message = message;
      }
    }
  }
</script>

父子组件案例

父组件下传数据, 子组件与自身表单进行双向绑定, 且同时与父组件双向绑定

父组件模板代码

<template>
  <div id="app">
    <!-- 1、父组件通过v-bind给子组件传递数据 -->
    <!-- 2、父组件监听子组件的自定义事件 -->
    <child :number="num" @numChange="numChange"></child>
  </div>
</template>

<script>
  import Child from './Child'

  export default {
    name: 'App',
    components: {
      Child
    },
    data() {
      return {
        num: "1"
      }
    },
    methods: {
      // 3、父组件自定义事件触发, 通过emit传来的数据修改自身的值
      numChange(value) {
        this.num = value;
      }
    }
  }
</script>

子组件模板代码

<template>
  <div>
    <h2>props:{{ number }}</h2>
    <h2>data:{{ dnumber }}</h2>

    <!-- 3、进行双向绑定 -->
    <input type="text" :value="dnumber" @input="numInput" />
  </div>
</template>

<script>
  export default {
    // 1、子组件通过props获得数据
    props: {
      number: [Number, String]
    },

    // 2、因为props得到的数据不能直接修改, 所以在子组件内拷贝props中的数据
    data() {
      return {
        dnumber: this.number
      };
    },
    methods: {
      // 4、在触发input事件后, 将最新的值通过emit传递给父组件
      numInput(e) {
        this.dnumber = e.target.value;
        this.$emit("numChange", this.dnumber);
      }
    }
  }
</script>

父子组件的访问方式

父组件访问子组件

$children

  • 在父组件中使用this.$children即可获得一个数组
    • 该数组中包含了父组件下所有子组件对象 VueComponent

$refs (!!!推荐使用!!!)

  • 在父组件中使用this.$refs获得一个空对象

  • 为这个对象添加子组件即可获得第一个子组件对象

<template>
  <div>
    <child ref="childOne"></child>
    <child></child>
    <p>为想要添加到对象中的子组件添加属性</p>
    <p>(ref="这是对象中的属性名里面存放了该子组件的对象(VueComponent)")</p>
    <button @click="showRefs">查看ref子组件</button>
  </div>
</template>

<script>
  export default {
    methods: {
      showRefs() {
        console.log(this.$refs.childOne);
      }
    }
  }
</script>

子组件访问父组件

$parent (不推荐使用, 耦合度太高)

  • 在子组件中使用this.$parent即可获得父组件对象
    • 如果是 Vue 实例下的子组件, 则获得 Vue 实例对象Vue
    • 如果不是 Vue 实例下的子组件, 则获得父组件对象VueComponent

访问根组件

$root

  • 在 Vue 实例下, 任意组件通过this.$root即可获得 Vue 的实例对象(Vue)

  • Vue 实例的 root 是自己本身

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 认识组件化 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展...
    独调1997阅读 786评论 0 0
  • 一、什么是组件化开发 人面对复杂问题的处理方式:任何一个人处理信息的逻辑能力都是有限的所以,当面对一个非常复杂的问...
    c_gentle阅读 2,101评论 0 1
  • 组件化开发步骤:1.创建组件构造器 Vue.extend({})2.注册组件 Vue.component()3.使...
    似朝朝我心阅读 419评论 0 7
  • 上期回顾 条件判断(v-if、v-show)的基本使用、开发时选择条件渲染案例(input添加key的区别)循环遍...
    kevin5979阅读 702评论 0 1
  • 一.如何创建Vue全局组件 特点:在任何一个Vue控制的区域都能使用1.创建组件构造器注意点:创建组件模板的时候只...
    Angel_6c4e阅读 331评论 0 4