导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


基本使用

MyButton.vue

<template>
  <button>
    <slot></slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton'
}
</script>

App.vue

<template>
  <div>
    <my-button>
      Hello<span>World</span>
    </my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  }
}
</script>

Untitled

填充默认值

MyButton.vue

<template>
  <button>
    <slot>Lance</slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton'
}
</script>

App.vue

<template>
  <div>
    <my-button></my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  }
}
</script>

Untitled

DEMO - Loading 组件

把 fontawesome 放进 public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  ...
  <link href="<https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.3/css/all.min.css>" rel="stylesheet">
</head>
<body>
  ...
</body>
</html>

App.vue 中测试

<template>
  <div>
    <my-button>
      <span class="fa fa-spinner fa-spin"></span>
    </my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  }
}
</script>

在转的 loading 图标

在转的 loading 图标

成品

MyButton.vue

<template>
  <button @click="handleClick">
    <span
        v-if="loadingIcon && isLoading"
        class="fa fa-spinner fa-spin"></span>
    <slot>Lance</slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  props: ['text', 'loadingIcon', 'isLoading'],
  methods: {
    handleClick() {
      this.$emit('changeStatus');
    }
  }
}
</script>

App.vue

<template>
  <div>
    <my-button
      @changeStatus="changeStatus"
      :disabled="isLoading"
      :loadingIcon="true"
      :isLoading="isLoading"
    >
      {{ isLoading ? 'Loading...' : 'Click' }}
    </my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  },
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    changeStatus() {
      console.log(1);
      this.isLoading = true;

      setTimeout(() => {
        this.isLoading = false;
      }, 3000);
    }
  }
}
</script>

具名 slot 插槽

MyButton.vue

<template>
  <button @click="handleClick">
    <span
        v-if="loadingIcon && isLoading"
        class="fa fa-spinner fa-spin"></span>
    <slot name="myBtn">Click</slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  props: ['text', 'loadingIcon', 'isLoading'],
  methods: {
    handleClick() {
      this.$emit('changeStatus');
    }
  }
}
</script>

App.vue

<template>
  <div>
    <my-button
      @changeStatus="changeStatus"
      :disabled="isLoading"
      :loadingIcon="true"
      :isLoading="isLoading"
    >
      <!-- {{ isLoading ? 'Loading...' : 'Click' }} -->
      <!-- <template v-slot:myBtn> -->
      <template #myBtn>
        {{ isLoading ? 'Loading...' : 'Click' }}
      </template>
    </my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  },
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    changeStatus() {
      console.log(1);
      this.isLoading = true;

      setTimeout(() => {
        this.isLoading = false;
      }, 3000);
    }
  }
}
</script>

v-slot:default

如果没有在组件中定义具名slot插槽,那么外边如果非要用 template,则可以用 <teamplte v-slot:default> 或者 <template #default> 的方式来当做默认插槽。

MyButton.vue

<template>
  <button @click="handleClick">
    <span
        v-if="loadingIcon && isLoading"
        class="fa fa-spinner fa-spin"></span>
    <slot>Click</slot>
  </button>
</template>

App.vue

<template>
  <div>
    <my-button
      @changeStatus="changeStatus"
      :disabled="isLoading"
      :loadingIcon="true"
      :isLoading="isLoading"
    >
      <!-- <template v-slot:default> -->
      <template #default>
        {{ isLoading ? 'Loading...' : 'Click' }}
      </template>
    </my-button>
  </div>
</template>

默认插槽、具名的同名插槽可以同时多个

MyButton.vue

<template>
  <button @click="handleClick">
    <span
        v-if="loadingIcon && isLoading"
        class="fa fa-spinner fa-spin"></span>
    <!-- <slot name="myBtn">Click</slot> -->
    <slot name="title"></slot>
    <slot name="title"></slot>
    <slot>Click</slot>
    <slot>Click</slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  props: ['text', 'loadingIcon', 'isLoading'],
  methods: {
    handleClick() {
      this.$emit('changeStatus');
    }
  }
}
</script>

App.vue

<template>
  <div>
    <my-button
      @changeStatus="changeStatus"
      :disabled="isLoading"
      :loadingIcon="true"
      :isLoading="isLoading"
    >
      <template #title>
        <h1>TITLE</h1>
      </template>
      <template #default>
        {{ isLoading ? 'Loading...' : 'Click' }}
      </template>
    </my-button>
  </div>
</template>

<script>
import MyButton from './MyButton.vue';
export default {
  name: 'App',
  components: {
    MyButton
  },
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    changeStatus() {
      console.log(1);
      this.isLoading = true;

      setTimeout(() => {
        this.isLoading = false;
      }, 3000);
    }
  }
}
</script>

Untitled

后台管理布局 - slot 应用

Untitled

components/BaseLayout/index.vue

