乐趣区

关于前端:wheel-ui

yarn global add create-vite-app
cva wheel-ui
// 等价于
npm init vite-app <



cd wheel-ui
yarn install
yarn dev

// 查看 vue-router 所有版本号
npm info nue-router versions

// 装置 vue-router
yarn add vue-router


yarn 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.ts
declare 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 clean
yarn 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-nocheck
import 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"
  }
}
退出移动版