Skip to content

Vue3 和 JSX -slot 的一些问题

JSX 和 slot 插槽

  • slot 是 Vue 发明的概念,为了完善 template 的能力
  • slot 是初学者的“噩梦”,特别是:作用域 slot
  • 使用 JSX 就容易理解,JSX 本质是 js

使用 JSX 和 slot 实现 tabs 功能

列子,有这样一个 tabs 组件 ./tabs-jsx/Index.vue

html
<template>
  <tabs default-active-key="1" @change="onTabsChange">
    <tab-panel key="1" title="title1">
      <div>tab panel content 1</div>
    </tab-panel>
    <tab-panel key="2" title="title1">
      <div>tab panel content 2</div>
    </tab-panel>
    <tab-panel key="3" title="title1">
      <div>tab panel content 3</div>
    </tab-panel>
  </tabs>
</template>

<script>
  import TabPanel from './TabPanel';
  import Tabs from './Tabs';

  export default {
    name: 'index',
    components: { TabPanel, Tabs },
    methods: {
      onTabsChange(key) {
        console.log('tab changed', key);
      },
    },
  };
</script>

然后 ./tabs-jsx/TabPanel.vue 有一个 slot

html
<template>
  <slot></slot>
</template>

<script>
  export default {
    name: 'TabPanel',
    props: ['key', 'title'],
  };
</script>

关键的在 ./tabs-jsx/Tabs.jsx 中,直接使用 filter 等 js 语法

js
import { ref } from 'vue';

export default {
  name: 'Tabs',
  props: ['defaultActiveKey'],
  emits: ['change'],
  setup(props, context) {
    const children = context.slots.default();
    const titles = children.map((panel) => {
      const { title, key } = panel.props || {};
      return {
        title,
        key,
      };
    });

    // 当前 actKey
    const actKey = ref(props.defaultActiveKey);

    function changeActKey(key) {
      actKey.value = key;
      context.emit('change', key);
    }

    // jsx
    const render = () => (
      <>
        <div>
          {/* 渲染 buttons */}
          {titles.map((titleInfo) => {
            const { title, key } = titleInfo;
            return (
              <button
                key={key}
                style={{ color: actKey.value === key ? 'blue' : '#333' }}
                onClick={() => changeActKey(key)}
              >
                {title}
              </button>
            );
          })}
        </div>

        <div>
          {children.filter((panel) => {
            const { key } = panel.props || {};
            // 匹配上,则显示,否则隐藏
            return actKey.value === key ? true : false;
          })}
        </div>
      </>
    );

    return render;
  },
};

使用 JSX 实现作用域 slot 插槽

首先回顾下作用域 slot 的使用

在 template 中使用作用域 slot 插槽

子文件 ./scoped-slot-template/Child.vue

html
<template>
  <slot :msg="msg"></slot>
</template>

<script>
  export default {
    name: 'Child',
    data() {
      return {
        msg: '作用域插槽 Child',
      };
    },
  };
</script>

在父文件 ./scoped-slot-template/Index.vue 通过作用域 slot 拿到子文件的 msg

html
<template>
  <child>
    <template v-slot:default="msgProp">
      <p>scoped slot template {{ msgProp.msg }}</p>
    </template>
  </child>
</template>

<script>
  import Child from './Child';

  export default {
    name: 'Index',
    components: { Child },
  };
</script>

在 JSX 实现作用域 slot 插槽

父文件 ./scoped-slot-jsx/Index.jsx,传递 render 函数

js
import { defineComponent } from 'vue';
import Child from './Child';

export default defineComponent(() => {
  function render(msg) {
    return <>{msg}</>;
  }

  return () => {
    return (
      <>
        <p>
          scoped slot Jsx <Child render={render}></Child>
        </p>
      </>
    );
  };
});

在子文件 ./scoped-slot-jsx/Child.jsx 执行 render 函数交给父组件显示

js
import { defineComponent, ref } from 'vue';

export default defineComponent({
  props: ['render'],
  setup(props) {
    const msgRef = ref('作用域插槽 Child');

    return () => {
      return <>{props.render(msgRef.value)}</>;
    };
  },
});

基于 MIT 许可发布