【VUE+TS】1.0 Vue3.0+TS打造企业级组件库

目的

使用vue单元测试库保证代码质量
开源项目的开发发布流程
设计合理的设计广泛适用的API
如何保证代码质量
vue3的实现原理

表单组件库

高频场景
交互复杂
定制型高
涉及到数据(校验)安全

学习优点

代码质量高
适应场景丰富
开源维护参与
完善的发布流程
源码(响应式原理)

核心功能

表单生成
主体系统
插件系统

和其他表单组件库的区别

无需编写代码:由json负责组装代码的要求。
开源全栈通用:对于校验部分可以做到全栈
跨平台
拖拽实现表单生成。

内容结构

项目结构
开发模式讲解:JSX的开发模式
vue3的TS规范定义(vue3的源码全部使用TS编写的)
单元测试
高泛用性的API
响应式原理(vue3的原理)
完善功能开发(组件开发)
自动化发布流程

TypeScript

TS最重要的核心就是type(类型),和js最大的区别就是把java换成了type,也就是说有类型的js。

所以使用ts:

1.任何变量都声明类型;
2.不到万不得已不要使用any
3.给对象声明接口

创建项目

win+R

cmd

cd 
F:\work_mysself\company_workspace\vue-json-schema-form_workspace

F:

vue create vue-json-schema-form

image.png

选择自定义,然后通过空格选中/取消选中,Babel必须的,用于编译JSX文件。
image.png

其中router和vuex其实不需要,但是我这里也选中了。
image.png

class-style是vue2喜欢用的一种编写方式,但是vue3已经不需要这种写法,所以这里选择n。

image.png

是否在ts的基础上使用babel,这是需要的 , 选择y。
这里用历史路由,并用node-sass


image.png

为了更好的学习,这里选择Perttier,个人其实更喜欢使用standard config。


image.png

这里选择Jest使用。
image.png

单独写在各自的文件里面。
image.png

image.png

完成后,打开项目,终端运行

npm run serve
image.png

image.png

Prettier

代码格式化工具,并在保存的时候会自动格式化,保持风格一致。
首先需要安装插件

image.png

创建配置文件.prettierrc,它支持json语法:

{
  "desc-semi": "代码里面是否需要写分号:不写",
  "semi": false,
  "desc-singleQuote": "是否使用单引号:习惯单引",
  "singleQuote": true,
  "desc-arrowParens": "匿名函数单个参数时是否写括号:习惯写",
  "arrowParens": "always",
  "desc-trailingComma": " object属性一行行写下去之后,是否在最后加一个逗号",
  "trailingComma": "all"
}

还有很多规则,可以去官网学习并设置。
设置好之后打开src\main.ts,尝试保存,或者shift+alt+f格式化,发现没有变化,这是需要我们配置一下这个文件:

image.png

image.png

这里我们需要设置工作区,所以在工作区打钩Format On Save。关闭这个设置页面,会发现项目下多出来一个.vscode文件夹
image.png

里面保存vscode针对这一个项目的各种配置。
然后去掉``,再打开src/main.ts,保存就会发现被规则修改:
image.png

vscode还有一个推荐的setting配置:


image.png

当切换文件、或者关闭保存、或者vscode突然崩溃的时候,自动保存代码。

vue3中TS如何去定义组件——Component接口

vue3中提供了一个defineComponent函数。
函数的实现直接返回了组件的定义。

如何定义Props的类型

打开src\views\Home.vue
发现初始化模板中已经实现了defineComponent函数。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src

export default defineComponent({
  name: "Home",
  components: {
    HelloWorld,
  },
});
</script>

避免props出现 undefined值,如下代码是正常的:

image.png

但如果像下面这样写,还是允许出现 undefined值:
image.png

<script lang="ts">
import { defineComponent } from 'vue'

const PropsType = {
  msg: String,
  age: { type: Number, required: true },
}
export default defineComponent({
  name: 'HelloWorld',
  props: PropsType,
  mounted() {
    this.age
  },
})
</script>

这时候需要如下约束:


image.png
<script lang="ts">
import { defineComponent } from 'vue'

const PropsType = {
  msg: String,
  age: { type: Number, required: true } as const,
}
export default defineComponent({
  name: 'HelloWorld',
  props: PropsType,
  mounted() {
    this.age
  },
})
</script>
image.png

这是放在外面,因为在声明的时候,vue并不知道它要使用的用途,如果放在里面就不会出现这个问题:


image.png

h函数

h函数就是用来创建节点的。
比如修改src/main.js

import { createApp, defineComponent, h } from 'vue'
// import App from './App.vue'
import router from './router'
import store from './store'
import HelloWorld from './components/HelloWorld.vue'

const App = defineComponent({
  render() {
    return h('div', { id: 'app' }, [
      h('img', { alt: 'Vue logo', src: './assets/logo.png' }),
      h(HelloWorld, {
        msg: '欢迎来到Vue+TS 的应用现场',
        age: 12,
      })
    ])
  },
})

createApp(App).use(store).use(router).mount('#app')

image.png

这里图片不显示是因为如果是正常写的html代码会vue-loader会进行自动寻址。
如果想正常显示可以这么写:

