从0搭建Vue3.0+TypeScript+antd-Vue+axios+JSX前端框架

背景

Vue3.0出来了很长一段时间了,Vue3.0对于TypeScript的支持也有了质的提升,因为自己现在用React稍微多一些,所以也想在Vue中加入JSX,试试利用一下Vue3.0的新特性搭建一个基础框架。
所需主要库包括:

  • UI antd-Vue 2.0
  • TypeScript
  • Axios
  • 状态管理采取的Provide、Inject的方案
  • JSX(TSX)
  • Vue-router

package.json的依赖如下:

{
  "name": "vue-cli",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "ant-design-vue": "^2.0.0-beta.9",
    "axios": "^0.20.0",
    "normalize.css": "^8.0.1",
    "core-js": "^3.6.5",
    "style-resources-loader": "^1.3.3",
    "vue": "^3.0.0-0",
    "vue-router": "^4.0.0-0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0-0",
    "postcss-import": "^12.0.1",
    "postcss-px-to-viewport": "^1.1.1",
    "postcss-url": "^8.0.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^8.0.2",
    "postcss-write-svg": "^3.0.1",
    "typescript": "~3.9.3"
  }
}

安装

  • yarn安装Vue + TypeScript
npm i -g @vue/cli 
OR
yarn global add @vue/cli

创建项目

vue create vue-cli
选择配置
yarn serve
  • yarn安装antd-design-vue
yarn add ant-design-vue@next -S

引入antd-Vue

import { createApp } from 'vue'
import { Button, message } from 'ant-design-vue';
import { App } from './App'
import router from './router'
import 'ant-design-vue/dist/antd.css';
import 'normalize.css'
const app = createApp(App)
app.use(Button)
app.use(router)
app.config.globalProperties.$message = message;
app.mount('#app')

目录结构

image.png

创建组件

这里稍微和React中的JSX不一样的地方就是,不是用children去取中间子元素,而是用slots获取

// compoents/Button.tsx
import { defineComponent } from 'vue';
import { Button } from 'ant-design-vue';
const ButtonCom = defineComponent({
    setup(props: {}, { slots }) {
        return () => (
            <Button type="primary">
                {slots.default && slots.default()}
            </Button>
        )
    }
})
export default ButtonCom;

引入组件

引入组件的方式和React一致,顶部import导入,页面直接调用

 <Button type="danger">这是一个按钮</Button>
引入按钮组件

改造原有HOME页面

原有Home组建的改造也和React写法一致,这里必须要先申明图片declare,不然要报错的。

// views/Home/index.tsx
import { defineComponent } from 'vue';
import HelloWorld from "@/components/HelloWorld/index";
import logo from '@/assets/logo.png'
interface HomeProps { }
const Home = defineComponent({
    setup(props: HomeProps) {
        return () => (
            <div class="home">
                <img alt="Vue logo" src={logo} />
                <HelloWorld />
            </div>
        )
    }
})
export default Home;

在src新建 images.d.ts文件

// images.d.ts
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'

重新打包,图片加载正常

这里有一个问题,比如这样的组件
//  components/Content/index.tsx
import { defineComponent, reactive, onMounted } from 'vue';

interface LabelProps {
    content: any;
}
const Label = defineComponent({
    setup(props: LabelProps) {
        onMounted(() => { console.log('mounted!'); });
        return () => {
            const { content } = props;
            return <span>{content}</span>;
        }
    }
})
export default Label
//调用
<Content content={props.msg}></Content>

按道理来说content会展示出来,而实际上查看却没有content内容,打开控制台会发现,content内容变成一个而属性跑到节点上去了,节点内部却没有任何内容。这个问题 尤大大也出来解释过https://github.com/vuejs/rfcs/pull/154,解决的方法我这里有两种。

image.png

  • attrs
    既然它成为了属性,那么就把他用属性的方式取出来
//  components/Content/index.tsx
import { defineComponent, reactive, onMounted } from 'vue';

