背景
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的依赖如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | { "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
1 2 3 | npm i -g @vue/cli OR yarn global add @vue/cli |
创建项目
1 | vue create vue-cli |
选择配置
1 | yarn serve |
- yarn安装antd-design-vue
1 | yarn add ant-design-vue@next -S |
引入antd-Vue
1 2 3 4 5 6 7 8 9 10 11 | 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获取
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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导入,页面直接调用
1 | <Button type="danger">这是一个按钮</Button> |
引入按钮组件
改造原有HOME页面
原有Home组建的改造也和React写法一致,这里必须要先申明图片declare,不然要报错的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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文件
1 2 3 4 5 6 7 8 | // images.d.ts declare module '*.svg' declare module '*.png' declare module '*.jpg' declare module '*.jpeg' declare module '*.gif' declare module '*.bmp' declare module '*.tiff' |
重新打包,图片加载正常
这里有一个问题,比如这样的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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
既然它成为了属性,那么就把他用属性的方式取出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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方法,只是写法和之前略有区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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:计算属性
1 2 3 | // src/context/button.ts import { provide, ref, Ref, inject, computed } from 'vue' import { getTestApi } from '@/api/testApi' |
定义interface
1 2 3 4 5 6 | // src/context/button.ts interface ListContext { count: Ref<number>, count2: Ref<number>, changeCount: (data: number) => void } |
provide方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 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方法:
1 2 3 4 5 6 7 8 | // 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 统一将他们暴露出去。
1 2 3 4 5 6 7 8 | // src/context/index.ts import { buttonProvide, buttonInject } from './button' console.log("buttonInject", buttonInject) export { buttonInject } export const useProvider = () => { buttonProvide() } |
现在就可以使用了,这里必须先将provide挂载到某个你需要共用的地方,可以是某几个组件的父页面,也可以是APP.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 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> ) } }) |
至于调用就下面这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 谢谢