vuejs / rfcs

RFCs for substantial changes / feature additions to Vue core
4.86k stars 548 forks source link

this.$set(this.$slotsData, name, slotFunc) or this.$setSlotsData(name, slotFunc) #195

Open jiankafei opened 4 years ago

jiankafei commented 4 years ago

What problem does this feature solve?

It can solve the problem that JSX or H functions are needed to build complex components, which is as flexible as react.

The slot component can respond to external $slots (similar to props, not modifiable), and can also respond to the dynamically set $slots in the component, just like setting data (let's call it $slotsdata, although all $slots actually come from the outside, in essence, it provides a means to dynamically set $slots).

What does the proposed API look like?

<!-- MyComp -->
<template>
  <div>
    <!-- render aaa slot -->
    <slot name="aaa"></slot>
    <div v-for="(row, rowIndex) of daat" :key="rowIndex">
      <div v-for="(col, colIndex) of columns" :key="colIndex">
        <!-- render `label` slot -->
        <!-- In fact, the 'label' slot component renders the slots passed to the MyCompItem component -->
        <slot :name="col.label" :row="row" :rowIndex="rowIndex" :colIndex="colIndex">{{row[col.prop]}}</slot>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyComp',
  props: {
    daat: Array,
  },
  data() {
    return {
      columns: [],
    };
  },
  created() {
    // Get default slot vnode
    // It is actually the vnode of MyCompItem
    const MyCompItemVNode = this.$slots.default();
    this.columns = MyCompItemVNode.map((vnode) => {
      const componentProps = vnode.componentProps;
      const propsData = componentProps.propsData;
      // --------------- key point ------------------------
      // Slots should be mounted in` vnode.componentProps.$slots',
      // there's no need to hang it in` vnode.componentInstance.$slots',
      // because it's just a function that generates slots.
      // Mounting to componentinstance also has hidden dangers,
      // because you must instantiate slots before you can get the $slots function inside slots.
      // The hidden danger is that if the internal slots need to pass parameters,
      // developers must track the actual parameters passed and pass the default values for the instantiated slots.
      // If you don't have to instantiate the slots function inside the vnode of the slots,
      // then developers only need to instantiate slots where they use the slots function to avoid this hidden danger.
      // slots.xxx  In fact, it is equivalent to an attribute, but classified as slots, not props.
      const $slots = componentProps.$slots;
      // --------------- key point ------------------------
      // Set up new slots for the MyComp component through the following API, similar to setting data
      // The slot component can respond to both the external incoming $slots and the dynamically added $slotsdata
      $slots.default && this.$set(this.$slotsData, propsData.label, $slots.default);
      // or
      // $slots.default && this.$setSlots(propsData.label, $slots.default);
      return propsData;
    });
  },
};
</script>

<!-- MyCompItem -->
<template>
  <div></div>
</template>
<script>
export default {
  name: 'MyCompItem',
  props: {
    label: String,
    prop: String,
  },
};
</script>

<!-- usage -->
<template>
  <div>
    <my-comp :daat="daat">
      <!-- Render to 'aaa' slot of MyComp -->
      <template #aaa>
        <div>AAAAAAAA</div>
      </template>
      <!-- my-comp-item is not actually rendered -->
      <my-comp-item label="名称" prop="name"></my-comp-item>
      <my-comp-item label="描述" prop="desc">
        <!-- It will actually render to the description slot of the MyComp component -->
        <template #default={ row, rowIndex, colIndex }>
          <div>{{row}}{{rowIndex}}{{colIndex}}</div>
        </template>
      </my-comp-item>
    </my-comp>
  </div>
</template>
<script>
import MyComp from './MyComp';
import MyCompItem from './MyCompItem';

export default {
  components: {
    MyComp,
    MyCompItem,
  },
  data() {
    return {
      daat: [
        {
          name: 'vue',
          desc: 'mvvm framework'
        }
        {
          name: 'react',
          desc: 'mvvm framework too'
        }
      ],
    };
  },
};
</script>