import { createApp, defineComponent, h } from 'vue'
// import App from './App.vue'
import router from './router'
import store from './store'
import HelloWorld from './components/HelloWorld.vue'

const img = require('./assets/logo.png') //eslint-disable-line

const App = defineComponent({
  render() {
    return h('div', { id: 'app' }, [
      h('img', { alt: 'Vue logo', src: img }),
      h(HelloWorld, {
        msg: '欢迎来到Vue+TS 的应用现场',
        age: 12,
      }),
    ])
  },
})

createApp(App).use(store).use(router).mount('#app')

image.png

setup

setup自带参数(props,{slots,attrs,emit})
attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用。

使用JSX开发vue3组件

JSX目前有2个解决方案,推荐vueComponent/jsx

声明使用,需要修改babel.config.js

module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'],
  plugins: ['@vue/babel-plugin-jsx'], //这样配置jsx就可以用了
}

修改src/main.js:

import { createApp, defineComponent } from 'vue'
// import App from './App.vue'
import router from './router'
import store from './store'
import HelloWorld from './components/HelloWorld.vue'
import App from './App'

createApp(App).use(store).use(router).mount('#app')

新增src\App.tsx:

import { ref, reactive, defineComponent } from 'vue'
const img = require('./assets/logo.png') //eslint-disable-line
export default defineComponent({
  setup() {
    const state = reactive({ name: 'jokcy' })

    const numberRef = ref(1)

    setInterval(() => {
      state.name + -1
      numberRef.value += 1
    }, 1000)

    return () => {
      const number = numberRef.value
      return (
        <div id="app">
          <img src={img} alt="Vue Logo" />
          <p>{state.name + number}</p>
        </div>
      )
    }
  },
})

运行:

image.png

JSX还有一个好处就是在编译阶段就防止一些参数问题。比如如下代码:
修改src\components\HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h1>{{ age }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String,
    age: { type: Number, required: true },
  },
})
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

修改src\App.tsx

import { ref, reactive, defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
const img = require('./assets/logo.png') //eslint-disable-line
export default defineComponent({
  setup() {
    const state = reactive({ name: 'jokcy' })

    const numberRef = ref(1)

    setInterval(() => {
      state.name + -1
      numberRef.value += 1
    }, 1000)

    return () => {
      const number = numberRef.value
      return (
        <div id="app">
          <img src={img} alt="Vue Logo" />
          <p>{state.name + number}</p>
          <HelloWorld age={24} />
        </div>
      )
    }
  },
})

如果age缺失,编译就会提示缺少必备参数。
包括传字符串也会报错:


image.png

所以用TSX文件比vue文件要好的地方。

还可以复用很复杂的业务逻辑:

import { ref, reactive, defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
const img = require('./assets/logo.png') //eslint-disable-line

// function renderHelloWorld(num: number) {
//   return <HelloWorld age={num} />
// }
const renderHelloWorld = (num: number) => {
  return <HelloWorld age={num} />
}

export default defineComponent({
  setup() {
    const state = reactive({ name: 'jokcy' })

    const numberRef = ref(1)

    setInterval(() => {
      state.name + -1
      numberRef.value += 1
    }, 1000)

    return () => {
      const number = numberRef.value
      return (
        <div id="app">
          <img src={img} alt="Vue Logo" />
          <p>{state.name + number}</p>
          {renderHelloWorld(12)}
        </div>
      )
    }
  },
})

2种写法都可以。
而且还可以在html代码中使用v-model等:

import { ref, reactive, defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
const img = require('./assets/logo.png') //eslint-disable-line

// function renderHelloWorld(num: number) {
//   return <HelloWorld age={num} />
// }
const renderHelloWorld = (num: number) => {
  return <HelloWorld age={num} />
}

export default defineComponent({
  setup() {
    const state = reactive({ name: 'jokcy' })

    const numberRef = ref(1)

    setInterval(() => {
      state.name += 1
      numberRef.value += 1
    }, 1000)

    return () => {
      const number = numberRef.value
      return (
        <div id="app">
          <img src={img} alt="Vue Logo" />
          <p>{state.name + number}</p>
          <input type="text" v-model={state.name} />
          {renderHelloWorld(12)}
        </div>
      )
    }
  },
})

其实我们相当于把每个例如div标签看成一个个h函数即可。
当然可以通过input修改上面显示的值:


export default defineComponent({
  setup() {
    const state = reactive({ name: 'jokcy' })

    const numberRef = ref(1)

    // setInterval(() => {
    //   state.name += 1
    //   numberRef.value += 1
    // }, 1000)

    return () => {
      const number = numberRef.value
      return (
        <div id="app">
          <img src={img} alt="Vue Logo" />
          <p>{state.name + number}</p>
          <input type="text" v-model={state.name} />
          {renderHelloWorld(12)}
        </div>
      )
    }
  },
})
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 前言 2020年初,vue3 发布了第一个版本,在随后的时间内,vue-next 一直保持着快速的更新,直到去年的...
    jad_design阅读 1,349评论 0 0
  • 关于vue3与jsx(tsx)写法自己了解哦,也可以看博主之前的博客。 css带有全局性,当我们的项目复杂到一定程...
    超人鸭阅读 21,874评论 16 17
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,467评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,041评论 0 4