属性
自定义属性props
props定义了该组件可以配置的属性,组件的核心功能都是由它决定的。父组件通过:属性名的方法将属性或者函数传入到子组件中,子组件通过props来接收父组件传过来的属性或者方法。
inheritAttrs
默认情况下父作用域的不被认作props的特性绑定将会‘回退’且作为普通的HTML特性应用在子组件的根元素上,可通过设置inheritAttrs为false,这些默认行为将会被去掉,简单来说,就是没有声明的属性,默认自动挂载到自组件的根元素上,可通过inheritAttrs去除。注意:这个选项不会影响到class和style绑定。
- 当设置inheritAttrs: true(默认)时,子组件的根元素中会渲染出父组件传递过来的属性。
- 当设置inheritAttrs: false时,子组件的根元素中不会渲染出父组件传递过来的属性。
- 不管inheritAttrs为true或者false,子组件中都能通过$attrs属性获取到父组件中传递过来的属性。
栗子如下:
//父组件
<template>
<div class="parent">
<Son1 aaa="1111"></Son1>
</div>
</template>
<script>
import Son1 from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
Son1
}
}
</script>
//子组件
<template>
<div class="child">子组件</div>
</template>
<script>
export default {
inheritAttrs: true,
mounted() {
console.log('this.$attrs', this.$attrs)
}
}
</script>
data与props的区别
相同点:
两者都可以存放各种类型的数据,当行为操作改变时,所有行为操作所用到的数据和模板所渲染的数据痛失都会发生同步变化
不同点:
- data被称为动态数据,在各自的实例中,在任何情况下,我们都可以随意改变它的数据类型和数据结构,不会被任何环境所影响。
- props被称为静态数据,在各自的实例中,一旦在初始化被定义好类型时,基于Vue是单向数据流,在数据传递时始终不能改变它的数据类型,而且不允许在子组件中直接操作传递过来的props数据,而是需要通过别的手段,改变传递源中的数据。
单向数据流
这个概念出现在组件通信。props的数据都是通过父组件或者更高层级的组件数据或者字面量的方式进行传递的,不允许直接操作改变各自实例中的props数据,而是需要通过别的手段,改变传递源中的数据。
如何修改传递过来的prop
- 过渡到data选项中:在子组件中拷贝一份prop,data是可以修改的
- 利用计算属性computed
但是上面两种方法只是单纯的修改子组件中的数据,却无法同步到父组件中去。在某些情况下,我们需要对prop进行双向绑定,就需要用到一下两种方法: - 使用.sync
javascript :show.sync='show' :msg.sync='msg' :arr='arr'
由上述所示,父组件通过.sync传过来的show、msg,子组件接收时进行了双向绑定,而arr没有使用.sync但当arr发生改变时,因为数组传过来的是引用,父组件中的arr也会同时发生改变。
不过.sync也存在限制,
1. 不能与表达式一块使用 ,如v-bind:title.sync="doc.title+’!’“是无效的
2. 不能用在字面量对象上,如v-bind.sync=”{title:doc.title}"是无法正常工作的
- 将父组件中的数据包装成对象传递给子组件
在JavaScript中对象和数组是通过引用传入的,所以对于一个数组或对象类型的prop来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
向子组件传递数据时加:(冒号)和不加v-bind的区别
只有在传递字符串常量时,不采用v-bind形式
- 传入String类型
传入的值title为一个常量(静态prop)时,不加v-bind(或者:) <blog-post title="My journey with Vue"></blog-post> 传入的值title为一个变量(动态prop)时,加v-bind(或者:) <blog-post v-bind:title="titleValue"></blog-post> //下面的两种写法有本质的区别 <counter conut="0"></counter> //父组件向子组件传值,如果不加:冒号,传过去的不是数值,而是一个字符串 <counter :conut="0"></counter> /* 父组件向子组件传值,加上:冒号,传过去的就是一个数值,不是字符串,因为加了:冒号以后引号""里面的内容 就是一个js表达式 */
事件
事件驱动与数据驱动
原生JavaScript的事件驱动通常的流程是这样的:
- 先通过特定的选择器查找到需要操作的节点 → 给节点添加相应的事件监听
- 然后用户执行某个事件(点击,输入,后退等等)→调用JavaScript来修改节点
事件驱动的开发成本和效率低,因此出现了数据驱动。
Vue的一个核心思想就是数据驱动,所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改不会直接操作DOM节点,而是通过数据绑定的关系修改数据,具体流程如下:
- 用户执行某个操作→ 反馈到VM处理(可以导致Model变动)→ VM层改变,通过绑定关系直接更新页面对应位置的数据
可以简单的理解为:数据驱动不是操作节点,而是通过虚拟的抽象数据层来直接更新页面。主要是因为这一点,数据驱动框架才得以较快的运行速度(因为不用去折腾节点),并且可以用到大型项目
修饰符事件
Vue事件分为普通事件和修饰符事件。
首先思考一个问题:怎么给自定义组件custom绑定一个原生的click事件?
如果回答是 <custom @click='xxx'>
,那就错了。这里的@click是自定义事件click,不是原生的click事件。绑定原生的click是这样的:
<custom @click.native='xxx'>组件内容</custom>
实际的开发过程中离不开事件修饰符,常见的事件修饰符有:
表单修饰符
需要添加到v-model后使用, 如v-model.number
- .lazy, 默认情况下,v-model在每次input事件触发后将输入框的值与数据同步。添加lazy修饰符,从而转为在change事件_之后_进行同步.
<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg">
- .number
将用户的输入值转为数值类型, 先输入数字后输入其他时, 会截取前面的数字
如果先输入数字,它会限制的是只能输入数字
如果先输入的是字符串,那么相当于没有加.number - .trim, 同trim方法, 去掉值前后的空格
事件修饰符
添加在事件后, 如@click.native
- .stop:阻止单击事件继续传播,即阻止事件冒泡
- .prevent:阻止标签默认行为
- .capture:使用事件捕获模式,即元素自身触发的事件先在此处处理, 然后才交由内部元素进行处理
- .self:只当在 event.target 是当前元素自身时触发处理函数
- .once:点击事件将只会触发一次
- .passive:告诉浏览器不需要检查是否有preventDefault动作了, 所有都按默认执行, 这里可以提升移动端性能
- .native 用于自定义组件上, 使其可以执行内置的事件, 如onclick
- .sync
一个语法糖, 因为子组件是无法直接修改父组件props传递来的数据的(单向数据流), 修改的话一般是父组件提供一个修改的方法, 子组件触发该方法并传递数据
当父组件向子组件传递时带上这个修饰符, 子组件触发一个update: <name, 即async修饰的名>, 提交新的数据, 父组件就正常修改了
按键修饰符
添加在@keyup后面,即@keup.enter这种
- .enter
- .tab
- .delete (捕获“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
- .keyCode:已弃用,但可以使用属性值13
<input v-on:keyup.13="submit">
插槽
插槽可以分为普通插槽和作用域插槽,其实两者类似,区别在于作用域插槽可以接收子组件传递过来的数据。
普通插槽
//父组件
<template>
<div>
<child>
<p>我是从父组件过来的</p>
</child>
</div>
</template>
<script>
import childfrom './components/child.vue'
export default {
name: 'App',
components: {child},
}
</script>
//子组件
<template>
<div>
<p>子组件内容</p>
<slot></slot>
</div>
</template>
具名插槽
//父组件
<template>
<div>
<child>
/*
slot="down"
相当于
v-slot:down
#down
*/
<template slot="down">
<p>我是第三</p>
</template>
<p>我是从父组件过来的第二</p>
<template slot="up">
<p>我是第一</p>
</template>
</child>
</div>
</template>
<script>
import childfrom './components/child.vue'
export default {
name: 'App',
components: {child},
}
</script>
//子组件
<template>
<div>
<p>子组件内容</p>
<slot name="up"></slot>
<slot></slot>
<slot name="down"></slot>
</div>
</template>
作用域插槽
// 父组件
<template>
<div class="item">
<input type="text" v-model="todo"><button @click="addItem">添加</button>
<ul>
<TodoItem v-for="(todo,index) in todos" :key="index">
<!--checked接收子组件传递过来的对象,对象中包含checked属性-->
<template slot="item" slot-scope="scope">
<span :style="{color: scope.checked ? 'yellow':'blue'}">
{{todo}}
</span>
</template>
<!--相当于-->
<template v-slot:item="checked">
<span :style="{color: checked.checked ? 'yellow':'blue'}">
{{todo}}
</span>
</template>
</TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from "./components/HelloWorld.vue";
export default{
data(){
return {
todo:'',
todos:[]
}
},
components:{TodoItem},
methods:{
addItem(){
this.todos.push(this.todo);
this.todo="";
}
}
}
</script>
<style scoped>
.item{
margin: 20px auto 20px 20px;
}
</style>
// 子组件
<template>
<div>
<li class="todo">
<input type="checkbox" v-model="checked">
<!--通过slot传递给父组件-->
<slot name="item" :checked="checked">
<!--后备内容-->
{{checked}}
</slot>
</li>
</div>
</template>
<script>
export default {
name: "TodoItem",
data(){
return {checked:false}
}
}
</script>
如果大家有什么意见或建议希望大家在评论区多多交流,谢谢😊