# value、 :value 区别

value="true"    // 字符串
:value="true"   // 布尔

# :value 和 @事件名 父子通信

  • 使用 context.emit(事件名,事件参数) 触发事件
  • $event 的值是 emit 的第二个参数
// SwitchDemo.vue
import Switch from '../lib/Switch.vue'
<Switch :value='bool' @input="bool = $event" />

export default{
  setup(){
    const bool = ref(false)
    return {bool}
  }
}

// Switch.vue
<button @click="toggle" :class="{select:value}"></button>

export default{
  props:{ value: Boolean}
  setup(props,context){
    const toggle = ()=>{
      context.emit('input', !props.value)
    }
  }
}

# v-model

<Switch v-model:value="bool"/>

<!-- 是以下的简写: -->

<Switch :value='bool' @update:value="bool = $event" />

完整代码
// SwitchDemo.vue
import Switch from '../lib/Switch.vue'
<Switch v-model:value="bool">

export default{
  setup(){
    const bool = ref(false)
    return {bool}
  }
}

// Switch.vue
<button @click="toggle" :class="{select:value}"></button>

export default{
  props:{ value: Boolean}
  setup(props,context){
    const toggle = ()=>{
      context.emit('update:value', !props.value)
    }
  }
}

# vue3 属性绑定

  • 默认所有属性都绑定到根元素
  • 使用 inheritAttrs:false 可以取消默认绑定
  • 使用 $attrs 或者 context.attrs 获取所有属性
  • 使用 v-bind="$attrs" 批量绑定属性
  • 使用 const {size,...rest} = context.attrs 将属性分开
// ButtonDemo.vue

<Button @click="onClick" @focus="onClick" @mouseover="onClick" size="small">按钮</Button>

import Button from '../lib/Button.vue'
export default{
  components: {Button},
  setup(){
    const onClick = ()=>{ console.log('hi')}
  }
}

// Button.vue

<!-- 默认所有属性都绑定到根元素 div -->
<div :size="size">
  <button v-bind="$attrs"></button>

  <button v-bind="rest"></button>
</div>

export default{
  <!-- 取消默认属性绑定 -->
  inheritAttrs: false
  setup(props,context){
    const { size, ...rest} = context.attrs
    return { siez, rest}
  }
}

# 库 CSS

  • 不能用 scoped
    • 因为 data-v-xxx 中的 xxx 每次运行可能不同
    • 必须输出稳定不变的 class 选择器, 方便使用者覆盖
  • 每个 CSS 类要加前缀
  • CSS 最小影响原则
[class^='gulu-']   // 以 gulu- 开头的 class
[class*=' gulu-']  // 包含 gulu- 的class

# loading 动画效果

<span
  v-if="loading"
  class="gulu-loadingIndicator"
></span>

// css
> .gulu-loadingIndicator {
  width: 14px;
  height: 14px;
  display: inline-block;
  margin-right: 4px;
  border-radius: 8px;
  border-color: $blue $blue $blue transparent;
  border-style: solid;
  border-width: 2px;
  animation: gulu-spin 1s infinite linear;
}

@keyframes gulu-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

# 使用 JS 获取插槽内容

const defaluts = context.slots.default()

<Tabs v-model:selected="x">
  <Tab title="导航1">内容1</Tab>
  <Tab title="导航2">内容2</Tab>
</Tabs>


import Tab from './Tab.vue'
export default{
  setup(props, context){
    const defaults = context.slots.default()
    console.log( defaults[0].type)

    defaults.forEach((tag) => {
      // @ts-ignore
      if (tag.type.name !== Tab.name) {
        throw new Error("Tabs 子标签必须是 Tab");
      }
    });
  }
}

# TypeScript 泛型

<div ref="container">
  <div :ref="el => { if (t===selected) selectedItem = el }"></div>
  <div ref="indicator"></div>
</div>

const container = ref<HTMLDivElement>(null);
const selectedItem = ref<HTMLDivElement>(null);
const indicator = ref<HTMLDivElement>(null);

# watchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

  • 获取宽高和位置 const {width,height}=el.getBoundingClientRect()
watchEffect(
  () => {
    const { width } = selectedItem.value.getBoundingClientRect();
    indicator.value.style.width = width + "px";

    <!-- ES6 析构赋值重命名语法 div -->
    const { left: left1 } = container.value.getBoundingClientRect();
    const { left: left2 } = selectedItem.value.getBoundingClientRect();
    const left = left2 - left1;
    indicator.value.style.left = left + "px";
  }
);

# 响应式页面

手机样式 + @media(min-width:600px) + @media(min-width:800px) + @media(min-width:1200px)

# Grid 布局

  • grid-template-columns: 定义每一列的列宽
  • grid-template-rows: 定义每一行的行高
  • grid-template-areas: 定义区域
display: grid;
justify-content: start;
align-content: space-between;
grid-template-areas: "icon title" "icon text";
grid-template-rows: 1fr auto;
grid-template-columns: 80px auto;

# 圆弧

background: linear-gradient(
  145deg,
  rgba(227, 255, 253, 1) 0%,
  rgba(183, 233, 230, 1) 100%
);
// 实现圆弧
clip-path: ellipse(80% 60% at 50% 40%);

# 高亮源代码

使用 prismjs 和 v-html

<pre
  class="language-html"
  v-html="html"
/>

import "prismjs";
import "prismjs/themes/prism.css";
const Prism = (window as any).Prism;

setup() {
  const html = computed(() => {
    return Prism.highlight(
      props.component.__sourceCode,
      Prism.languages.html,
      "html"
    );
  });
}

# 引入 Github Markdown 样式

使用 github-markdown-css

// 引入
import 'github-markdown-css'

// 添加 class
<template>
  <article class="mardown-body"></article>
</template>