使用Vite 搭建官网过程

Rinsann 2021年11月07日 595次浏览

使用Vite 搭建官网

全局安装create-vite-app

yarn global add create-vite-app@1.18.0  // 推荐yarn
npm i -g create-vite-app@1.18.0

创建项目目录

cva Sakura-UI 或者 create-vite-app Sakura-UI
// 其中Sakura-UI 是 自定义项目名称
cd Sakura-UI
npm install (or `yarn`)
npm run dev (or `yarn dev`)

然后打开http://localhost:3000/,浏览效果~

vite 文档给出的命令是

  • npm init vite-app
  • yarn create vite-app

等价于

  • 全局安装 create-vite-app 之后 cva

等价于

  • npx create-vite-app
  • 即 npx 会帮你全局安装用到的包

使用VSCode 或 Webstorm 打开目录。

Vue2 和 Vue3 的区别

  • 90%的写法一致,除了以下几点
  • Vue3的Template 支持多个根标签,Vue2不支持
  • Vue3 有 createApp(),而 Vue2 的是 new Vue()
  • createApp(组件), new Vue({template,render})

引入 Vue Router 4

Router 路由器,用于页面切换

使用命令行查看 vue-router 的 所有版本号

//查看 
npm info vue-router versions
//安装 
yarn add vue-router@4.0.0-beta.3

初始化 vue-router

  • 新建 history 对象
  • 新建 router 对象
  • app.use(router)
  • 添加
  • 添加

image20211101213049945.png

三个create 分别对应History路由、内存型路由和Hash型路由

TS项目找不到模块 xxx.vue

出现原因

  • TypeScript只能理解.ts文件,无法理解.vue文件

解决方法

  • Google 搜索 Vue 3 can not find module
  • 创建xxx.d.ts,告诉TS如何理解.vue文件

image20211101213720504.png

// shims-vue.d.ts
declare module '*.vue' {
    import { ComponentOptions } from "vue";
    const componentOptions: ComponentOptions
    export default componentOptions
}

开始创建官网

Home.vue

  • Topnav:左边是logo,右边是menu
  • Banner:文件介绍 + 开始按钮

Doc.vue

  • Topnav:左边是logo,右边是menu
  • Content:左边是 aside,右边是 main

新的路由

路径为 #/ 时

  • 渲染Home.vue

路径为 #/doc 时

  • 渲染Doc.vue

点击切换 aside

点击一次显示,再点击一次隐藏

需要用到Vue 提供的 provide/inject

//App.vue
<template>
  <router-view />
</template>

<script lang="ts">
import { ref, provide } from "vue";
export default {
  name: "App",
  setup() {
    const menuVisible = ref(false);
    provide("xxx", menuVisible);
  },
};
</script>
// Topnav.vue
<template>
  <div class="topnav">
    <div class="logo" @click="toggleMenu">LOGO</div>
    <ul class="menu">
      <li>菜单1</li>
      <li>菜单2</li>
    </ul>
  </div>
</template>

<script lang="ts">
import { inject, Ref } from "vue";
export default {
  setup() {
    const menuVisible = inject<Ref<boolean>>("xxx");
    console.log("topnav 获取的 menuVisible 为:" + menuVisible.value);
    const toggleMenu = () => {
      menuVisible.value = !menuVisible.value;
    };
    return { toggleMenu };
  },
};
</script>

<style lang="scss" scoped>
.topnav {
  background: pink;
  display: flex;
  padding: 16px;
  position: relative;
  z-index: 10;
  > .logo {
    max-width: 6em;
    margin-right: auto;
  }
  > .menu {
    display: flex;
    white-space: nowrap;
    flex-wrap: nowrap;
    > li {
      margin: 0 1em;
    }
  }
}
</style>
// Doc.vue
<template>
  <div>
    <Topnav />
    <div class="content">
      <aside v-if="menuVisible">
        <h2>组件列表</h2>
        <ol>
          <li>
            <router-link to="/doc/swich">Switch 组件</router-link>
          </li>
          <li>
            <router-link to="/doc/button">Button 组件</router-link>
          </li>
          <li>
            <router-link to="/doc/dialog">Dialog 组件</router-link>
          </li>
          <li>
            <router-link to="/doc/tabs">Tabs 组件</router-link>
          </li>
        </ol>
      </aside>
      <main>主内容</main>
    </div>
  </div>
