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(组件),Vue2 new 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"  }}