yarn global add create-vite-appcva wheel-ui// 等价于npm init vite-app <cd wheel-uiyarn installyarn dev// 查看 vue-router 所有版本号npm info nue-router versions// 装置 vue-routeryarn add vue-routeryarn add -D sass
vite 文档给出的命令是
npm init vite-app 我的项目名
yarn create vite-app 我的项目名
等价于
yarn global creat-vite-app
cva 我的项目名
也等价于
npx create-vite-app 我的项目名
即 npx 会帮你全局装置用到的包
初始化 vue-router
新建 history 对象
新建 router 对象
app.use(router)
const history = createWebHashHistory();const router = createRouter({ history: history, routes: [{ path: "/", component: HelloWorld }],});const app = createApp(App);app.use(router);app.mount("#app");
增加 <router-view>
<router-link>
<template> <div> 导航栏 | <router-link to="/"> frank </router-link> | <router-link to="/xxx"> frank2</router-link> </div> <hr /> <router-view /></template><script>export default { name: "App",};</script>
provide inject
需要:点击子组件 A ,暗藏子组件 B 某一内容。
App.vue
<script lang="ts">import { provide, ref } from "vue";export default { name: "App", setup() { const asideVisible = ref(false); // set provide("xxx", asideVisible); },};</script>
子组件 A
<!-- Topnav.vue --><template> <div class="topnav"> <div class="logo" @click="toggleAside">LOGO</div> </div></template><script lang="ts">import Vue, { inject, Ref } from "vue";export default { setup() { const asideVisible = inject<Ref<boolean>>("xxx"); const toggleAside = () => { asideVisible.value = !asideVisible.value; }; return { toggleAside }; },};</script>
子组件 B
<!-- Doc.vue --><template> <div> <Topnav /> <div class="content"> <aside v-if="asideVisible"> ... </aside> </div> </div></template><script lang="ts">import Topnav from "../components/Topnav.vue";import { inject, Ref } from "vue";export default { components: { Topnav }, setup() { const asideVisible = inject<Ref<boolean>>("xxx"); return { asideVisible }; },};</script>
TS 引入 .vue文件
找不到模块“./App.vue”或其相应的类型申明。ts(2307)
src 目录下创立以 .d.ts 为结尾的文件
// shims-vue.d.tsdeclare module '*.vue' { import { ComponentOptions } from "vue"; const componentOptions:ComponentOptions export default componentOptions}
error Command failed with exit code 1.
<center><img src="https://cdn.jsdelivr.net/gh/Drwna/image//images/Snipaste_2022-02-08_15-42-14.png" alt="Snipaste_2022-02-08_15-42-14" width="800px"></center>
<center><img src="https://cdn.jsdelivr.net/gh/Drwna/image//images/Snipaste_2022-02-08_15-42-14.png" alt="Snipaste_2022-02-08_15-42-14" width="800px"></center>
解决:
rm -rf node_modules/yarn cache cleanyarn install
父子组件通信
父组件
<template> <Switch :value="y" @input="y = $event" /></template><script lang="ts">import Switch from "../lib/Switch.vue";import { ref } from "vue";export default { components: { Switch }, setup() { const y = ref(false); return { y }; },};</script>
子组件
<template> <button @click="toggle" :class="{ checked: value }"> <span></span> </button></template><script lang="ts">import { ref } from "vue";export default { props: { value: Boolean, }, setup(props, context) { const toggle = () => { // 外围 context.emit("input", !props.value); }; return { toggle }; },};</script>
或者应用 v-model
父组件
<Switch v-model:value="y" />
子组件
context.emit("update:value", !props.value);
属性绑定
默认所有属性都绑定到根元素。
应用 inheritAttrs: false
能够勾销默认绑定。
应用 $attrs
或者 context.attrs
获取所有属性。
应用 v-bind=“$attrs”
批量绑定属性。
应用 cons {size, ...rest} = context.attrs
将属性放离开。
父组件
// ButtonDemo.vue<template> <div>Button 示例</div> <h1>示例1</h1> <div> <Button @click="onClick" @mouseover="onClick" size="small">你好</Button> </div></template><script lang="ts">import Button from "../lib/Button.vue";export default { components: { Button }, setup() { const onClick = () => { alert("hello"); }; return { onClick }; },};</script>
子组件
// Button.vue<template> // 将 size 属性绑定到根元素 <div :size="size"> // 将其余的属性绑定到指标元素 <button v-bind="rest"> <slot /> </button> </div></template><script lang="ts">export default { // 勾销默认绑定到根元素 inheritAttrs: false, setup(props, context) { const { size, ...rest } = context.attrs; return { size, rest }; },};</script>
开发 UI 库的 CSS 注意事项
不能应用 scoped
因为 data-v-xxx 中的 xxx 不能保障每次运行都雷同,必须输入稳固不变的 class 选择器,不便使用者笼罩。
必须加前缀
.button 不行,很容易被使用者笼罩,.wheel-button 行,不太容易被笼罩。
.theme-link 不行,很容易被使用者笼罩,.wheel-theme-link 行,不太容易被笼罩。
插槽
具名插槽
<template> <div class="wheel-dialog"> <header> <slot name="title"/> <span @click="close"></span> </header> <main> <slot name="content"/> </main> </template>
<template> <div> <template v-slot:content> <div>你好</div> <div>hello</div> </template> <template v-slot:title> <strong>xxx</strong> </template> </div></template>
Teleport
将 xxx 传送到 body 下
<Teleport to="body"> xxx</Teleport>
openDialog
openDialog.js
import Dialog from './Dialog.vue';import {createApp, h} from 'vue';export const openDialog = (options) => { const {title, content, closeOnClickOverlay, ok, cancel} = options; const div = document.createElement('div'); document.body.appendChild(div); const close = () => { //@ts-ignore app.unmount(div); div.remove(); }; const app = createApp({ render() { return h(Dialog, { visible: true, closeOnClickOverlay, 'onUpdate:visible': (newVisible) => {if (newVisible === false) close(); }, ok, cancel }, {title, content}); } }); app.mount(div);};
vue 组件应用
const showDialog = () => { openDialog({ title: '题目', content: '你好啊', closeOnClickOverlay: false, ok() {console.log('ok');}, cancel() {console.log('cancel');} }); };
查看子组件的类型
获取插槽内容 const defaults = context.slots.default()
<template> <div> Tabs 组件 </div> <component :is="defaults[0]"/> <component :is="defaults[1]"/></template><script lang="ts">import Tab from '../lib/Tab.vue';export default { setup(props, context) { // 调试 查看子组件是否为 Tab 组件 // console.log({...context.slots.default()[0]}); // console.log({...context.slots.default()[1]}); // const defaults = context.slots.default(); // console.log(defaults[0].type === Tab); const defaults = context.slots.default(); defaults.forEach(tag => { if (tag.type !== Tab) { throw new Error('Tabs 子标签必须是 Tab'); } }); return {defaults}; }};</script>
获取 el 的 width、left const {width, height, left, top} = el.getBoundingClientRect()
CSS 最小影响准则
css 相对不能影响库的使用者。
markdown
yarn add --dev marked
新建 plugins/md.ts
// @ts-nocheckimport path from 'path';import fs from 'fs';import { marked } from 'marked';const mdToJs = str => { const content = JSON.stringify(marked(str)); return `export default ${content}`;};export function md() { return { configureServer: [ // 用于开发 async ({app}) => { app.use(async (ctx, next) => { // koa if (ctx.path.endsWith('.md')) { ctx.type = 'js'; const filePath = path.join(process.cwd(), ctx.path); ctx.body = mdToJs(fs.readFileSync(filePath).toString()); } else { await next(); } }); }, ], transforms: [{ // 用于 rollup // 插件 test: context => context.path.endsWith('.md'), transform: ({code}) => mdToJs(code) }] };}
新建 vite.config.ts
import {md} from './plugins/md';export default { plugins: [md()]};
应用
<template> <article class="markdown-body" v-html="md"></article></template><script lang="ts">// intro.md 为写好的 markdown文件import md from '../markdown/intro.md'; export default { data() { return {md}; }};</script>
打消反复 markdown
应用动静引入 import()
==异步操作==
Markdown.vue
<template> <article class="markdown-body" v-html="content"></article></template><script lang="ts">import {ref} from 'vue';export default { props: { path: { type: String, required: true } }, setup(props) { // 外围 const content = ref<string>(); import(props.path).then(result => { console.log(result.default); // 动静援用 3s 后才会呈现 setTimeout(() => { content.value = result.default; }, 3000); }); return {content}; }};</script>
应用
Intro.vue
<template> <Markdown path="../markdown/intro.md"/></template><script lang="ts">import Markdown from '../components/Markdown.vue';export default {components: {Markdown},};</script>
或者间接全局引入 Markdown 组件,就不须要每个组件独自引入了。
main.ts app.component('Markdown', Markdown)
进一步优化:
间接在 router.js 里渲染,删除无用的 intro.vue
const history = createWebHashHistory();// 外围const md = filename => h(Markdown, {path: `../markdown/${filename}.md`, key: filename});export const router = createRouter({ history: history, routes: [ {path: '/', component: Home}, { path: '/doc', component: Doc, children: [ {path: '', component: DocDemo}, // 外围 {path: 'intro', component: md('intro')}, {path: 'install', component: md('install')}, {path: 'get-started', component: md('get-started')}, // {path: 'switch', component: SwitchDemo}, ... ], }, ],});
展现源代码
配置 vite.config.ts
import {md} from './plugins/md';import * as fs from 'fs';import {baseParse} from '@vue/compiler-core';export default { plugins: [md()], vueCustomBlockTransforms: { demo: (options) => { const {code, path} = options; const file = fs.readFileSync(path).toString(); //@ts-ignore const parsed = baseParse(file).children.find(n => n.tag === 'demo'); //@ts-ignore const title = parsed.children[0].content; const main = file.split(parsed.loc.source).join('').trim(); return `export default function (Component) { Component.__sourceCode = ${ JSON.stringify(main) } Component.__sourceCodeTitle = ${JSON.stringify(title)} }`.trim(); } }};
SwitchDemo.vue
<template> <div> <div class="demo-code"> <!-- 用法 --> <pre>{{ Switch1Demo.__sourceCode }}</pre> </div> </div></template><script lang="ts">export default { components: {Switch2Demo, Switch1Demo,Switch,Button}, setup() { const bool = ref(false); return {bool, Switch1Demo, Switch2Demo}; }};</script>
Switch1Demo.vue 组件结尾增加上 <demo>xxx</demo>
高亮源代码
应用 prismjs
yarn add prismjs导入import Prism from 'prismjs';import '../../node_modules/prismjs/themes/prism.min.css';...set(){return (Prism)}...应用<pre class="language-css" v-html="Prism.highlight(Switch1Demo.__sourceCode, Prism.languages.html, 'html')"/>
Vue2 和 Vue3 的区别
- Vue3 的 template 反对==多个跟标签==,Vue2 不反对。
- Vue3 有
createApp()
,而Vue2 的是new Vue()
- Vue3
createApp(组件)
,Vue2new Vue({template, render})
v-model 代替以前的 v-model,.sync。
新增 context.emit 和 this.$emit 作用雷同。
部署上线注意事项
配置 vite.config.ts
增加以下代码,
base: './',assetsDir: 'assets',
报错
runtime-core.esm-bundler.js?5c40:38 [Vue warn]: Invalid VNode type: Symbol(Comment) (symbol)
解决:
vue.config.js
const path = require(`path`);module.exports = { configureWebpack: { resolve: { symlinks: false, alias: { vue: path.resolve(`./node_modules/vue`) } } }};
打包部署
rollup -c
{ "name": "whl-ui", "version": "0.0.2", "files": [ "dist/lib/*" ], "main": "dist/lib/whl.js", "scripts": { "dev": "vite", "build": "vite build" }, "resolutions": { "node-sass": "npm:sass@^1.26.11" }, "dependencies": { "github-markdown-css": "4.0.0", "marked": "4.0.12", "prismjs": "1.21.0", "vue": "3.0.0", "vue-router": "4.0.0-beta.3" }, "devDependencies": { "@types/prismjs": "^1.26.0", "@vue/compiler-sfc": "3.0.0", "rollup-plugin-esbuild": "2.5.0", "rollup-plugin-scss": "2.6.0", "rollup-plugin-terser": "7.0.2", "rollup-plugin-vue": "6.0.0-beta.10", "sass": "1.26.11", "vite": "1.0.0-rc.1" }}
yarn build
{ "name": "whl-ui", "version": "0.0.2", "files": [ "dist/lib/*" ], "main": "dist/lib/whl.js", "scripts": { "dev": "vite", "build": "vite build" }, "resolutions": { "node-sass": "npm:sass@^1.26.11" }, "dependencies": { "github-markdown-css": "4.0.0", "marked": "4.0.12", "prismjs": "1.21.0", "vue": "^3.0.0", "vue-router": "4.0.0-beta.3" }, "devDependencies": { "@types/prismjs": "^1.26.0", "@vue/compiler-sfc": "^3.0.0", "rollup-plugin-esbuild": "2.5.0", "rollup-plugin-scss": "2.6.0", "rollup-plugin-terser": "7.0.2", "rollup-plugin-vue": "6.0.0-beta.10", "sass": "1.26.11", "vite": "1.0.0-rc.13" }}