likes
comments
collection
share

vue 递归组件 作用域插槽

作者站长头像
站长
· 阅读数 17

开头

这里主要是根据 vue 递归组件 作用域插槽 代码的理解和el-tree是如何写的。

代码

父组件

<template>
  <div>
    <Tree :data="data">
      <template #default="{ title }">
        <div class="prent">
          {{ title + "+自定义" }}
        </div>
      </template>
    </Tree>
  </div>
</template>
<script>
import Tree from "./tree.vue";
export default {
  components: {
    Tree,
  },
  data() {
    return {
      data: [{
        title: "父1",
        children: [{
          title: "子",
          children:[{title:"孙",}]
        }],
      },{
        title: "父2",
        children:[{title:"子"}]
      }]
    };
  }
};
</script>

子组件

<template>
  <div class="tree">
    <div v-for="item of data" :key="item.title">
      <!-- 显示title标题 -->
      <div class="title">
        <!-- 插槽,这里也是把title传出去, A插槽 -->
        <slot :title="item.title">
          <!-- {{ item.title }} -->
        </slot>
      </div>
      <!-- 如果存在子项则调用本身组件 递归 -->
      <Tree v-if="item.children" :data='item.children'>
            <!-- B 插槽 -->
          <slot :title='item.title' />
      </Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Tree',
  props: {
    data: Array,
  },
};
</script>

<style scoped>
.tree {
  padding-left: 10px;
}

ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}
</style>

理解步骤,始终知道 -> 递归就是把最里面的放到最外面来,你就当 A插槽最后会被 B 插槽替代。 所以,父组件的 default 插槽用的是 B 插槽,因此 B 插槽就暴露出一个 title 给父组件使用。

删掉 A 的title :

<template>
  <div class="tree">
    <div v-for="item of data" :key="item.title">
      <!-- 显示title标题 -->
      <div class="title">
        <!-- 插槽,这里也是把title传出去, A插槽 -->
        <slot :title="item.title">
          <!-- {{ item.title }} -->
        </slot>
      </div>
      <!-- 如果存在子项则调用本身组件 递归 -->
      <Tree v-if="item.children" :data='item.children'>
            <!-- B 插槽 -->
          <slot :title='item.title' />
      </Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Tree',
  props: {
    data: Array,
  },
};
</script>

<style scoped>
.tree {
  padding-left: 10px;
}

ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}
</style>

结果:

vue 递归组件 作用域插槽

由于可能只有一层,所以走不到 B 插槽,因此 A 插槽也需要暴露一个 title 给外面使用。

el-tree 的原理

父组件

<template>
  <div>
    <Tree :data="data">
        <!-- C -->
      <template #default="{ title }">
        <div class="prent">
          {{ title + "+自定义11" }}
        </div>
      </template>
    </Tree>
  </div>
</template>
<script>
import Tree from "./tree.vue";
export default {
  components: {
    Tree,
  },
  data() {
    return {
      data: [{
        title: "父1",
        children: [{
          title: "子",
          children:[{title:"孙",}]
        }],
      },{
        title: "父2",
        children:[{title:"子"}]
      }]
    };
  }
};
</script>

子组件

<template>
  <div class="tree">
    <div v-for="item of data" :key="item.title">
      <!-- 显示title标题 -->
      <div class="title">
        <!-- 插槽,这里也是把title传出去, A -->
        <slot :title="item.title">
          <!-- {{ item.title }} -->
        </slot>
      </div>
      <!-- 如果存在子项则调用本身组件 递归 -->
      <Tree v-if="item.children" :data='item.children'>
        <!-- B -->
        <template #default="{ title }">
          <div class="prent">
            {{ title + "+自定义22" }}
          </div>
        </template>
      </Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Tree',
  props: {
    data: Array,
  },
  data() {
    return {
      tree: null,
    }
  },
  created() {
    if(!this.$parent.$scopedSlots.default) {
      this.tree = this
    }else {
      this.tree = this.$parent.tree
    }
  },
};
</script>

<style scoped>
.tree {
  padding-left: 10px;
}

ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}
</style>

结果:

vue 递归组件 作用域插槽

这里可以看到,父组件的 C 和 子组件中的 B 都是使用到了 A 这个插槽。

这里我们只要能把 B 替换成父组件的 C 就完成了递归插槽。(递归是使用父类的插槽模版)

子组件的代码转变

<template>
  <div class="tree">
    <div v-for="item of data" :key="item.title">
      <!-- 显示title标题 -->
      <div class="title">
        <!-- 插槽,这里也是把title传出去 -->
        <slot :title="item.title">
          <!-- {{ item.title }} -->
        </slot>
      </div>
      <!-- 如果存在子项则调用本身组件 递归 -->
      <Tree v-if="item.children" :data='item.children'>
        <template #default="{ title }">
          <node :title="title">
          </node>
        </template>
      </Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Tree',
  components: {
    node: {
      props: {
        title: String,
      },
      render(h) {
        const parent = this.$parent;
        const tree = parent.tree
        const title = this.title
        return (tree.$scopedSlots.default({ title }))
      }
    }
  },
  props: {
    data: Array,
  },
  data() {
    return {
      tree: null,
    }
  },
  created() {
    if (!this.$parent.$scopedSlots.default) {
      this.tree = this
    } else {
      this.tree = this.$parent.tree
    }
  },
};
</script>

<style scoped>
.tree {
  padding-left: 10px;
}

ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}
</style>

这里搞了一个 node 的函数组件,node 函数组件拿到 子组件的 tree, tree也是一层层的保存着 $scopedSlots.default 其实就是 C 的那些编译节点。 然后把 title 传给了 C。

el-tree 源码贴图

vue 递归组件 作用域插槽

tree

vue 递归组件 作用域插槽

tree-node

vue 递归组件 作用域插槽

vue 递归组件 作用域插槽

vue 递归组件 作用域插槽

vue 递归组件 作用域插槽

总结

写的有点乱啊,这个只是辅助你理解 递归插槽,其实一开始都是懵逼了,多看下代码理解还是能看的懂的。