这是我参与「第五届青训营 」伴学笔记创作活动的第4天
组件基础:
组件实例
// 创建一个Vue 应用
const app = Vue.createApp({})
// 定义一个名为 button-counter 的新全局组件
app.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
})
组件是带有名称的可复用实例,在这个例子中是 <button-counter>。我们可以把这个组件作为一个根实例中的自定义元素来使用:
<div id="components-demo">
<button-counter></button-counter>
</div>
app.mount('#components-demo')
因为组件是可复用的实例,所以它们与根实例接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。
组件的复用
你可以将组件进行任意次数的复用。
组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 component 方法全局注册的:
const app = Vue.createApp({})
app.component('my-component-name', {
// ... 选项 ...
})
全局注册的组件可以在应用中的任何组件的模板中使用。
通过Prop向子组件传递数据
Prop 是你可以在组件上注册的一些自定义 attribute。为了给博文组件传递一个标题,我们可以用 props 选项将其包含在该组件可接受的 prop 列表中:
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
一个组件可以拥有任意数量的 prop,并且在默认情况下,无论任何值都可以传递给 prop。
然而在一个典型的应用中,你可能在 data 里有一个博文的数组:
const App = {
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
}
}
const app = Vue.createApp(App)
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-posts-demo')
并想要为每篇博文渲染一个组件:
<div id="blog-posts-demo">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
</div>
如上所示,你会发现我们可以使用 v-bind 来动态传递 prop。这在你一开始不清楚要渲染的具体内容,是非常有用的。
监听子组件事件
我们在开发 <blog-post> 组件时,它的一些功能可能需要与父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。
在其父组件中,我们可以通过添加一个 postFontSize data property 来支持这个功能:
const App = {
data() {
return {
posts: [
/* ... */
],
postFontSize: 1
}
}
}
它可以在模板中用来控制所有博文的字号:
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
</div>
</div>
现在我们在每篇博文正文之前添加一个按钮来放大字号:
app.component('blog-post', {
props: ['title'],
template: `
<div class="blog-post">
<h4>{{ title }}</h4>
<button>
Enlarge text
</button>
</div>
`
})
问题是这个按钮不会做任何事:
<button>
Enlarge text
</button>
当点击这个按钮时,我们需要告诉父级组件放大所有博文的文本。幸好组件实例提供了一个自定义事件的系统来解决这个问题。父级组件可以像处理原生 DOM 事件一样通过 v-on 或 @ 监听子组件实例的任意事件:
<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>
同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件:
<button @click="$emit('enlargeText')">
Enlarge text
</button>
多亏了 @enlarge-text="postFontSize += 0.1" 监听器,父级组件能够接收事件并更新 postFontSize 的值。
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 <blog-post> 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
1 2 3
然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
1
或者,如果这个事件处理函数是一个方法:
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
1
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在组件上使用v-model
自定义事件也可以用于创建支持 v-model 的自定义输入组件。记住:
<input v-model="searchText" />
1
等价于:
<input :value="searchText" @input="searchText = $event.target.value" />
1
当用在组件上时,v-model 则会这样:
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的 <input> 必须:
- 将其
valueattribute 绑定到一个名叫modelValue的 prop 上 - 在其
input事件被触发时,将新的值通过自定义的update:modelValue事件抛出
写成代码之后是这样的:
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
1 2 3 4 5 6 7 8 9 10
现在 v-model 就可以在这个组件上完美地工作起来了:
<custom-input v-model="searchText"></custom-input>
1
在该组件中实现 v-model 的另一种方法是使用 computed property 的功能来定义 getter 和 setter。get 方法应返回 modelValue property,set 方法应该触发相应的事件。
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})
通过插槽分发内容(slot)
这可以通过使用 Vue 的自定义 <slot> 元素来实现:
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
如你所见,我们使用 <slot> 作为我们想要插入内容的占位符——就这么简单!
如果没有slot,子级无法显示,父级会优先显示。
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['tab-button', { active: currentTab === tab }]"
v-on:click="currentTab = tab"
>
{{ tab }}
</button>
<component v-bind:is="currentTabComponent" class="tab"></component>
</div>
.demo {
font-family: sans-serif;
border: 1px solid #eee;
border-radius: 2px;
padding: 20px 30px;
margin-top: 1em;
margin-bottom: 40px;
user-select: none;
overflow-x: auto;
}
.tab-button {
padding: 6px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid #ccc;
cursor: pointer;
background: #f0f0f0;
margin-bottom: -1px;
margin-right: -1px;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-button.active {
background: #e0e0e0;
}
.demo-tab {
border: 1px solid #ccc;
padding: 10px;
}
const app = Vue.createApp({
data() {
return {
currentTab: 'Home',
tabs: ['Home', 'Posts', 'Archive']
}
},
computed: {
currentTabComponent() {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
app.component('tab-home', {
template: `<div class="demo-tab">Home component</div>`
})
app.component('tab-posts', {
template: `<div class="demo-tab">Posts component</div>`
})
app.component('tab-archive', {
template: `<div class="demo-tab">Archive component</div>`
})
app.mount('#dynamic-component-demo')
上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component :is="currentTabComponent"></component>
在上述示例中,currentTabComponent 可以包括:
- 已注册组件的名字,或
- 一个组件选项对象
查看该沙盒以调试绑定了组件注册名的完整代码,或在另一个沙盒中查看绑定了组件选项对象的示例。
你也可以使用 is attribute 来创建常规的 HTML 元素。
组件注册
组件注册:
<template id = "cpn">
<div>
</div>
</template>
const app = new Vue({
el="#app",
data:{
message:""
},
conmponents:{
cpn:{
template:"cpn"//绑定id
}
}
})
<div id="app">
<cpn></cpn>
</div>
Props
自定义事件
插槽
插槽的目的是让组件更加具有扩展性。
插槽内容
<todo-button></todo-button>
<slot></slot>的作用:
如果<todo-button中的template中没有包含一个该元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
渲染作用域
当你想在一个插槽中使用数据时,例如:
<todo-button>
Delete a {{ item.name }}
</todo-button>
该插槽可以访问与模板其余部分相同的实例 property (即相同的“作用域”)。
插槽不能访问 <todo-button> 的作用域。例如,尝试访问 action 将不起作用:
<todo-button action="delete">
Clicking here will {{ action }} an item
<!--
`action` 将会是 undefined,因为这个内容是
传递到 <todo-button>,
而不是在 <todo-button> 中定义的。
-->
</todo-button>
请记住这条规则:
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
导航栏的使用便是在一个nav-bar中添加不同的插槽,这样以便于变成不同的导航栏。以jd为例。
如何封装这类组件
1.抽取共性,保留不同。
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
- 预留了插槽,就可以让使用者根据自己的需求,觉得插槽中插入什么内容。
- 是搜索框,还是文字,还是菜单。由调用者自己来决定。
栗子:
//父组件
<div id="app">
<cpn><button>dadadad
</button></cpn>
<cpn><button>lalalala
</button></cpn>
<cpn><button>hahahaha
</button></cpn>
<cpn><button>adwaadwad
</button></cpn>//这会使button出现不同的名称
</div>//父组件有东西时会覆盖子组件中的插槽内容。
//子组件
<template>
<div>
<h1>
我是组件
</h1>
<p>
我是组件2
</p>
<slot>《这里面写东西会显示为默认值,即如果多次运用时不需要在父组件多次修改,直接用cpn即可全部呈现》</slot>//给组件预留位置
</div>
</template>
一个插槽会替换所有东西。
插槽总结:
基本使用。
默认值。meaning
如果有多槽,同时放入组件中进行替换时,一起作为替换元素。
简介
JavaScript框架
简化Dom操作
响应式数据驱动
第一个Vue程序
- 导入开发版本的Vue.js
- 创建Vue实例对象,设置el属性和data属性
- 使用简洁的模板语法把数据渲染到页面上
1.el挂载点
- Vue会管理el选项命中的元素以及内部的后代元素;
- 可以使用其他的选择器,建议使用id选择器;
- 使用双标签,不要使用body和HTML;
<p id="app" class="app">
{{message }}
<span id="app">
{{message}}
</span>
</p>
<script>
var app = new new Vue({
// el:"#app",
// el:".app",
el:"p",
data:{
message:'黑马'
}
})
</script>
2.data数据对象
- Vue用到的数据写在data中;
- data中可以写复杂类型的数据;
- 选赞复杂类型的数据时 ,遵守js的语法;(对象的点语法,数据的索引语法);
<div id="app">
{{message}}
<h2>{{school.name}} {{school.mobile}}</h2>
<ul>
<li>
{{campus[0]}}
</li>
<li>
{{campus[1]}}
</li>
</ul>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"hai",
school:{
name:"黑马程序员",
mobile:"123456",
},
campus:["北京","上海"]
}
})
</script>
本地应用
v-text 设置内容
<div id="app">
<h2 v-text="message+'!'"></h2>
<h2>深圳{{message+"!"}}</h2>
<h2>haha{{info+"@"}}</h2>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"黑马程序员",
info:"前端移动",
}
})
</script>
- 直接v-text 为data里数据的名称;
- 使用大括号,内部为数据的名称;
- 利用+进行字符串的拼接;
v-html 标签的innerHtml
如果只是设置文本效果与v-text一样,如果设置的内容中时HTML结构就会被解析为标签;
<div id="app">
<p v-html="content"></p>
<p v-html="contents"></p>
</div>
<script>
var app =new Vue({
el:"#app",
data:{
content:"黑马程序员",
contents:"<a href='http://www.itheima.com'>黑马程序员</a>"
}
})
</script>
v-on 为元素绑定事件
- 事件名不需要写on;
- 指令可以简写为@;
- 绑定的方法定义在methods属性中;
- 方法内部通过this关键字访问定义在data中俄数据;
<div id="app">
<!-- 点击 -->
<input type="button" value="v-on" v-on:click="doit">
<!-- 简写 -->
<input type="button" value="v-on简写" @click="doit">
<!-- 双击 -->
<input type="button" value="双击事件" @dblclick="doit">
<h2 @click="change">{{food}}</h2>
</div>
<script>
var app = new Vue({
el:"#app",
methods:{
doit:function() {
alert("做IT");
},
change:function() {
this.food+="好好吃"
}
},
data:{
food:"西兰花",
}
})
</script>
v-on 自定义参数
- 传到自定义参数,事件修饰符;
- 事件绑定的方法写成函数调用的形式,可以传入自定义参数;
- 定义方法需要定义形参来接收传入的实参;
- 事件后面(.事件修饰符)可以触发;
<div id="app">
<button @click="doit(666,'啦啦啦')">点击</button>
<input type="text" @keyup.enter="sayhi">
</div>
<script>
var app = new Vue({
el:"#app",
data:{
},
methods:{
doit:function(p1,p2) {
console.log(p1)
console.log(p2)
},
sayhi:function(){
alert("haha")
}
}
})
</script>
v-show 元素的显示和隐藏
- 根据表达式的真假,切换元素的显示和隐藏;
- 修改元素的display实现隐藏;
- 数据也可以做切换条件;
- 写在最后的都会被解析为布尔值;
<div id="app">
<div v-show="age>=18" style="width: 500px; height: 250px; background-color: green;"></div>
<div>{{age}}</div>
<input type="button" value="年龄" @click="Addage">
<div v-show="isShow" style="width: 500px; height: 250px; background-color: red;"></div>
<button value="切换显示状态" @click="change" >切换显示状态</button>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
isShow:false,
age:17,
},
methods: {
change:function(){
this.isShow=!this.isShow;//取反,进行显示和隐藏的切换
},
Addage:function(){
this.age++;
console.log(this.age)
}
},
})
</script>
v-if 操作dom元素
- 根据表达式的真假切换元素的显示状态,
- 本质:操作dom元素;
- true在dom树中添加;false在dom树 中删除;
<div id="app">
<input type="button" value="切换显示" @click="TisShow">
<p v-if="isShow">黑马程序员</p>
<input type="button" value="切换显示" @click="TisShow2">
<p v-show="isShow2">黑马程序员</p>
<h2 v-if="temperature>=35">热死了</h2>
<input type="button" value="温度上升" @click="up">
</div>
<script>
var app = new Vue({
el:"#app",
data:{
isShow:false,
isShow2:false,
temperature:33
},
methods: {
TisShow:function() {
this.isShow=!this.isShow;
},
TisShow2:function() {
this.isShow2=!this.isShow2;
},
up:function() {
this.temperature++;
}
}
})
</script>
v-bind 绑定属性
为元素绑定属性(title,src,class);
语法:v-bind:属性名=表达式;简写(:属性名);
<div id="app">
<!-- 图片地址 -->
<img v-bind:src="imgSrc">
<br>
<!-- 简写 -->
<!-- 鼠标悬停 -->
<img :src="imgSrc" :title="imgTitle+'!!!'">
<br>
<!-- 设置class -->
<img :src="imgSrc" v-bind:class="{active:imgActive}" @click="Active">
</div>
<script>
var app = new Vue({
el:"#app",
data:{
imgSrc:"images/fly.png",
imgTitle:"云翼",
imgActive:false
},
methods: {
Active:function() {
this.imgActive=!this.imgActive;
}
}
})
</script>
v-for 生成列表结构
语法:
v-for="名称 in arr"
<div id="app">
<ul>
<!-- 根据数组长度创建li,再利用item写入数组的内容,item是名称可以更改-->
<li v-for="(item,index) in arr">{{index+1}}哈哈{{item}}</li>
</ul>
<h2 v-for="it in vegetables" v-bind:title="it.name">{{it.name}}</h2>
<input type="button"value="添加" @click="add">
<input type="button"value="移除" @click="remove">
</div>
<script>
var app= new Vue({
el:"#app",
data:{
arr:["北京","上海","广州","深圳"],
vegetables:[
{name:"西兰花"},
{name:"苦瓜"},
],
},
methods: {
add:function() {
this.vegetables.push({name:"番茄"});
},
remove:function() {
this.vegetables.shift();
},
}
})
</script>
v-model获取和设置表单元素的值
绑定的数据会和表单的值相关联;
绑定的数据与表单元素的值双向绑定;
<div id="app">
<input type="button" value="修改message" @click="setm">
<input type="text" v-model="message" @keyup.enter="getm">
<!-- 同步更新 -->
<h2>{{message}}</h2>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"黑马sxdgfv"
},
methods: {
getm:function() {
alert(this.message)
},
setm:function() {
this.message="hahha";
}
}
})
</script>