使用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)
- 添加
- 添加
三个create 分别对应History路由、内存型路由和Hash型路由
TS项目找不到模块 xxx.vue
出现原因
- TypeScript只能理解.ts文件,无法理解.vue文件
解决方法
- Google 搜索 Vue 3 can not find module
- 创建xxx.d.ts,告诉TS如何理解.vue文件
// 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. 父子数据