</template>
<script lang="ts">
import { inject, Ref } from "vue";
import Topnav from "../components/Topnav.vue";
export default {
  components: { Topnav },
  setup() {
    const menuVisible = inject<Ref<boolean>>("xxx");
    console.log("Doc aside 获取的 menuVisible 为:" + menuVisible.value);
    return { menuVisible };
  },
};
</script>
<style lang="scss" scoped>
aside {
  background: lightblue;
  width: 150px;
  padding: 16px;
  position: fixed;
  top: 0;
  left: 0;
  padding-top: 70px;
  > h2 {
    margin-bottom: 4px;
  }
  > ol {
    > li {
      padding: 4px 0;
    }
  }
}
</style>

Switch 组件

API 设计

Switch 组件怎么用

<Switch value="true" /> value 为字符串 "true"
<Switch value="false" /> value 为字符串 "false"
<Switch :value="true" /> value 为布尔值 true
<Switch :value="false" /> value 为布尔值 false

总结

  • 当value为字符串"true"或布尔值true时,显示为开
  • 其他情况一律显示为关

value 不仅仅是初始状态,也可以也表示更新之后的状态

如何让 Switch 接受 value ? 使用props

添加 value 属性 添加 input事件 用于让外界知道当前的状态是开还是关

<Switch value="xxx" @input="xxx = $event" />  // value 记录的是每一次的状态

如何让 Switch 发出 input 事件?

用 context.emit('input',xxx) 即可,xxx 会被当做$event。

$event 是什么

$event 的 指是emit 的第二个参数,emit(事件名,事件参数)

知识点总结

  • value="true" 和 :value="true" 的区别,前者是字符串后者是boolean
  • 使用 CSS transition 添加过渡动画
  • 使用 ref 创建 内部数据
  • 使用 :value 和 @input 让 父子组件进行交流(组件通信)
  • 使用$event、v-model
  • 框架 = 限制:不准改props

Vue2 和 Vue3的区别

  • 新增v-model替代以前的 v-model和.sync
  • 新增context.emit,与this.$emit作用相同

Button组件

需求

  • 可以有不同的等级(level)
  • 可以是连接,可以是文字
  • 可以click、focus、鼠标悬浮
  • 可以改变size:大中小
  • 可以禁用(disabled)
  • 可以加载中(loading)

API设计

Button组件怎么用

<Button @click=? @focus=? @mouseover=? 
        theme="buttom pr link or text" 
        level="main or normal or minor" s
        ize="big or normal or small"
        disabled loading>
</Button>

让Button 支持事件

@click @focus @mouseover

第一步div不继承属性

第二步让div里的button绑定 $attrs

小结

Vue3属性绑定

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

让button支持theme属性 : theme的值为button / link / text

让button支持size属性:size的值为 big/ normal / small

让button支持 level 属性 :level 的值为 main / normal / minnor / danger

让 button 支持 disabled:disabled 的 值为 true / false

<button disabled> //true
<button :disabled="true"> //true
<button disabled="true"> //false
<button disabled="false"> //false

让 button 支持loading : loading值为 true / false

UI 库的CSS注意事项

不能使用scoped

  • 因为data-v-xxx中的xxx每次运行都可能不同
  • 必须输入稳定不变的class选择器,方便使用者覆盖

必须加前缀

  • .button 不行,很容易被使用者覆盖
  • .gulu-button可以,不太容易被覆盖
  • .theme-link,不行 很容易被使用者覆盖
  • .gulu-theme-link 可以,不太容易被覆盖

CSS最小影响原则

你的CSS绝对不能影响库使用者