interface LabelProps {
    content: any;
}
const Label = defineComponent({
    setup(props: LabelProps, { attrs }: any) {
        console.log(attrs)
        onMounted(() => { console.log('mounted!'); });
        return () => {
            const { content } = attrs;
            return <span>{content}</span>;
        }
    }
})
export default Label
image.png
  • props
    有人会觉得attrs会不优雅,就想用props,怎么办,就可以使用props方法,只是写法和之前略有区别:
//  components/Content/index.tsx
import { defineComponent, reactive, onMounted } from 'vue';

const Label = defineComponent({
    props: {
        content: String,
    },
    setup: (props) => {
        return () => (
            <p>{props.content}</p>
        )
    }
})
export default Label
image.png

现在就能正确拿到props内容,同时也不会有一个属性叫content;

状态管理

本来采取的是Vuex的方式,后来想着既然都使用了Vue3.0了何不用 provide和inject;于是这篇文章就改了;
在context文件夹下创建button.ts 主要用于button组件的状态管理:
provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。
ref:用于类似于Hooks的 useRef用于存储变量
Ref:是 ref的types
inject:一个字符串数组,或者是一个对象。
computed:计算属性

//  src/context/button.ts
import { provide, ref, Ref, inject, computed } from 'vue'
import { getTestApi } from '@/api/testApi'

定义interface

//  src/context/button.ts
interface ListContext {
    count: Ref<number>,
    count2: Ref<number>,
    changeCount: (data: number) => void
}

provide方法:

//  src/context/button.ts
// provide名称,推荐用Symbol
const listymbol = Symbol()
// 提供provide的函数
export const buttonProvide = () => {
    const count = ref<number>(0);

    // 计算属性
    const count2 = computed(() => {
        return count.value * 2
    })
   //在这里可以引入axios api做异步请求
    const changeCount = async function (data: number) {
         try {
            let res: any = await getTestApi("async")
         } catch (error) {
            console.log(error)
            count.value = count.value + data
            console.log(count.value)
         }
    }

    provide(listymbol, {
        count,
        count2,
        changeCount
    })
}

inject方法:

//  src/context/button.ts
export const buttonInject = () => {
    const listContext = inject<ListContext>(listymbol);
    if (!listContext) {
        throw new Error(`buttonInject must be used after buttonProvide`);
    }
    return listContext
};

这就是一个button组建的状态provide、inject方法就写好了,接下来需要另外写个index.ts 统一将他们暴露出去。

//  src/context/index.ts
import { buttonProvide, buttonInject } from './button'
console.log("buttonInject", buttonInject)

export { buttonInject }
export const useProvider = () => {
    buttonProvide()
}

现在就可以使用了,这里必须先将provide挂载到某个你需要共用的地方,可以是某几个组件的父页面,也可以是APP.tsx

// App.tsx
import { defineComponent } from 'vue';
import { useProvider } from '@/context/index'
import '@/assets/stylus/index.scss'
export const App = defineComponent({
  name: 'App',
  props: {
    content: String,
  },
  setup: (props) => {
    useProvider()
    return () => (
      <div>
        <div id="nav">
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link>
        </div>
        <router-view />
      </div>
    )
  }
})

至于调用就下面这样

import { defineComponent } from 'vue';
import { Button } from 'ant-design-vue';
import { buttonInject } from '@/context/index'     //引入入口index.ts
interface ButtonProps {
    type: any
}
const ButtonCom = defineComponent({
    setup(props: ButtonProps, { slots }) {
        const { changeCount, count, count2 } = buttonInject()        //获取到方法属性 就可以使用了
        const handleClick = () => {
            changeCount(1)
        };
        return () => (
            <Button type={props.type} onClick={handleClick}>
                {slots.default && slots.default()}count:{count.value}count2:{count2.value}   
            </Button>
        )
    }
})

export default ButtonCom;
chrome-capture (10).gif

项目gitHub地址:https://github.com/Benzic/vue3.0-typescript-antdVue-tsx
欢迎star 谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339