vue组件三要素
- props参数
- slot定制插槽
- event自定义事件
基本用法
在使用 vue-cli 创建的项目中,组件的创建非常方便,只需要新建一个 .vue 文件,然后在 template 中写好 HTML 代码,一个简单的组件就完成了 一个完整的组件,除了 template 以外,还有 script和 style
<template>
<div class="headComponent">
子组件{{{myData}}
</div>
</template>
<script>
export default {
props:['data','type'],
inheritAttrs: false,
data(){
return{
myData:'',
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
然后在其他文件的 js 里面引入并注册,就能直接使用这个组件了
import list from '../components/headComponent.vue'
components: {list },
父对子传参,就需要用到 props,通常的 props 是这样的:
props:['data','type']
但是通用组件的的应用场景比较复杂,对 props 传递的参数应该添加一些验证规则,常用格式如下:
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
对于通过 props 传入的参数,不建议对其进行操作,因为会同时修改父组件里面的数据 // vue2.5已经针对 props 做出优化,这个问题已经不存在了 如果一定需要有这样的操作,可以这么写:
let copyData = JSON.parse(JSON.stringify(this.data))
为什么不直接写 let myData = this.data 呢? 因为直接赋值,对于对象和数组而言只是浅拷贝,指向的是同一个内存地址,其中一个改变另一个也会改变。而通过 JSON颠倒转换之后,实现了深拷贝,则可以互不影响。
在通用组件中,通常会需要有各种事件,比如复选框的 change 事件,或者组件中某个按钮的 click 事件,有时子组件需要触发一个事件,并传递给父组件
// 子组件方法:触发父组件方法,并传递参数data到父组件
handleSubmit(data){
this.$emit('submitToParent', data)
}
// 父组件调用子组件
<child-component @submitToParent="parentSubmit"></child-component>
... ...
// 父组件中被触发的方法,接受到子组件传来的参数
parentSubmit(data){
// 父组件的逻辑处理
}
父组件中的逻辑要放在父组件处理,子组件基于父组件的数据做的逻辑放在子组件中处理; 这样既降低了耦合性,也保证了各自的数据不被污染。
一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决
上面是一个通用组件,在某些场景中,右侧的按钮是 “处理” 和 “委托”。在另外的场景中,按钮需要换成 “查看” 或者 “删除” 在封装组件的时候,就不用写按钮,只需要在合适的位置留一个 slot,将按钮的位置留出来,然后在父组件写入按钮
子组件
<div class="child-btn">
<!-- 具名插槽 -->
<slot name="button"></slot>
<!-- 匿名插槽(每个组件只能有一个) -->
<slot><slot>
</div>
父组件
<child>
<!-- 对应子组件中button的插槽 -->
<button slot="button">slot按钮</button>
</child>
开发通用组件的时候,只要不是独立性很高的组件,建议都留一个 slot,即使还没想好用来干什么。
再来个例子:
开发过程中,常常需要在子组件内添加新的内容,这时候可以在子组件内部留一个或者多个插口
然后在调用这个子组件的时候加入内容
添加的内容就会分发到对应的 slot 中
slot 中还可以作为一个作用域,在子组件中定义变量,然后在父组件中自定义渲染的方式
子组件:
父组件:
这个示例中,首先在子组件中添加 slot,并在子组件中定义了数组变量 navs
然后在父组件中以作用域 template 添加内容,其中 scope 是固有属性,它的值对应一个临时变量 props
而 props 将接收从父组件传递给子组件的参数 navs
子组件向子组件传递数据
Vue 没有直接子对子传参的方法,建议将需要传递数据的子组件,都合并为一个组件。如果一定需要子对子传参,可以先从传到父组件,再传到子组件。
父子组件之间是通过 props 和 自定义事件来传参,非父子组件通常会采用 Vuex 传参
Vuex设计初衷是用来管理组件状态,也可以用于复杂组件的参数传递
但是 Vuex 的虽然可以用来传参,但并不推荐;因为 Vuex 类似于一个全局变量,会一直占用内存
在写入数据庞大的 state 的时候,就会产生内存泄露(人是活的,不能因为事物有弊端就不使用,合理利用即可)。
Tips:
当页面刷新的时候,Vuex 会初始化,同时也会 丢失编辑过的数据
如果刷新页面时需要保留数据,可以通过 url 或者 sessionstorage 保存 id,然后 ajax 请求数据
在编写组件的时候,可以在 style 标签中添加 scoped,让标签中的样式只对当前组件生效
但是一味的使用 scoped,肯定会产生大量的重复代码
所以在开发的时候,应该避免在组件中写样式
当全局样式写好之后,再针对每个组件,通过 scoped 属性添加组件样式
Vue 还可以将多个子组件,都挂载在同一个位置,通过变量来切换组件,实现 tab 菜单这样的效果
这样的功能可以通过路由 vue-router 实现,但路由更适合较大的组件,而且 url 会有相应的改变 Vue 自身保留的 元素,可以将组件动态绑定到 is 特性上,从而很方便的实现动态组件切换
上例中,当 tabView 的值改变,component 就会渲染对应的组件,和路由的效果十分类似,但是地址栏并没有发生改变,但这样一来,每次切换组件都会重新渲染,无法保留组件上的数据。这时可以使用 ** keep-alive ** 将组件保留在内存中,避免重新渲染
当组件拥有 name 属性的时候,就可以在它的模板内递归的调用自己,这在开发树形组件的时候十分有效
上面是一个子组件,定义了 name 为 simple03,然后在模板中调用自身,结合 v-for 实现递归 为了防止出现死循环,在调用自身的时候,加入了 v-if 作为判定条件 父组件中调用的时候,需要通过 props 传入一个 tree:
子组件改变父组件的数据
在vue2.0之后的版本中,不允许子组件直接改变父组件的数据,在1.0的版本中可以这样操作的,但是往往项目需求需要改变父组件的数据,那我们如何变通呢?
当我们把父元素的数据给子组件时,要传一个非基础类型,即传递对象or数组,子组件通过访问对象中的属性操作数据,因为对象和数组是传引用,所以在子组件中修改的时候,父组件也会同步改变,如下:
// 父组件要props传递给子组件的数据
myData:{
info:'父组件信息'
}
// 子组件
<template id="tpl">
<div>
<button @click="change">change</button>
<p>{{data.info}}</p>
</div>
</template>
... 省略部分无关代码 ...
props:['data'],
methods:{
change(){
this.data.info = 'change info'
}
}
当子组件点击change按钮改变数据的时候,父组件也会同步改变
如果不想影响父组件的值,我们也可以用文章开头讲的JSON颠倒转换的方式,在mounted方法中进行深拷贝,在data中初始化数据时用另一个变量作为承载,这样才承载变量改变,就不会影响父组件该变量的值了。。