文章目录
# 组件化
## 不用组件的两个【灾难性问题】
随着ajax异步请求的出现,慢慢形成了前后端的分离
客户端需要完成的事情越来越多,代码量也是与日俱增。
为了应对代码量的剧增,我们通常会将代码组织在<mark>多个js文件中</mark>,进行维护。
但是这种维护方式,依然<mark>不能避免一些灾难性的问题</mark>。
1、如<mark>全局变量同名</mark>问题
2、又如 代码的编写方式对js文件的依赖顺序几乎是强制性的
但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。
而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。
## Vue 组件化思想
官方 - 《组件基础》 - https://cn.vuejs.org/v2/guide/components.html
-
我们希望开发出一个个<mark>独立可复用</mark>的小组件来构造我们的应哟
-
那样, 任何都会被抽象成一颗组件数
(其中,连线指的是下级对上级组件的依赖) -
这样,我们的代码更加方便组织和管理,并且扩展性也更强。
## 注册组件
注册组件的基本步骤
- 创建组件构造器
调用Vue.extend()
方法<mark>创建组件构造器</mark> - 注册组件
调用 Vue.component() 方法 <mark>注册组件</mark> - 使用组件
<mark>在Vue实例的作用域范围内使用组件</mark>
基本使用
<body>
<div id='app'>
<!-- 3. 使用组件 -->
<my-cpn></my-cpn>
</div>
<script src="../dist/vue.js"></script>
<script> // 1.创建组件的构造器对象 const cpnC = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容</p> </div> ` }) // 2. 注册组件 (全局组件,意味着可以在多个Vue的实例下使用) Vue.component('my-cpn', cpnC) const app = new Vue({ el: '#app', data: { } }) </script>
</body>
全局组件 、 局部组件
- 全局组件,意味着可以在多个Vue的实例下使用(例子如上)
- 局部组件
<body>
<div id='app'>
<!-- 3. 使用组件 -->
<my-cpn></my-cpn>
</div>
<script src="../dist/vue.js"></script>
<script> // 1.创建组件的构造器对象 const cpnC = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容</p> </div> ` }) const app = new Vue({ el: '#app', data: { }, components: { // 2. 注册局部组件 'my-cpn': cpnC } }) </script>
</body>
父组件和子组件
<body>
<div id='app'>
<cpn2></cpn2>
</div>
<script src="../dist/vue.js"></script>
<script> // 1.创建第一个组件 const cpn1 = Vue.extend({ template: ` <div> <p>我是内容 cpn1</p> </div> ` }) // 2.创建第一个组件 const cpn2 = Vue.extend({ template: ` <div> <h2>我是标题 cpn2</h2> <cpn1></cpn1> </div> `, components: { cpn1: cpn1 } }) const app = new Vue({ el: '#app', data: { }, components: { // 2. 注册局部组件 cpn2: cpn2 } }) </script>
</body>
<mark>cpn2包含了 cpn1,所以cpn2是父组件</mark>
语法糖:组件注册
省略 Vue.extend ,直接在 component里面定义 <mark>组件构造器</mark>
<body>
<div id='app'>
<cpn></cpn>
</div>
<script src="../dist/vue.js"></script>
<script> Vue.component('cpn', { template: ` <div> <h2>我是标题</h2> <p>我是内容,哈哈哈</p> </div> ` }) const app = new Vue({ el: '#app', data: { } }) </script>
</body>
## 技巧:模板分离写法
把模板写在 标签内
<body>
<div id='app'>
<cpn></cpn>
</div>
<!-- 1. -->
<script type="text/x-template" id='cpn'> <div> <h2>我是标题</h2> <p>我是内容,哈哈哈</p> </div> </script>
<script src="../dist/vue.js"></script>
<script> Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: { } }) </script>
</body>
或者这样
<body>
<div id='app'>
<cpn></cpn>
</div>
<!-- 2. -->
<template id='cpn'>
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: {} }) </script>
</body>
## 组件访问实例数据 【无法访问的】
如果组件内容需要动态显示,需要在组件内定义数据(<mark>数据不能放在顶级实例内,组件是无法访问到的</mark>)
另外:
<mark>组件内部的 data 属性值必须是个 函数</mark>
## 为什么 组件内部的 data 属性值必须是个 函数???
因为,Vue为了防止多个同一种组件之间的数据相互影响
详细看 : https://cn.vuejs.org/v2/guide/components.html#data-必须是个函数
即,写法要如下
<body>
<div id='app'>
<cpn></cpn>
</div>
<!-- 2. -->
<template id='cpn'>
<div>
<h2>{{title}}</h2>
<p>我是内容,哈哈哈</p>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> Vue.component('cpn', { template: '#cpn', data() { return { title: '我是个标题' } } }) const app = new Vue({ el: '#app', data: {} }) </script>
</body>
## 父子组件之间通讯
- 通过 props 向子组件传递数据
- 通过 事件 向父组件发送消息
通过 props 向子组件传递数据
<body>
<div id='app'>
<cpn v-bind:cmovies='movies' :cmessage='message'></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cmessage}}</h2>
<div>{{cmovies}}</div>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> const cpn = { template: '#cpn', props: ['cmovies', 'cmessage'] } const app = new Vue({ el: '#app', data: { movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'], message: '你好啊' }, components: { cpn: cpn } }) </script>
</body>
通过 props 向子组件传递数据 - 验证数据类型 - type
<body>
<div id='app'>
<cpn v-bind:cmovies='movies' :cmessage='message'></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cmessage}}</h2>
<div>{{cmovies}}</div>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> const cpn = { template: '#cpn', props: { cmovies: Array, cmessage: String } } const app = new Vue({ el: '#app', data: { movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'], message: '你好啊' }, components: { cpn: cpn } }) </script>
</body>
如果偷偷把 message 类型改一下(如下)
const app = new Vue({
el: '#app',
data: {
movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'],
message: ['你好啊']
},
components: {
cpn: cpn
}
})
会尽量将数据转为 相依类型显示
同时报错
通过 props 向子组件传递数据 - 验证数据类型 - validator + required
通过 props 向子组件传递数据 - 默认值 - default
<body>
<div id='app'>
<cpn v-bind:cmovies='movies' :cmessage='message'></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cmessage}}</h2>
<div>{{cmovies}}</div>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> const cpn = { template: '#cpn', props: { cmovies: Array, cmessage: { type: String, default: '你好骚啊' } } } const app = new Vue({ el: '#app', data: { movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'], //message: ['你好啊'] }, components: { cpn: cpn } }) </script>
</body>
通过 props 向子组件传递数据 - 驼峰标识
结论:
- 在不用脚手架情况下,写html标签时候,必须不能用驼峰(html不区分大小写)
- 用了脚手架,随意()
【误区】props 双向绑定
如果我们在子组件内做 props 的双向绑定,打开控制台,发现会报错(如下图)
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<cpn :num1='num1'></cpn>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<h2>{{num1}}</h2>
<input type="text" v-model="num1">
</div>
</template>
<script src="../dist/vue.js"></script>
<script> // 1.子组件 const cpn = { template: '#cpn', props: { num1: String } } // 2.父组件 const app = new Vue({ el: '#app', data: { num1: '1' }, components: { cpn: cpn }, }) </script>
</body>
看报错信息,知道是官方不推荐。
一个很大的原因是 :<mark>这样违背了 组件的独立性</mark>(如,子组件1改了值 ⇒ 父组件值也改 ⇒ 子组件2值也改了 ⇒ 【组件1和组件2的值绑定了(不独立)】)
报错信息里面也说了处理方法
Instead, use a data or computed property based on the prop's value. Prop being mutated: "num1"
翻译:用一个 data 或者 计算属性,接收传过来的 prop 值作为初始值。
解决方案如下
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<!-- 驼峰命名到html标签中,要转换成'xxx-xx' -->
<cpn :father-num='fatherNum'></cpn>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<h2>{{num}}</h2>
<input type="text" v-model="num">
</div>
</template>
<script src="../dist/vue.js"></script>
<script> // 1.子组件 const cpn = { template: '#cpn', props: { fatherNum: String }, data() { return { num: this['fatherNum'] } } } // 2.父组件 const app = new Vue({ el: '#app', data: { fatherNum: '1' }, components: { cpn: cpn }, }) </script>
</body>
通过 事件 向父组件发送消息
注意 驼峰标识 问题
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<cpn @item-click='cpnClick'></cpn>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> // 1.子组件 const cpn = { template: '#cpn', data() { return { categories: [{ id: 73944, name: 'Route' }, { id: 72827, name: 'generating' }, { id: 52405, name: 'Intelligent RAM Books' }, { id: 86224, name: 'Usability' }, ] } }, methods: { btnClick(item) { this.$emit('item-click', item); } } } // 2.父组件 const app = new Vue({ el: '#app', data: {}, components: { cpn: cpn }, methods: { cpnClick(value) { console.log(value) } }, }) </script>
</body>
父子通信案例:
- 子元素接收父元素初始值
- 子元素 data双向绑定
- 父元素接收子元素修改值
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<h2>fatherNum:{{fatherNum}}</h2>
<!-- 驼峰命名到html标签中,要转换成'xxx-xx' -->
<cpn :father-num='fatherNum' @num-changed='numChangedHandler'></cpn>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<h2>num:{{num}}</h2>
回车把值返回父组件:<input type="text" v-model="num" v-on:keyup.enter="numHandler(num)">
</div>
</template>
<script src="../dist/vue.js"></script>
<script> // 1.子组件 const cpn = { template: '#cpn', props: { fatherNum: String }, data() { return { num: this['fatherNum'] } }, methods: { numHandler(num) { this.$emit('num-changed', num); } }, } // 2.父组件 const app = new Vue({ el: '#app', data: { fatherNum: '1' }, components: { cpn: cpn }, methods: { numChangedHandler(value) { this.fatherNum = value; } }, }) </script>
</body>
## 子组件最顶层只能有一个element
否则报错(如下)
(下面)才是正确的
## 父子组件之间 - 访问方式
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
- 父组件访问子组件:使用
$children
或$refs
- 子组件访问父组件:使用
$parent
$children - 父访子 - 【不常用,常用 ref】
我们先来看下 $children
的访问
this.$children
是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的 message
状态。
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<button @click='btnClick'>打印子组件</button>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../dist/vue.js"></script>
<script> const app = new Vue({ el: '#app', data: {}, methods: { btnClick() { console.log('展示子组件:', this.$children) this.$children[0].showMessage(); } }, components: { cpn: { template: '#cpn', methods: { showMessage() { console.log('showMessage'); } }, } } }) </script>
</body>
ref - 父访子
$children
的缺陷:
- 通过
$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。 - 当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
- 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用
$refs
$refs
的使用:
$refs
和ref
指令通常是一起使用的。- 首先,我们通过ref给某一个子组件绑定一个特定的ID。
- 其次,通过
this.$refs.ID
就可以访问到该组件了。
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<cpn></cpn>
<cpn ref="cpn"></cpn>
<cpn></cpn>
<button @click='btnClick'>打印子组件</button>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../dist/vue.js"></script>
<script> const app = new Vue({ el: '#app', data: {}, methods: { btnClick() { console.log('展示子组件:', this.$children) console.log('展示 $refs.cpn :', this.$refs['cpn']) console.log('展示 $refs.cpn.$el :', this.$refs['cpn'].$el) this.$refs.cpn.showMessage(); } }, components: { cpn: { template: '#cpn', methods: { showMessage() { console.log('showMessage'); } }, } } }) </script>
</body>
$parent
- 子访问父
如果我们想在子组件中直接访问父组件,可以通过 $parent
注意事项:
- 尽管在Vue开发中,我们允许通过
$parent
来访问父组件,但是在真实开发中尽量不要这样做。 - <mark>子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了</mark>。
- 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
- <mark>另外,更不好做的是通过 $parent`` 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。</mark>
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<cpn></cpn>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<button @click='btnClick'>点击 - 控制台打印父组件</button>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> const cpn = { template: '#cpn', methods: { btnClick() { console.log(this.$parent); } }, } const app = new Vue({ el: '#app', data: {}, components: { cpn: cpn } }) </script>
</body>
$root 访问根组件
根组件也就是 我们 new 出来的 Vue
用 vue dev-tool 看腋下
上面界面 代码如下
<body>
<!-- 2.父组件模板 -->
<div id='app'>
<p>root</p>
<cpn></cpn>
<p>root</p>
</div>
<!-- 1.子组件模板 -->
<template id="cpn">
<div>
<button @click='btnClick'>点击 - 控制台打印【 根 root 】组件</button>
</div>
</template>
<script src="../dist/vue.js"></script>
<script> const cpn = { template: '#cpn', methods: { btnClick() { console.log(this.$root); } }, } const app = new Vue({ el: '#app', data: {}, components: { cpn: cpn } }) </script>
</body>
我们点击一下,就能访问到 root 组件了(跟上面例子结果一样,是因为这个的父组件就是根组件,感兴趣套多层组件看看)