背景
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')
目录结构
创建组件
这里稍微和React中的JSX不一样的地方就是,不是用children去取中间子元素,而是用slots获取
// compoents/Button.tsx
import { defineComponent } from 'vue';
import { Button } from 'ant-design-vue';
const ButtonCom = defineComponent({
setup(props: {}, { slots }) {
return () => (
)
}
})
export default ButtonCom;
引入组件
引入组件的方式和React一致,顶部import导入,页面直接调用
改造原有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 () => (
)
}
})
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 {content};
}
}
})
export default Label
//调用
按道理来说content会展示出来,而实际上查看却没有content内容,打开控制台会发现,content内容变成一个而属性跑到节点上去了,节点内部却没有任何内容。这个问题 尤大大也出来解释过https://github.com/vuejs/rfcs/pull/154,解决的方法我这里有两种。
- 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 {content};
}
}
})
export default Label
- props
有人会觉得attrs会不优雅,就想用props,怎么办,就可以使用props方法,只是写法和之前略有区别:
// components/Content/index.tsx
import { defineComponent, reactive, onMounted } from 'vue';
const Label = defineComponent({
props: {
content: String,
},
setup: (props) => {
return () => (
{props.content}
)
}
})
export default Label
现在就能正确拿到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,
count2: Ref,
changeCount: (data: number) => void
}
provide方法:
// src/context/button.ts
// provide名称,推荐用Symbol
const listymbol = Symbol()
// 提供provide的函数
export const buttonProvide = () => {
const count = ref(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(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 () => (
)
}
})
至于调用就下面这样
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 () => (
)
}
})
export default ButtonCom;
项目gitHub地址:https://github.com/Benzic/vue3.0-typescript-antdVue-tsx
欢迎star 谢谢