思维导图参考:
https://www.processon.com/view/link/5d430271e4b01ed2c6aa4171#map
第一个词:
组件化:
组件化到底是什么?
重点词:抽象、独立、可复用
官方回答:vue组件系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护性,便于多人协同开发
第二个词:
组件通信常用方式
常用
props
:需要在子组件里定义一个props即参数,一般是[’’,’’]数组,但如果你想对参数进行验证,他就是一个对象,{canshuA:{type:,default:}},在父组件中调用该子组件时进行传参,参数名就是定义的props名
$emit/$on
:
var Event = new Vue();
相当于又new了一个vue实例,Event中含有vue的全部方法;
可在main.js里全局注册
Event.$emit('msg',this.msg);
发送数据,第一个参数是发送数据的名称,接收时还用这个名字接收,
第二个参数是这个数据现在的位置,即参数本身;
Event.$on('msg',function(msg){
接收数据,第一个参数是数据的名字,与发送时的名字对应,
第二个参数是一个方法,要对数据的操作
/这里是对数据的操作
})
event bus
:
//vue原型链挂载总线(main.js)
Vue.prototype.bus = new Vue();
//子组件发送数据
this.bus.$emit("change",data);
//子组件接收数据
this.bus.$on("change",function(data){
})
vuex
:
Vuex 主要有四部分:
state:包含了store中存储的各个状态。
Vuex 使用 state 来存储应用中需要共享的状态。为了能让 Vue 组件在 state更改后也随着更改,需要基于state 创建计算属性。
return this.$store.state.count // count 为某个状态
getter: 类似于 Vue 中的计算属性,根据其他 getter 或 state 计算返回值。
类似于 Vue 中的 计算属性(可以认为是 store 的计算属性),getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
Getter 会暴露为 store.getters 对象,可以以属性的形式访问这些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mutation: 一组方法,是改变store中状态的执行者,只能是同步操作。
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。也就是说,前面两个都是状态值本身,mutations才是改变状态的执行者。
注意:mutations只能是同步地更改状态。
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
store.commit('increment')
action: 一组方法,其中可以包含异步操作。
想要异步地更改状态,就需要使用action。action并不直接改变state,而是发起mutation
参考自博客,本身还是没有太了解vuex,会在日后的学习中继续补充。
不推荐使用
$parent:用法与event bus 一致 ,this.$parent.$on,this.$parent.$emit 就是一个相同的中间人就可以了 即都是$parent
$children:可以获得全部孩子,访问到孩子 然后去调用孩子的方法(只会有自定义组件)$children不能保证⼦元素顺序,只能根据同步组件排序异步组件不行
$root
不推荐原因:耦合性(模块间关联程度的度量)高–>复用性低
$refs:给组件写一个类似ref="a",就可以通过this.$refs.a访问到该组件,任意组件都可
provide/inject隔层传参:即跨很多代:index.vue里 provide() { return { bar: 'bar' } }这里面return的对象不管隔多少代都可以拿出来,如:在children2中inject: { bar1: { from: 'bar',这里是防止命名冲突,所以采用取别名的形式,名字叫bar1但其实来源于bar default: 'barrrrrrrr' } },注入一下,就可以用了<p>{{bar1}}</p>
非prop特性(props中未声明)
$attrs:就比如<Child2 ref="child2" msg="some message">,使用了msg属性,但在Child2 组件内并没有定义msg的props属性就可以在Child2 组件内利用<p>{{$attrs.msg}}</p>使用了
$attrs:重点使用 :爷孙通信,例如:index.vue是爷爷,有一个parent.vue父组件,children2.vue是孙组件,parent里引入children2,index里引入parent,<Parent msg="msg from grandpa"> 从爷爷辈传来的信息在老爹辈原封不动的转给孙子,即children2里如果想访问爷爷里msg属性,就需要在parent里进行属性展开,<Child2 v-bind="$attrs" > v-bind就会把这个对象通过键值对的方式把它展开了
$listeners:事件转发,隔代转发,与attrs同理,index里<Parent msg="msg from grandpa" @foo="onFoo"> parent里<Child2 v-bind="$attrs" v-on="$listeners"> 在children2里触发一下this.$emit('foo'),就会调到index的onFoo方法
第三个词:
插槽
说到插槽 就要想到 内容分发,即实际上内容是在子组件里面,但其实我们是在父组件里面传递内容,在子组件显示
匿名插槽:
// comp1 <div> <slot></slot> </div>
// parent <comp>hello</comp>
具名插槽
将内容分发到⼦组件指定位置
// comp2 <div> <slot></slot> <slot name="content"></slot> </div>
// parent <Comp2> <!-- 默认插槽⽤default做参数 --> <template v-slot:default>具名插槽</template> <!-- 具名插槽⽤插槽名做参数 --> <template v-slot:content>内容...</template> </Comp2>
作⽤域插槽
分发内容要⽤到⼦组件中的数据
// comp3 <div> <slot :foo="foo"></slot> </div>
// parent <Comp3> <!-- 把v-slot的值指定为作⽤域上下⽂对象 --> <template v-slot:default="slotProps"> 来⾃⼦组件数据:{{slotProps.foo}} </template> </Comp3>
组件化实战 仿Element-ui的表单案例
<!-- KInput -->
<template>
<div>
<!-- 管理数据,实现双向绑定 -->
<!-- :value, @input -->
<input :type="type" :value="value" @input="onInput"
v-bind="$attrs">
</div>
</template>
<script>
export default {
inheritAttrs: false,//根元素上就不会显示了,如果你不希望组件的根元素继承特性,你可以在组件的选项中设置
props: {
value: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
}
},
methods: {
// 把值派发出去,实现双绑
onInput(e) {
this.$emit('input', e.target.value)
// 触发校验 Input通知校验 $parent指FormItem
// 这里使用$parent会产生耦合,如果KInput的老爹不是KFormItem就无效了
this.$parent.$emit('validate')
}
},
}
</script>
<!-- KFormItem -->
<template>
<div>
<!-- 显示label -->
<label v-if="label">{{label}}</label>
<!-- 显示内部表单元素 -->
<!-- 占一个坑位,有内容就显示,例如表单项 -->
<slot></slot>
<!-- 错误提示信息 -->
<p v-if="error" class="error">{{error}}</p>
<!-- <p>{{form.rules[prop]}}</p> -->
</div>
</template>
<script>
import Schema from "async-validator";
export default {
inject: ["form"],//注⼊
props: {
label: {
type: String,
default: ""
},
prop: {
type: String,
default: ""
}
},
data() {
return {
error: 'some error'
}
},
mounted() {// 监听校验事件
this.$on("validate", () => {
this.validate();
});
},
methods: {
validate() {
// 当前表单项校验
// element使用的是async-validator需要安装 npm i async-validator
// 获取校验规则和当前数据 // 获取对应FormItem校验规则
const rules = this.form.rules[this.prop]
// 获取校验值
const value = this.form.model[this.prop]
// 创建校验器 // 校验描述对象
const schema = new Schema({[this.prop]: rules})//用这个规则去校验这个值
// 返回promise,全局可以统一处理 // 返回Promise,没有触发catch就说明验证通过
return schema.validate({[this.prop]: value}, errors => {
// errors存在则校验失败
if (errors) {
this.error = errors[0].message
} else {
// 校验通过
this.error = ''
}
})
}
}
};
</script>
<style scoped>
.error {
color: red;
}
</style>
<!-- KForm -->
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
//隔代传参,传好几代
provide(){
return {
form:this// 将组件实例作为提供者,⼦代组件可⽅便获取
}
},
props: {
model: {
type: Object,
required:true
},
rules:Object
},
methods:{
validate(cb){
// 调⽤所有含有prop属性的⼦组件的validate⽅法并得到Promise数组
const tasks=this.$children.filter(item=>item.prop).map(item=>item.validate())
// 所有任务必须全部成功才算校验通过,任⼀失败则校验失败
Promise.all(tasks).then(()=>cb(true)).catch(()=>cb(false))
}
}
}
</script>
<!-- index -->
<template>
<div>
<KForm :model="model" :rules="rules" ref="loginForm">
<KFormItem label="用户名" prop="username">
<KInput v-model="model.username" placeholder="请输入用户名"></KInput>
</KFormItem>
<KFormItem label="密码" prop="password">
<KInput v-model="model.password" placeholder="请输入密码"></KInput>
</KFormItem>
<KFormItem>
<button @click="login">登录</button>
</KFormItem>
</KForm>
</div>
</template>
<script>
import KInput from './KInput.vue'
import KFormItem from './KFormItem.vue'
import KForm from './KForm.vue'
export default {
components:{
KInput,
KFormItem,
KForm
},
data(){
return {
model:{
username:'tom',
password:''
},
rules:{
username:[{required:true,message:"yhm不能为空"}],
password:[{required:true,message:"mm不能为空"}]
}
}
},
methods:{
login(){
this.$refs.loginForm.validate((isValidate)=>{
if(isValidate){
console.log('login')
}else{
alert('校验失败')
}
})
}
}
}
</script>
<style scoped>
</style>
为什么要用自定义组件?----> 由此引入 vue.extend及$mount
的使用
因为当前项目使用的ui框架不能满足需求,由此产生自定义组件的念头。自定义组件写完后,又想能够自定义创建组件实例。这样一步一步,接触到了vue.extend。
vue.extend(简洁,重点)
1.vue.extend的作用(官方文档API)
vue.extend(option) :
- option的参数是一个包含组件选项的对象
- 返回一个Vue构造器
import AComponent from path; // 自定义的组件//此组件已存在,并已创建
// 第一种,option为自定义
// 此时baseExtend为构造函数
let baseExtend = vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
});
let baseInstance = new baseExtend(); // 创建组件实例
baseInstance.$mount('#mount-point');// 将实例挂载到id位mount-point的元素上
// 第二种,option为导入的组件对象
// 此时baseExtendTwo为AComponent组件的构造函数
let baseExtendTwo = vue.extend(AComponent);
let baseInstanceTwo = new baseExtendTwo(); // 创建AComponent组件实例
baseInstanceTwo.$mount('#mount-point');// 将实例挂载到id位mount-point的元素上
动态生成一个自定义组件与实例不同。官方文档中是事先定义一个id为“mount-point”的元素,然后挂载上去。没有举例如何动态生成一个元素并挂载。所以下一步,是查看如果将组件实例挂载到动态生成的元素上。
2.查看了$mount
的用法vm.$mount( [elementOrSelector] )
返回值:vm - 实例自身
如果Vue实例在实例化时没有收到el选项,则它处于“未挂载"状态,没有关联的DOM元素。可以使用vm.$mount()手动地挂载一个未挂载的实例。如果没有提供element0rSelector参数,模板将被渲染为文档之外的的元素,并且你必须使用原生DOM API把它插入文档中。
//官方API使用介绍
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')
// 同上
new MyComponent({ el: '#app' })
// 或者,在文档之外渲染并且随后挂载 //本次需要使用
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
$mount()中不传参数,将实例渲染为文档之外的元素。渲染为文档之外的元素就相当于createElement了,但是还需要将元素插入到文档中,因此可做以下需求:
import AComponent from path; // 自定义的组件
// 第一种,option为自定义
// 此时baseExtend为构造函数
let baseExtend = vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
});
let baseInstance = new baseExtend(); // 创建组件实例
baseInstance.$mount();// 将实例挂载到id位mount-point的元素上
document.body.appenChild(baseInstance.$el); // 将实例元素插入body最后
// 第二种,option为导入的组件对象
// 此时baseExtendTwo为AComponent组件的构造函数
let baseExtendTwo = vue.extend(AComponent);
let baseInstanceTwo = new baseExtendTwo(); // 创建AComponent组件实例
baseInstanceTwo.$mount();// 将实例挂载到id位mount-point的元素上
document.body.appenChild(baseInstanceTwo.$el); // 将实例元素插入body最后
问题探究:元素动态挂载方法一二,$parent
,$children
解耦
问题1:需求:将弹窗由alert变成自定义弹窗组件,并挂载,步骤如下
1.在网上拷的不知名弹窗组件
<template>
<div class="box" v-if="isShow">
<h3>{{title}}</h3>
<p class="box-content">{{message}}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ""
},
message: {
type: String,
default: ""
},
// 持续几秒钟
duration: {
type: Number,
default: 1000
}
},
data() {
return {
isShow: false
};
},
methods: {
show() {
this.isShow = true;
//定时器
setTimeout(this.hide, this.duration);
},
hide() {
this.isShow = false;
//时间到了自动remove
this.remove();
}
}
};
</script>
<style>
.box {
position: fixed;
width: 100%;
top: 16px;
left: 0;
text-align: center;
pointer-events: none;
background-color: #fff;
border: grey 3px solid;
box-sizing: border-box;
}
.box-content {
width: 200px;
margin: 10px auto;
font-size: 14px;
padding: 8px 16px;
background: #fff;
border-radius: 3px;
margin-bottom: 8px;
}
</style>
2.写$mount
挂载
方法一:
import Vue from 'vue'
// 传入一个组件配置
// 创建它的实例,并且将它挂载到body上
// 返回组件实例
export default function create(Component, props) {
// 实例创建
// 方式一:借鸡生蛋
const vm = new Vue({
render(h) {
return h(Component, { props })
}
}).$mount() // $mount()本质上将vdom=》dom//挂载的本质 将虚拟的dom变成真实的dom
// 通过vm.$el获取生成的dom //vm.$el就是自己,vue实例中使用的根dom元素
document.body.appendChild(vm.$el)
// 删除函数
// 获取组件实例
const comp = vm.$children[0]
// 未来我不需要你的时候,要把你干掉
comp.remove = () => {
document.body.removeChild(vm.$el)
vm.$destroy()
}
return comp
}
方法二:
import Vue from 'vue'
export default function exCreate(Component,props){
var baseExtend = Vue.extend(Component)
//propsData选项传递属性
//此时拿到的直接就是组件实例 ,和借鸡生蛋不同,借鸡生蛋外面还有一层老母鸡
var baseInstance = new baseExtend({propsData:props})
baseInstance.$mount()
//输出$el,它是一个注释(标签),即console.log(baseInstance.$el)
//组件里面v-if导致的
//patch算法,diff一样的
//当后面执行show()即展示该对话框时,就会变成组件里的div了
document.body.appendChild(baseInstance.$el)
baseInstance.remove=()=>{
//移除dom
document.body.removeChild(baseInstance.$el)
//销毁组件
baseInstance.$destroy()
}
return baseInstance
}
3 .index.vue里的使用
methods:{
login(){
// this.$refs.loginForm.validate((isValidate)=>{
// if(isValidate){
// console.log('login')
// }else{
// alert('校验失败')
// }
// })
this.$refs.loginForm.validate((isValid)=>{
exCreate(Notice,{
title:'我是标题',
message:isValid?'登陆成功':'登陆失败',
duration:3000
}).show()
})
}
}
问题2:使用$parent
会产生耦合,如果KInput的老爹不是KFormItem就无效了
由此去看源码:需要“混入(mixin)”
作为混入被引用,一般是为了复用
什么是混入?
官方解释: 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
类型:
Array<Object>
详细:
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
示例:
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
通俗理解: 它可以制作一个可以复用的功能,可以复用到各个组件中,同时呢这个mixin功能可以使用vue组件里任意组件选项,比如data,method,watch,和各个生命周期函数。当需要运用的组件,引用它时,相当于把mixin的各个组件选项合并到引用的组件中。
boardcast及dispatch的mixin(来源:VUE源码)
广播boardcast:由上而下派发事件,而dispatch类似于冒泡,是由下向上的去查找componentName相同的组件名并派发事件
apply
apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性
到底怎么理解这句话呢?
apply() 方法接受数组形式的参数,即apply(对象,参数)时,使用了apply,无论是哪个对象使用,对象都变成apply(的这个对象)
因此使用此解决$parent
耦合性问题,解决方案如下:
1.参考Elment-UI源码 混入(mixin)的使用,粘贴混入
emitter.js
// 广播:由上而下派发事件 (组件名,事件名,参数数组)
function broadcast(componentName, eventName, params) {
// 遍历所有子元素,如果componentName与传入的相同 则派发事件
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
// dispatch类似于冒泡,是由下向上的去查找componentName相同的组件名并派发事件
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 向上查找 直到找到相同名称的组件
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
// 如果找到就派发事件
if (parent) {
// 使用apply:为了打散后面的数组,使后面数组的每一个元素对应一个参数
// apply作用:劫持另外一个对象的方法,继承另外一个对象的属性
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
2.修改KInput.vue里有关$parent
派发事件的问题
//需要增加修改的代码
import emitter from '../../mixins/emitter.js'
export default {
mixins:[emitter],
methods:{
onInput(e){
// this.$parent.$emit('validate');
//需要找到的老爹组件是KFormItem
this.dispatch('KFormItem','validate');
}
}
}
3 .由于老爹组件是KFormItem,为了找到他 需要在老爹组件是KFormItem给他定义一个名字
export default {
name:"KFormItem",//这个名字一般用于路由,路由缓存
componentName:"KFormItem",//这个才是本次需要使用的名字,但他不属于vue,是自定义的
}
问题3:同理解决$children
耦合性问题,解决方案如下:
由于广播会造成性能损失(所以还是使用了dispatch),因为他会不断的向下寻找知道找到叶子节点,因此查阅Element-UI的源码(form.vue)发现使用 this.fields
这个字段,遍历字段调用validate,因此猜测所有KFormItem的实例所组成的数组。
探索 this.fields
的产生发现 ,created()里面的addField事件,因此他与FormItem是有一个通信的,当FormItem挂载的时候会派发一个事件叫addField,会告诉当前的表单实例 我已经来了,你把我注册一下,加到这个数组保存一下,这样将来校验的时候就知道去调谁的校验方法了。
为什么FormItem要在mounted里去派发事件,因为这个时候他的老爹就确确实实存在了,这个时候派发事件通知KForm,新增一个字段 即 FormItem实例,引入混入emitter去派发事件,然后在KForm里监听该事件,一派发就监听(同源码一样在created里监听)
补充:什么是响应式数据? “响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
具体步骤如下:
1.在KFormItem里引用emitter.js
import emitter from '@/mixins/emitter'//@表示src
export default {
mixins:[emitter],
mounted() {
//在mounted里派发事件
//kkb.form.addField:kkb的库,表单里要新增一个字段(KForm派发)
//参数是自己 且要是一个数组(通过源码得)
//if(this.prop),过滤掉没有prop的KFormItem
if(this.prop){
this.dispatch('KForm','kkb.form.addField',[this])
}
}
}
2.在KForm里监听事件
export default {
name:"KForm",
componentName:"KForm",//注意一定不要拼错,不然找不到派发事件
created() {
// 不是响应式可写在这,响应式数据需写在data里面
this.fields=[],
//监听事件,事件名称,第二个参数是每一个formItem,将所有的item推进数组中
this.$on('kkb.form.addField',(item) => {
this.fields.push(item)
})
},
methods:{
validate(cb){
// const tasks=this.$children.filter(item=>item.prop).map(item=>item.validate())
// 上面的filter过滤属性也就可以在派发事件时做判断了,即:
const tasks = this.fields.map(item=>item.validate())
}
}
}
$children
解耦
全部基于 vue-study-web20案例
知识点补充
补充1:使用插件进⼀步封装便于使用Notice <~~~> create.js
import Notice from '@/components/Notice.vue'
//...
export default {
install(Vue) {
Vue.prototype.$notice = function (options) {
return create(Notice, options)
}
}
}
使用:
this.$notice({title:'xx'})
补充2:递归组件(一般用于多级菜单以及数据有孩子的)
递归组件是可以在它们⾃⼰模板中调⽤⾃身的组件。
例如,下⾯是Node.vue
<template>
<div>
<h3>{{data.name}}</h3>
<!-- 有条件嵌套 -->
<Node v-for="n in data.children" :key="n.name" :data="n">
</Node>
</div>
</template>
<script>
export default {
name: 'Node', // name对递归组件是必要的
props: {
data: {
type: Object,
require: true
},
},
}
</script>
递归组件的数据通常是⼀棵树,NodeTest.vue
<template>
<div>
<Node :data="folder"></Node>
</div>
</template>
<script>
import Node from '@/components/recursion/Node.vue';
export default {
components: {
Node,
},
data() {
return {
// 树形结构的数据
folder: {
name: "vue-study",
children: [
{ name: "src", children: [{ name: "main.js" }] },
{ name: "package.json" }
]
}
};
}
};
</script>
适当添加⼀些缩进
<h3 :style="{'text-indent': indent+'em'}">{{data.name}}</h3>
export default {
props: {
// 添加⼀个层级属性
level: {
type: Number,
default: 0
}
},
computed: {
indent() {
// 缩进两个字的宽度
return this.level * 2
}
},
}
实现Tree组件
Tree组件是典型的递归组件,其他的诸如菜单组件都属于这⼀类,也是相当常⻅的。
实现tree-node组件,recursion/TreeNode.vue
<template>
<div>
<div @click="toggle" :style="{paddingLeft: (level-1)+'em'}">
<label>{{model.title}}</label>
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<div v-show="open" v-if="isFolder">
<tree-node class="item" v-for="model in model.children" :model="model" :key="model.title" :level="level + 1"></tree-node>
</div>
</div>
</template>
<script>
export default {
name: "tree-node",
props: {
model: Object,
level: {
type: Number,
default: 1
}
},
data: function() {
return {
open: false
};
},
computed: {
isFolder: function() {
return this.model.children && this.model.children.length;
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.open = !this.open;
}
},
}
};
</script>
实现tree组件,recursion/Tree.vue
<template>
<div class="kkb-tree">
<TreeNode v-for="item in data" :key="item.title" :model="item"></TreeNode>
</div>
</template>
<script>
import TreeNode from '@/components/recursion/TreeNode.vue';
export default {
props: {
data: {
type: Array,
required: true
},
},
components: {
TreeNode,
},
}
</script>
<style scoped>
.kkb-tree {
text-align: left;
}
</style>
使⽤,recursion/index.vue
<template>
<Tree :data="treeData"></Tree>
</template>
<script>
import Tree from '@/components/recursion/Tree.vue';
export default {
data() {
return {
treeData: [{
title: "Web全栈架构师",
children: [
{
title: "Java架构师"
},
{
title: "JS⾼级",
children: [
{
title: "ES6"
},
{
title: "动效"
}
]
},
{
title: "Web全栈",
children: [
{
title: "Vue训练营",
expand: true,
children: [
{
title: "组件化"
},
{
title: "源码"
},
{
title: "docker部署"
}
]
},
{
title: "React",
children: [
{
title: "JSX"
},
{
title: "虚拟DOM"
}
]
},
{
title: "Node"
}
]
}
]
}]
};
},
components: {
Tree,
},
};
</script>