知识点

Vue属性继承

  • 默认属性传给根元素
  • inheritAttrs:false 禁用
  • 绑定特定元素 v-bind="$attrs" 或 context.attrs
  • vue3中使用组件时,默认属性添加到组件的根元素上,如果想把属性放在指定的元素上,可以用v-bind="$attrs",但是这样做会把所有属性都导入进去,如果想指定元素添加的话,可以在setup(props,context)中 const {析构属性名, ...其他元素} = context.attrs,然后在通过v-bind绑定指定元素

props V.S. attrs

  • props 需要先声明才能获取值,而attrs则不用
  • props声明郭的属性,attrs里面不会出现
  • props不包含事件,attrs包含事件
  • props支持String以外的类型,而attrs只有String类型

库的CSS要求

  • 不能用scoped
  • 每个CSS类要加前缀
  • CSS最小影响原则

其它

  • 如何做loading 动画
> .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)
  }
}			

Dialog 组件

需求

  • 点击后弹出,有遮罩层 overlay
  • 有close 按钮,有标题、内容
  • 有 yes / no 按钮

API设计

Dialog 组件怎么用

<Dialog visible title="标题" @yes="fn1" @no="fn2"></Dialog>

让 Dialog 支持 visible 属性:不用show表示是否可见

让 Dialog 可以点击关闭:注意不能直接修改props

具名插槽

<template v-slot:content>
      <strong>你好</strong>
      <div>hi</div>
</template>
<template v-slot:title>
      <strong>加粗的标题</strong>
</template>
// 使用
<slot name="title"/>
<slot name="content"/>

把Dialog移到body下,防止Dialog被遮挡

使用新组建:Teleport

一句话打开Dialog,不想声明visible变量然后改变它的值,技术点:动态挂载组件

Tags标签页组件

Tags组件

参考 AntD、AntD Vue或者 Bulma、Element、iView等

需求

点击tab切换内容,有一条横线在动

第二步:API设计

Tabs组件怎么用

<Tabs>
	<Tab title="导航1">内容1</Tab>
	<Tab title="导航2"><component1 /></Tab>
	<Tab title="导航3"><component1 x="hi" /></Tab>
</Tabs>
// 或者
<Tabs :data="[
	{title="导航1",content:'内容1'},
    {title="导航2",content:Component1},
    {title="导航3",content:h(Component1,{x:'hi'})},
]" />

如何在运行时确认子组件的类型:检查 context.slots.default() 数组

如何渲染嵌套的组件:嵌套插槽

切换标签页

用selected 标记被选中的标签页

  • selected用index表示,不推荐
  • selected用name表示,不方便
  • selected用title表示,有漏洞
  • 设计就是妥协的艺术

总结

用JS获取插槽内容

  • const default = context.slots.default()

使用Vue3遇到bug

  • 确定不是自己代码
  • 用一个例子复现bug
  • 把例子链接和bug复现步骤提交给Vue团队

钩子

  • onMounted / onUpdated / watchEffect

TypeScript泛型

const indicator = ref<HTMLDivElement>(null)

获取宽高和位置

  • const {width,height,left,} = el.getBoundingClientRect()

ES6 析构赋值的重命名语法

  • const = x.getBoundingClientRect()
  • const = y.getBoundingClientRect()

首页装修

添加icon

  • 使用iconfont.cn的symbol模式

添加圆弧

  • 使用border-radius:100px 40px
  • 使用clip-path:ellipse(80% 60% at 50% 40%)

响应式页面

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

Don't Repeat Yourself

Vue3 的 v-model

要求

  • 属性名任意,假设为x
  • 事件名必须为"update:x"

效果

  • <Switch :value="y" @update:value="y = $event " />
   简写为 <Switch v-model:value="y" />

文档
v-model文档这是 Vue2 到 Vue3的一个大变动 (breaking change)

Vue3编程模型

内部数据 V.S. 父子数据
image20211103214504918.png

image20211103214526282.png