<template>
  <div class="container">
    <header class="base-header">
      <slot name="baseHeader">HEADER</slot>
    </header>
    <footer class="base-footer">
      <slot name="baseFooter">FOOTER</slot>
    </footer>
    <aside class="base-sidebar">
      <slot name="baseSidebar">SIDEBAR</slot>
    </aside>
    <main class="base-main">
      <slot>MAIN</slot>
    </main>
  </div>
</template>

<script>
export default {
  name: 'BaseLayout'
}
</script>

<style lang="scss">
html {
  margin: 0;
}
.base-header,
.base-footer,
.base-sidebar,
.base-main {
  position: fixed;
  left: 0;
}
.base-header {
  top: 0;
  z-index: 2;
  width: 100%;
  height: 60px;
  background-color: #000;
}
.base-footer {
  bottom: 0;
  z-index: 2;
  width: 100%;
  height: 60px;
  background-color: #ddd;
}
.base-sidebar {
  top: 0;
  z-index: 1;
  width: 200px;
  height: 100%;
  padding: 90px 0;
  background-color: #eee;
  box-sizing: border-box;
}
.base-main {
  top: 0;
  width: 100%;
  height: 100%;
  padding: 90px 30px 90px 230px;
  box-sizing: border-box;
}
</style>

App.vue

<template>
  <div>
    <base-layout>
      <template #baseHeader>
        <main-logo></main-logo>
      </template>
      <template #baseFooter>
        <footer-content></footer-content>
      </template>
      <template #baseSidebar>
        <base-list></base-list>
      </template>
      <template #default>
        <h1>This is my MAIN BOARD</h1>
      </template>
    </base-layout>
  </div>
</template>

<script>
import BaseLayout from './components/BaseLayout';
import MainLogo from './components/MainLogo';
import FooterContent from './components/FooterContent';
import BaseList from './components/BaseList';
export default {
  name: 'App',
  components: {
    BaseLayout,
    MainLogo,
    FooterContent,
    BaseList
  }
}
</script>

作用域插槽

PicBoard.vue

<template>
  <div>
    <ul>
      <li v-for="item of picData" :key="item.id">
        <div>
          <h1>{{ item.title }}</h1>
          <slot :item="item" :field="1"></slot>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'PicBoard',
  data() {
    return {
      picData: [
        {
          id: 1,
          title: '123',
          desc: '12333333',
          url: '<https://placekitten.com/100/100>'
        },
        {
          id: 2,
          title: '234',
          desc: '234444444',
          url: '<https://placekitten.com/200/200>'
        },
        {
          id: 3,
          title: '345',
          desc: '34555555',
          url: '<https://placekitten.com/300/300>'
        }
      ]
    }
  }
}
</script>

<style>

</style>

App.vue

<template>
  <div>
    <pic-board>
      <template #default="props">
        {{ props.item }} {{ props.field }}
      </template>
    </pic-board>
  </div>
</template>

<script>
import PicBoard from './components/PicBoard';
export default {
  name: 'App',
  components: {
    PicBoard
  }
}
</script>

如果没有具名插槽,上边的 <template #default="props" 还能写成:

<template v-slot="props">

没有具名插槽时可以直接把 props 挂在组件标签上

<pic-board v-slot="props">
  <img :src="props.url" :alt="props.desc" style="width: 200px;"/>
  <p>{{ props.desc }}</p>
</pic-board>

推荐写法:

<pic-board #default="props">
  <img :src="props.url" :alt="props.desc" style="width: 200px;"/>
  <p>{{ props.desc }}</p>
</pic-board>

props 可以解构

<pic-board v-slot="{ url, desc }">
  <img :src="url" :alt="desc" style="width: 200px;"/>
  <p>{{ desc }}</p>
</pic-board>

解构后起别名

<pic-board v-slot="{ url: imgUrl, desc: description }">
  <img :src="imgUrl" :alt="description" style="width: 200px;"/>
  <p>{{ description }}</p>
</pic-board>

还能给变量设置默认值

<pic-board v-slot="{ url: imgUrl, desc: description, field = 0 }">
  <img :src="imgUrl" :alt="description" style="width: 200px;"/>
  <p>【排行:{{field}}】{{ description }}</p>
</pic-board>

Untitled

配合 methods 使用

PicBoard.vue

<template>
  <div>
    <ul>
      <li v-for="item of picData" :key="item.id">
        <div>
          <h1>{{ item.title }}</h1>
          <slot :url="item.url" :desc="item.desc" :field="item.field"></slot>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'PicBoard',
  data() {
    return {
      picData: [
        {
          id: 1,
          title: '123',
          desc: '12333333',
          url: '<https://placekitten.com/100/100>',
          field: 0,
        },
        {
          id: 2,
          title: '234',
          desc: '234444444',
          url: '<https://placekitten.com/200/200>',
          field: 1,
        },
        {
          id: 3,
          title: '345',
          desc: '34555555',
          url: '<https://placekitten.com/300/300>',
          field: 2,
        }
      ]
    }
  }
}
</script>

<style>

</style>