目录
8. 坑: 一旦使用子组件,则引入时,子组件必须在父组件之前先引入!
11. 示例: 实现todo案例中父组件给子组件传值(父子)
【前文回顾】👉 vue中axios的使用及vue生命周期详解_07
一. 组件
1. 什么是组件
拥有专属的HTML+CSS+JS+数据的独立的可重用的页面功能区域
🌲 组件是什么?
组件是Vue中的一个重要概念,是一个可以重复使用的Vue实例,它拥有独一无二的组件名称,它可以扩展HTML元素,以组件名称的方式作为自定义的HTML标签。组件可大大提高了代码的复用率。因为组件是可复用的Vue实例,所以它们与new Vue()接收相同的选项,例如data,computed、watch、methods以及生命周期钩子等。仅有的例外是像el这样根实例特有的选项。
把一些公共的模块抽取出来,然后写成单独的的工具组件或者页面,在需要的页面中就直接引入即可。那么我们可以将其抽出为一个组件进行复用。
例如 页面头部、侧边、内容区,尾部,上传图片,等多个页面要用到一样的就可以做成组件,提高了代码的复用率。
2. 为什么用组件
重用
3. 何时使用组件
今后只要一个功能可能会被反复使用时,都用组件
4. 如何使用组件
(1). 创建组件:
组件
Vue.component("组件名",{
//模板/界面
template:`HTML片段`, //必须用唯一父元素包裹
data(){ // 这里的data是函数
return { //模型对象
数据/变量: 值,
... : ...
}
},
/******以下内容就和new Vue()完全一样了*******/
methods:{ 函数 },
watch:{ 监视函数 },
computed:{ 计算属性 },
其它生命周期函数...
})
🌲 为什么data是函数——data(){...}
原因:为了反复调用,这样就能反复创建多个模型对象,这样每个组件都有属于自己专属的模型对象,且互不(干扰)影响(只有函数才能反复调用,每次调用时,就会return出一个对象, 反复创建多个模型对象,每个组件就有属于专属的模型对象)
(2). 在页面上反复使用组件:
VUE中一个组件,其实就是一个可重用的自定义标签而已!
<组件名></组件名>
5. 组件的加载过程
(1). Vue.component()创建一个组件对象,保存在内存中vue大家庭里备用
(2). new Vue()在扫描页面过程中,发现不是标准的HTML元素的标签,就会自动去内存中vue大家庭中找是否有同名的组件对象
(3). 一旦找到同名的组件对象,做3件事:
a. 用组件对象的template代替页面中<组件名>标签的位置
b. 自动调用组件对象的data()函数,在data函数内部先创建一个新的模型对象,包含新的模型变量。然后将新创建的模型对象返回给当前组件所在的位置
c. 将methods中的事件处理函数,分发给每个组件的副本
(4). 结果: 在页面中就会形成多个麻雀虽小五脏俱全的小组件副本。每个组件副本内部的原型原理和过程与new Vue()的原理是完全一样的!
6. 示例: 定义计数器组件,并反复使用
1_component.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<ul>
<li>
<my-counter></my-counter>
</li>
<li>
<my-counter></my-counter>
</li>
<li>
<my-counter></my-counter>
</li>
</ul>
</div>
<script>
//因为界面中修改数量部分经常被重用,所以应该下封装为一个组件,再在界面中反复使用组件
//创建一个组件:
Vue.component("my-counter",{
//组件其实就是一个缩微的new Vue()
//麻雀虽小五脏俱全!
//1 做界面:和new Vue()做界面过程一样
//模板: 一次定义,反复生成一模一样的副本!
//1.1 也找可能发生变化的位置
//本例中: span的内容可能发生变化
//1.2 也找触发事件的元素
//本例中: 两个button可能触发事件
template:`<div>
<button @click="minus">-</button>
<span>{{n}}</span>
<button @click="add">+</button>
</div>`,
//2. 创建模型: 大致和new Vue()相同
data(){//变为一个函数
return {//相当于之前的data:{}
//因为界面中需要一个变量
n:0
}
},
methods:{ //和以前没有差别
//因为界面中需要2个函数
add(){
this.n++;
},
minus(){
if(this.n>0){
this.n--
}
}
}
})
//必须加new Vue()
new Vue({//扫描页面,识别vue的各种功能!
el:"#app"
})
</script>
</body>
</html>
运行结果:
二. 组件化开发
1. 什么是组件化开发
拿到一个页面后,先划分组件。然后再把每个组件分给不同的人去开发!最后运行时,再将所有组件合并为一个页面运行!
2. 为什么要用组件化开发
使用组件化开发的2个原因:
(1). 极其便于多人协作开发!极大提高开发的效率
(2). 松耦合: 即使一个组件出错,不影响整个页面中其它组件的功能!
3. 何时使用组件化开发
今后所有的前端框架项目都是用组件化开发实现的。
4. 如何使用组件化开发
(1). 拿到功能/页面时,先划分组件(也就是我们所说的功能区域):
a. 位置
b. 是否重用
(2). 为每个组件,在独立的js文件中创建组件对象
(3). 在父组件中,希望加载子组件的位置,用<子组件名>占位
(4). 在唯一完整的html页面中,先引入vue.js,再引入所有组件的独立js文件
(5). 创建new Vue()对象,在<div id="app">内,用<父组件>占位
5. 产生的问题
Vue.component()创建的组件,称为全局组件。可放在任何位置!没有限制!但是,有些子组件,离开父组件单独使用,是没有意义的!所以不应该随意在父组件外部使用子组件!
6. 组件化原理
其实vue中共有三大类组件:
(1). new Vue() 根组件: 整个项目只有一个根组件,监控整个界面范围。
(2). Vue.component()全局组件: 可在界面中任何位置使用,没有限制
(3). 子组件: 被限制只能在一个指定的父组件内使用的组件。一旦超出指定的父组件范围就报错!
7. 如何解决
今后只要限制在指定父组件内才能使用的组件都要创建为子组件,3步:
(1). 不要用Vue.component()创建,而应该将子组件创建为一个普通的js对象。但是对象的内容,要符合vue组件的格式要求:
//子组件对象名,应该是将来组件标签名的驼峰命名形式
// 比如: <todo-add>,则子组件对象名应该为: todoAdd
// <todo-list>,则子组件对象名应该为: todoList
var 对象名={
template:`HTML片段`,
data(){ return { ... } },
methods:{ ... },
... ...
}
(2). 在父组件对象中添加一个新的成员:
父组件对象:{
template:xxx,
data(){ return { ... }},
... ...,
components:{ 子组件对象名, ... }
} todoAdd 或 todoList
(3). 在父组件的template中,使用组件标签引入子组件:
子组件的标签名,必须恢复成-分割
vue会自动将驼峰命名翻译为-分割!
比如: <todo-add> <todo-list>
8. 坑: 一旦使用子组件,则引入时,子组件必须在父组件之前先引入!
9. 示例: 使用组件化开发,实现todo案例的界面部分
todo1/js/todoAdd.js
//创建todo-add组件
var todoAdd={
template:`<div>
<input><button>+</button>
</div>`
}
todo1/js/todoItem.js
//创建todo-item组件
var todoItem={
template:`<div>
1. 吃饭 <a href="javascript:;">×</a>
</div>`
}
todo1/js/todoList.js
//创建todo-list组件
var todoList={
template:`<ul>
<li><todo-item></todo-item></li>
<li><todo-item></todo-item></li>
<li><todo-item></todo-item></li>
</ul>`,
//todoItem是todoList的子组件
components:{ todoItem }
}
todo1/js/todo.js
//创建todo组件
Vue.component("todo",{
template:`<div>
<h3>待办事项列表</h3>
<todo-add></todo-add>
<todo-list></todo-list>
</div>`,
//todoAdd和todoList是todo的子组件
components:{ todoAdd, todoList }
})
todo1/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<script src="js/todoAdd.js">
//是todo的子
</script>
<script src="js/todoItem.js">
//是todoList的子
</script>
<script src="js/todoList.js">
//是todo的子
</script>
<script src="js/todo.js"></script>
</head>
<body>
<div id="app">
<todo></todo>
</div>
<script>
new Vue({
el:"#app"
});
</script>
</body>
</html>
运行结果:
10. 组件间传参
(1). 问题: Vue中父组件的成员,子组件也无权使用!
(2). 原理: Vue中每个组件的数据是专属的!
(3). 解决: 其实组件之间可以传递数据的!
(4). 父给子: 2步:
a. 父组件在子组件身上放一个数据:
父组件的template中:
<子组件 :自定义属性名="父组件变量"></子组件>
b. 子组件从父组件放变量的自定义属性中取出父组件传来的值
子组件对象中:
var 子组件={
template:xxx,
data(){ return { ... } }, //自己定义的
//属性,从自定义属性中获取变量值
props: [ "自定义属性名" , ... ] //父组件给的
//结果: props中的自定义属性名,等效于当前子组件中的data中的变量。用法和data中的变量完全一样!
this.自定义属性名
或
{{自定义属性名}}
}
11. 示例: 实现todo案例中父组件给子组件传值(父子)
todo2/js/todoAdd.js
//创建todo-add组件
var todoAdd={
template:`<div>
<input><button>+</button>
</div>`
}
todo2/js/todoItem.js
//创建todo-item组件
var todoItem={
template:`<div>
{{i+1}}. {{t}} <a href="javascript:;">×</a>
</div>`,
props:["t","i"] // 父组件todo-list传过来的t,i
}
todo2/js/todoList.js
//创建todo-list组件
var todoList={
template:`<ul>
//task是自定义属性名,v-for可以通过遍历 props:["tasks"]中接受到的task,
//即第一个口袋,获得爹即父组件todo给的数组
<li v-for="(t,i) of tasks" :key="i">
<todo-item :t="t" :i="i"></todo-item>
</li>
</ul>`,
//task是todo里todo-list冒号后的自定义属性名task,即第一个口袋
props:["tasks"],
//todoItem是todoList的子组件
components:{ todoItem }
}
补:变量取名保持一致性原则
强烈建议我们在做项目时有一个基本的要求,就是在项目当中,任何位置表示同一个东西,命名一定要保持一致,以便不懵逼。所以上面自定义属性和等号后的变量保持一致。
todo2/js/todo.js
//创建todo组件
Vue.component("todo",{
template:`<div>
<h3>待办事项列表</h3>
<todo-add></todo-add>
//=号后的变量task是data(){}里的数组task
<todo-list :tasks="tasks"></todo-list>
</div>`,
data(){
return {
tasks:[ "吃饭", "睡觉", "打亮亮"]
}
},
//todoAdd和todoList是todo的子组件
components:{ todoAdd, todoList }
})
todo2/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<script src="js/todoAdd.js">
//是todo的子
</script>
<script src="js/todoItem.js">
//是todoList的子
</script>
<script src="js/todoList.js">
//是todo的子
</script>
<script src="js/todo.js"></script>
</head>
<body>
<div id="app">
<todo></todo>
</div>
<script>
new Vue({
el:"#app"
});
</script>
</body>
</html>
运行结果:
💥 扩展:this判断—8种指向
this 8种指向: 判断this,一定不要看定义在哪儿!只看调用时!
➡️ 1. obj.fun() this->obj
➡️ 2. fun() 或 (function(){ ... })() 或 多数回调函数 或 定时器函数 this->window
➡️ 3. new Fun() this->new正在创建的新对象
➡️ 4. 类型名.prototype.共有方法=function(){ ... } this->将来谁调用指谁,同第一种情况
➡️ 5. DOM或jq中事件处理函数中的this->当前正在触发事件的DOM元素对象
如果需要使用简化版函数,必须$(this)
➡️ 6. 箭头函数中的this->箭头函数外部作用域中的this
➡️ 7. jQuery.fn.自定义函数=function(){ ... } this->将来调用这个自定义函数的.前的jQuery子对象,不用再$(this)
➡️ 8. new Vue()中methods中的函数中的this->当前new Vue()对象
❣️ 总结:知识点提炼
1. MVVM: 界面View+模型Model+视图模型ViewModel
2. Vue绑定原理: 访问器属性+虚拟DOM树
变量被修改时: 访问器属性发出通知,虚拟DOM树扫描并仅更新受影响的元素
3. 虚拟DOM树优点:
(1). 小: 只包含可能变化的元素。
(2). 遍历查找快
(3). 修改效率高: 只修改受影响的元素。
(4). 避免重复编码: 已封装DOM增删改查代码
4. Vue功能3步:
(1). 先创建增强版的界面:
a. 整个界面必须包含在一个唯一的父元素下:
通常是<div id="app">
b. 可能变化的元素内容用{{自定义变量名}}标记
c. 触发事件的元素用@click="自定义处理函数名"标记
(2). 再创建new Vue()对象,其中el:指向new Vue()要监控的页面区域
(3). 在new Vue()对象内定义模型对象data和methods
a.界面所需的所有变量都放在data中
b.界面所需的所有事件处理函数都放在methods中
5. 总结: 绑定语法+13种指令
(1). 如果元素的内容需要随变量自动变化: {{}}
(2). 如果元素的属性值需要随变量自动变化: :
(3). 控制一个元素显示隐藏: v-show //使用display:none隐藏元素
(4). 控制两个元素二选一显示: v-if v-else //使用删除元素方式隐藏元素
(5). 多个元素多选一显示: v-if v-else-if v-else
(6). 只要反复生成多个相同结构的元素组成列表时: v-for :key="唯一标识"
强调: 为什么必须加:key="i"?给每个元素副本添加唯一标识。修改数组中某个元素值时,避免重建整个列表,只需要修改一个DOM元素副本即可!提高修改效率。
(7). 只要绑定事件: @ $event
(8). 防止用户短暂看到{{}}: v-cloak和v-text
(9). 只要绑定原始HTML代码片段内容: v-html
(10). 如果元素的内容只在首次加载时绑定一次,之后都不会改变: v-once
优化: 减少虚拟DOM树中元素个数。
(11). 保护内容中的{{}}不被编译: v-pre
(12). 今后只要想获得表单元素的值或状态: v-model
6. 绑定样式:
(1). 需要精确修改某一个css属性,就绑定style:
a. <元素 style="固定样式" :style="{css属性:变量名, ...}"
data:{
变量名:css属性值
... : ...
}
b. <元素 style="固定样式" :style="变量名"
data:{
变量名:{
css属性名: 属性值,
... : ...
}
}
(2). 只要批量修改一个元素的多个css属性就绑定class
a. <元素 class="固定class" :class="{class名:变量名, ...}"
data:{
变量名:true或false,
... : ...
}
b. <元素 class="固定class" :class="变量名"
data:{
变量名:{
class名:true或false,
... : ...
}
}
7. 只要希望在页面加载时自动对元素执行一些初始化操作时就用自定义指令:
(1). 添加自定义指令:
Vue.directive("自定义指令名",{
inserted(domElem){
对domElem执行DOM操作
}
})
(2). 使用自定义指令:
<元素 v-自定义指令名>
8. 今后只要根据其他变量的值动态计算出一个属性值就用计算属性:
<元素>{{计算属性}}</元素>
new Vue({
el:"#app",
data:{...},
methods:{...},
computed:{
计算属性名(){
计算过程
return 计算结果
}
}
})
9. 希望将变量的原始值先加工后再显示给用户看时就用过滤器:
Vue.filter("过滤器名",function(oldVal, 自定义形参,...){
return 加工后的新值
})
<元素>{{ 变量 | 过滤器(实参值, ...) | ... }}</元素>
10. 只要在vue中发送ajax请求,就用axios
axios.defaults.baseURL="服务器端接口的公共基础地址部分"
axios.get(
"服务器端接口地址的相对路径",
{
params:{ 参数名: 参数值, ... }
}
).then(result=>{
... result.data...
})
或
axios.post(
"服务器端接口地址的相对路径",
"参数名1=参数值1&参数名2=参数值2&..."
).then(result=>{
... result.data...
})
强调: 在vue内使用axios,then中必须用箭头函数,保持then内this与外部this一致,都指向当前new Vue()对象
11. vue生命周期4个阶段 8个钩子函数
beforeCreate(){ ... }
(1). 创建(create)
created(){ ... }
beforeMount(){ ... }
(2). 挂载(mount)
mounted(){ ... 经常在这里发送ajax请求 ... }
beforeUpdate(){ ... }
(3). 更新(update)
updated(){ ... }
beforeDestroy(){ ... }
(4). 销毁(destroy)
destroyed(){ ... }
12. 只要希望重用一块独立的功能区域就用组件:
(1). 定义组件
Vue.component(`组件标签名`,{
template:`HTML内容片段`,
data(){ return { 变量 } },
//其余和new Vue()完全相同
})
(2). 在HTML中使用自定义组件
<组件标签名/>或双标记也行
(3). 原理: new Vue()扫描到自定义组件标签,
a.组件的template中的HTML内容代替页面中<组件标签>位置。
b. 并为这个小区域专门创建一个缩微版的vue类型对象。
1). 调用组件的data()函数为当前组件副本创建一个专属数据对象副本。
2). 引入组件对象中的methods等其他内容到当前组件对象副本中
13. 组件化开发:
(1). 步骤:
a. 拿到页面先划分功能区域
1). 从上到下,按功能不同划分区域
2). 按是否重用划分
b. 为每个组件创建独立的.js文件,其中包含一个组件对象及其内容
c. 将所有组件引入唯一完整的html页面中,并在<div id=”app”></div>中添加父组件标签。
(2). 运行时:
a. new Vue()扫描<div id=”app”>,发现父组件标签,创建并替换父组件
b. 父组件扫描自己内部的template内容,创建并替换子组件
(3). 三种组件:
a. 根组件: new Vue()
b. 全局组件: Vue.component(...)
c. 子组件: 3步
1). var 子组件对象名={
内容必须符合组件的要求
}
子组件对象名必须是驼峰命名
2). 父组件对象中:{
... ...
components{ 子组件对象名, ... ,... }
}
子组件对象名必须是驼峰命名
3). 父组件template中用<子组件标签名/>引入子组件内容
components会将子组件对象名的驼峰命名自动翻译为-分隔
所以, 使用子组件标签时,要用-分隔多个单词
(2). 组件间传参: 父给子
a. 父组件给:
<子组件 :自定义属性名="父组件变量">
b. 子组件取:
props:["自定义属性名"]
结果: 在子组件内,props中的"自定义属性名"与子组件自己data中的变量用法完全相同!
【后文传送门】👉 vue中如何实现SPA 单页面应用_09
如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️【青春木鱼】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!