前情提要
在一个阳光明媚的下午,公司老板尤总在网上冲浪,一个不小心发现了一篇团队前端技术栈不统一导致公司倒闭
的推文。随即立刻召集全部开发人员,进行了一场关于前端技术栈的研讨会。
老板:最近公司项目越来越多,我想了解一下,我们现在前端项目都在用哪些技术栈啊。
前端负责人:目前我们这边的项目,主要是vue和react这俩种,目前60%的项目是react,40%的项目是vue,分别由同事A和同事B负责。
老板:那为什么我们这边技术栈没有统一呢,这2个框架有什么区别呢?
前端负责人:目前我们这边核心项目,为保证项目的可扩展和灵活性使用的是react。但是像一些后台为了保障快速交付使用的vue进行开发。当然,目前技术栈没有保持统一,主要也是有部分历史遗漏问题导致的,我们后面也会重点关注下这个问题。A和B,你们分别说下react和vue的优缺点吧。
同事A,一位热衷于React的年轻程序员,立刻举手发言:"尤总,我认为React是目前最优秀的前端框架。它由Facebook维护,社区活跃,生态丰富。React的组件化开发让代码更加模块化,易于维护和扩展。而且,React Native的出现,让我们可以一套代码搞定Web和移动端,大大提高了开发效率。
同事B,一位Vue的忠实粉丝,不甘示弱地反驳道:"尤总,我不同意A的观点。Vue才是最适合我们的前端技术栈。首先,Vue的学习曲线相对平缓,上手速度快,有利于团队成员快速掌握。其次,Vue的文档齐全,中文支持好,对我们国内的开发者非常友好。最重要的是,Vue的双向数据绑定让状态管理更加简单,开发效率更高。"
同事A: React的虚拟DOM技术,fiber架构,让性能得到了极大提升,你们Vue能比吗?
同事B: Vue的性能也不差,而且我们团队更注重开发体验,Vue在这方面完胜React!
双方进行了,长达半个小时的激烈争论.....
同事A: 看看你们那个vue3的hook,怎么这么像我们react这一套呢?网上都说vue和react越来越像。
同事B: 我去 ** 的,那能一样么,我们只是借鉴了设计思想,但是原理不同。你看看你们那个社区生态,再看看现在国内除了大公司,有几个用react的。
同事B: 我看你是啥也不懂,你打开招聘软件,看看有几个招react的,后面为了公司发展,总不能专门招react的。
同事A: 菜就多练,那不就是现在培训班都是先教vue么,啥也不懂,就不能多学学啊。
旁白: B拿起了刚领的M3 MacBookPro朝着A就扔过去了,A也示弱,上去就进行了一套5连鞭。双方甚至进行了道具赛,一看就是有备而来。
这时,同事A一个不小心,打到老板身上了,会议室内瞬间安静了。
老板:看样子大家都很有本事啊,那我看你们不是很符合公司文化,去领毕业证书吧,前端负责人留一下。
老板:我怎么看现在国内都是vue多啊,而且react一个框架挖下很多艰深复杂的坑,然后不去填这些坑,而是靠文档去解释如何绕开这些坑。我看这个框架不行,要不然公司后面都用vue,旧项目react重构成vue吧,1周完成。
前端负责人: ???
前端负责人: **,十几个项目 , 给你二周ni自己搞吧。
突然公司的前端团队就解散了,于是老板招了你来接手这个烂摊子。
react vs vue
这里先叠个甲,vue和react都是非常优秀的框架。
最早从jquery -> vue2 -> react16 -> react16+ -> vue3,基本上这些框架你都用过,理论上对于大部分项目并不会遇到框架层级的性能瓶颈,开发都一样。技术选型的话,主要还是看团队内对应的熟练程度,主要是保证快速交付。项目稳定和保证扩展,大部分是和开发人员有很大关系,很难赖到框架选择上。如果非说有差异的话,react的jsx可能会更灵活,而vue的模板和封装的部分api非常好用。
这里主要讨论一些vue和react一些语法层次的区别,react开发如何快速转换成vue开发。
项目搭建
目前不管vue和react项目,你这里都会推荐使用vite,vite的效率真的是比webpack快太多了。
pnpm create vite
状态管理
react
react项目现在推荐使用 zustand,api比以前的redux
简洁很多
js
// 定义
import { create } from 'zustand'
interface globalState {
token: string
setToken: (token: globalState['token']) => void,
}
const useGloBalStore = create<globalState>((set) => ({
token: '',
setToken: (value) => {
set(() => ({
token: value
}))
},
}))
export default useGloBalStore
js
// 调用
const { token,setToken } = useGloBalStore.getState()
console.log(token,'token')
setToken('xxx')
vue
vue的状态管理工具还是推荐 Pinia
js
// 定义
import { defineStore } from 'pinia'
export const useUserStore = defineStore({
id: 'user',
state: () => ({
token: '',
}),
actions: {
setToken(token) {
this.$patch({
token,
})
},
},
})
但是这里是要把react项目快速改成vue,那状态管理可以使用 zustand-vue, 这样我们代码就一点也不需要进行调整。
ui库
react项目,你们这边使用的是antd;vue项目,使用的是element plus。对于ui库,可以全局替换下引入和使用,例如:
模板渲染
react
react使用的是jsx结构,需要定义一个函数,return返回需要渲染的内容即可
js
// app.tsx
const App =()=>{
return <div>app</div>
}
export default App
vue
vue虽然现在可以支持jsx,但是vue最大特点还是它的模板,不能浪费它的优势。
js
// app.vue
<script setup></script>
<template>
<div>app</div>
</template>
<style scoped lang="less"></style>
路由
react使用react-router,vue使用vue-router,路由的注册方式差别比较大。 主要包含以下几点:路由配置参数,异步导入参数,路由注册,动态子路由渲染容器等
路由配置和异步导入
js
// react - 路由配置
const Layout = lazy(() => import('@/layout')) // 异步导入
const Login = lazy(() => import('@/pages/login'))
const Home = lazy(() => import('@/pages/home'))
const Project = lazy(() => import('@/pages/project'))
const getRoutes = () => {
return [
{
path: "/",
element: <Layout />, // 组件
children: [
{
path: MODULE_TYPE_HOME,
element: <Home />,
},
{
path: MODULE_TYPE_PROJECT,
element: <Project />,
},
],
},
{ path: LOGIN_PATH, element: <Login /> },
]
}
export default getRoutes
js
// vue - 路由配置
const routes = [
{
path: '/',
component: () => import('@/layout/index.vue'), // 组件+异步导入
redirect: "/home",
children: [
{
path: MODULE_TYPE_HOME,
component: () => import('@/pages/home/index.vue'),
},
{
path: MODULE_TYPE_PROJECT,
component: () => import('@/pages/project/index.vue'),
},
]
},
{
path: '/login',
component: () => import('@/pages/login/index.vue')
},
]
export default routes
路由注册
js
// react
import { HashRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root')!).render(
<HashRouter>
<App/>
</HashRouter>
)
// app.tsx
import { useRoutes } from 'react-router-dom'
function App() {
const element = useRoutes(getRoutes());
return <React.Suspense fallback={<LoadingPage />}>
{element}
</React.Suspense>;
}
export default App
js
// vue
import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./constant";
const router = createRouter({
history: createWebHashHistory(),
routes,
})
app.use(router)
// app.vue
<template>
<RouterView />
</template>
子路由容器渲染
react使用Outlet,vue使用RouterView当做路由的渲染容器。
js
// react
<Layout>
<Content className={styles["outlet-container"]}>
<Suspense fallback={<div> </div>}>
<Outlet />
</Suspense>
</Content>
</Layout>
js
// vue
<el-container class="right">
<RouterView />
</el-container>
生命周期
这里只是举2个常用的生命周期吧,初始化和卸载。
初始化
js
// react
import { useEffect } from 'react'
useEffect(() => {
console.log("组件初始化")
}, [])
js
// vue
import { onMounted } from 'vue'
onMounted(() => {
console.log("组件初始化")
})
卸载
js
// react
import { useEffect } from 'react'
useEffect(() => {
return ()=>{
console.log("组件卸载!")
}
}, [])
js
// vue
import { onMounted } from 'vue'
onMounted(() => {
console.log("组件卸载!")
})
组件相关
组件结构
vue和react,在组件结构还是有很大差距的,但是其实也能发现很多相似的地方。
vue单文件组件分为三个模块,script模块处理js逻辑,template 处理html模板渲染,style处理css样式。
而react组件本质上是一个函数,return出去的内容就是我们的html模板渲染,函数内部就是我们的js逻辑,css样式我们经常放到一个单独的文件进行处理。
js
// vue
<script setup>
// js
// 这是业务代码执行区域
// 提供函数或者变量给模板使用
</script>
<template>
<!-- html -->
<!-- 这是html模块渲染区域 -->
<div>这是div</div>
</template>
<style scoped lang="less">
// css
// 在这里可以对当前组件的html进行样式编写
</style>
js
// react
// css
// 单独引入一个css文件来维护当前组件的样式
import styles from './index.module.less'
const Test =()=>{
/**js**/
const a =1
const log =()=>{
console.log(a)
}
/**js**/
/**html**/
return <div className={styles.test} >
111
</div>
/**html**/
}
export default Test
组件引入
组件引入,vue3和react就非常相似了,但是vue2需要在components里注册一下,vue3引入组件的时候需要带上.vue后缀。 vue
js
// react
import Login from './login.vue'
const Test =()=>{
return <Login></Login>
}
js
// vue
<script setup>
import Login from './Login.vue'
</script>
<template>
<Login />
</template>
父子组件传参
父子组件传递的参数类型一般分为2种,函数和属性。
vue传递属性和函数的方式还是有点不太一样。传递参数,:属性名
传递变量,直接属性名
传递字符串,@属性名
传递事件。接收参数,defineProps定义父组件传递的属性,defineEmits定义父组件传递的函数。
js
// 父组件
<SearchButton
:is-show-ai-tip="true" // 变量名前面添加: ,表示是个变量
autofocus // 可简写,传递值为true
placeholder="提示文本" // 参数为常量,没有:
// 使用@参数名,标识传递的是函数
@search="
(e) => {
condition = {
...condition,
keyword: e,
};
}
"
/>
// 子组件
<script setup>
const inputValue = ref("");
defineProps({
autofocus: Boolean,
isSupportImg: Boolean,
isSupportVideo: Boolean,
isShowAiTip: Boolean,
});
const emit = defineEmits(["search"]);
</script>
<template>
<div class="search-button">
<el-input
autofocus
v-model="inputValue"
style="width: 600px"
class="input-with-select"
v-bind="$attrs" // 透传defineProps未声明的参数
v-on:keydown.enter="emit('search', inputValue)"
>
<template #append>
// 通过emit调用父组件的方法,第一个参数是函数名,第二个参数是变量
<span class="search" @click="emit('search', inputValue)"> 搜索 </span>
</template>
</el-input>
</div>
</template>
</style>
而react的参数传递就比较简单了,属性和函数都是一样的,属性名=属性或者函数
,子组件接收到属性后直接进行解构或者props调用即可。
js
// 父组件
<SearchButton
isShowAiTip
placeholder="提示文本"
onSearch={(e) => {
setCondition({
...condition,
keyword: e
})
}} />
// 子组件
import { Input } from 'antd'
import styles from './index.module.less'
import { FC, useEffect, useRef } from 'react'
import { SearchProps } from 'antd/lib/input'
import useIsInViewport from '@/hooks/useIsInViewport'
const { Search } = Input
type SearchButtonProps = {
autoFocus?: boolean,
isSupportImg?: boolean,
isSupportVideo?: boolean,
isShowAiTip?: boolean
} & SearchProps
const SearchButton: FC<SearchButtonProps> = (
// 可通过解构函数参数props获取自己需要的参数
// 也可以通过props.autoFocus直接调用
{ autoFocus, isSupportImg, isSupportVideo, isShowAiTip, ...props }
) => {
const ref = useRef<any>(null)
const wrapperRef = useRef(null)
const isInViewPort = useIsInViewport(wrapperRef)
useEffect(() => {
useIsInViewport
if (autoFocus) {
ref?.current?.focus({
cursor: 'end',
})
}
}, [isInViewPort])
return <div className={styles['search-button']} ref={wrapperRef}>
<Search
className={styles.search}
ref={ref}
enterButton="搜索"
size="large"
onPressEnter={(e: any) => {
// props直接取未解构的值
props.onSearch && props.onSearch(e.target.value)
}}
{...props} // 透传顶部未解构属性和函数
/>
</div>
}
export default SearchButton
动态组件
动态组件可以让我们的代码非常灵活,提高扩展性,这一点上react比vue要灵活很多。react由于jsx的特性,一个变量可以是一个组件,也可以是部分html代码。而vue则需要通过引入组件或h
方法定义组件+ <component:is=组件
的方法进行组合使用。
下面以封装表格,支持自定义操作类为例
js
// vue 定义操作列
columns.push({
title: "操作",
dataIndex: "operation",
width: 80,
align: "center",
render: (text, record) =>
// 通过h创建一个操作组件
h(
"span",
{
style: {
cursor: "pointer",
color: "#4D94FC",
fontSize: "18px",
},
onClick: () =>
console.log('操作列被点击了')
},
h("img", {
src: "/components/row-oper.png",
class: "oper-icon",
})
),
});
// vue table渲染操作列
<script setup>
defineProps({
tableData: Array,
tableColumns: Array,
});
</script>
<template>
<el-table
class="wl-table"
:data="tableData"
row-class-name="list-row"
>
<el-table-column
v-for="(item, index) in tableColumns"
:key="item.dataIndex"
:prop="item.dataIndex"
:label="item.title"
:width="item.width"
>
<template v-if="item.render" #default="scope">
// 通过component is 调用自定义render方法或者组件
<component
:is="item.render(scope.row[item.dataIndex], scope.row, index, item)"
/>
</template>
</el-table-column>
</el-table>
</template>
</style>
下面看一下react的,就能感受到区别了。
js
// react定义操作列
columns.push({
title: "操作",
dataIndex: "operation",
width: 80,
align: "center",
// 直接返回jsx
render: (_, record: any) => (
<span
style={{ cursor: "pointer", color: "#4D94FC", fontSize: "18px" }}
onClick={() => getJumpUrl(record)}
>
<RightCircleOutlined />
</span>
),
})
// react封装table组件
import { Table } from "antd"
import { TableProps } from "antd/lib"
import { FC, useEffect, useState } from "react"
import { ColumnItemType } from "@/type"
import styles from './index.module.less'
import classNames from "classnames"
type WlTablePropsType = {
className?:string
titleDraggable?: boolean // 表头是否支持拖拽
} & TableProps<any>
const WlTable: FC<WlTablePropsType> = ({ titleDraggable,className, ...props }) => {
const [customColumns, setCustomColumns] = useState<any>([])
const transferColumns = (columns: ColumnItemType[]) => {
// 直接调用对应的render函数,实现自定义
return columns.map((item: ColumnItemType) => {
return {
...item,
render: item.render ? item.render : (text: any) => {
return text
}
}
})
}
useEffect(() => {
setCustomColumns(props.columns)
}, [props.columns])
return (
<Table
{...props}
className={classNames(styles['wl-table'],className)}
columns={transferColumns(customColumns)}
/>
)
}
export default WlTable
子组件
在父组件中编写子组件的内容, 这个和上面的动态组件有点类似。react可以通过children属性或者像上面动态组件一样直接传递一个render函数给子组件调用。vue也有对应的slot插槽
可以实现该功能。
react
js
// react 父组件
const Test = () => {
return (
<PageWrapper>
1
</PageWrapper>
)
}
export default Test
// react子组件
import { FC } from "react"
import styles from './index.module.less'
type PageWrapperProps = {
children: React.ReactNode
}
const PageWrapper:FC<PageWrapperProps> =({children})=>{
return (
<div className={styles["page-wrapper"]}>{children}</div>
)
}
export default PageWrapper
vue
js
// vue 父组件
<script setup></script>
<template>
<PageWrapper>
1
</PageWrapper>
</template>
// vue子组件
<template>
<div class="page-wrapper">
<slot></slot>
</div>
</template>
对于父组件调用子组件的内部的变量,react可以通过自定义render函数传递,而vue则可以通过插槽的高阶用法实现,大家感兴趣可以看下文档,这里就不赘述了。
响应式
在响应式方面,react的api就比vue少很多。react基本上一个useState
就可以解决。vue3则提供很多比如ref
,reactive
等。
下面以实现一个点击+1的案例,感受下两者的区别:
react:
js
import { useState } from "react"
const Test =()=>{
const [count,setCount]=useState(1)
return (
<div>
<button onClick={()=>setCount(count+1)}>按钮({count})</button>
</div>
)
}
vue
js
<script setup>
const count = ref(0);
</script>
<template>
<div>
<button @click="count++">按钮{{ count }}</button>
</div>
</template>
这个从写法层面,似乎vue会更简洁一些,但是vue提供的各种响应式api大家要注意使用的场景,比如reactive
只能作用于对象,并且解构后会失去响应式等问题。vue的api有的时候真的很好用,但是我感觉有些设计确实比较奇怪,比如用ref时,在script中需要.value,但是在template中不需要,感觉这些还是保持一致的话会更合适些。
计算属性和监听
计算属性和监听其实是非常类似的,vue针对这2个提供了2个不同的apiwatch
和computed
,而react只有一个useEffect
vue
js
// watch
// 监听第一个响应式参数的变化,更新后自动执行一个函数,无返回值
const count =ref(0)
watch(
count,
(newVal) => {
console.log('count更新了',newVal)
},
{
immediate: true,
deep: true,
}
);
// computed
// 监听函数内的响应式变量,当响应式变量有更新后,自动执行该函数,并返回生成一个新的响应式变量
let count = ref([]);
const doubleCount = computed(() => {
return count.value * 2
})
react如果要实现coumputed和watch,基本上就只用watchEffect
就可以了。
js
const [count, setCount] = useState(1)
const [dobuleCount,setDoubleCount]=useState(count)
// 更新后执行函数回调
const watch = (newVal) => {
console.log(newVal)
}
// 虽然没办法像vue一样直接产生新的ref,但是可以对state重新赋值
const computed = (newVal) => {
setDoubleCount(newVal*2))
}
useEffect(() => {
watch(count)
computed(count)
}, [count])
写法差异
最近在看了一下python,感觉python和js真的好像啊,学习成本比较低。那vue和react都是前端的框架,那学习成本主要体现在哪些方面呢,一个是框架提供的api不同,还有一些写法差异。
插值
vue的插值是{{name}}
, react的插值是{name}
。
class
react因为使用的是jsx语法,其中class是关键字,所以使用className代替,而vue还是使用的class。 当我们class为值为多个时,不管vue还是react我们都可以使用模板字符串
,但是vue需要在前端加上个:
表示是个变量,react在定义多个class的时候,为了方便处理逻辑,会用classNames第三方库。
vue
js
// 同时定义了2个class row 和 check
<div :class="`row check`"></div>
react
js
// 同时定义了2个class row 和 check
<div className={classNames(styles.row, styles.check)}></div>
条件渲染
vue的条件渲染是通过v-if
的指令,而react也还是用插值。
vue
js
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
react
js
<>
{awesome && <h1 >Vue is awesome!</h1>}
{!awesome && <h1 >Oh no 😢</h1>}
</>
列表渲染
vue的列表渲染是通过v-for
指令,而react也还是用插值。
vue
js
<li v-for="(item, index) in items" :key="item.message">
{{ index }} - {{ item.message }}
</li>
react
js
{
items.map((item,index)=>{
return <li key={item.message}>
{ index } - { item.message }
</li>
})
}
事件处理
vue的事件处理使用v-on
指令,或者用@
简写,比如@click
而react大多数情况使用on事件
,比如onClick
第三库或者表单值
像react为例,antd组件的大部分组件参数都是value,而换成vue 的elementPlus,我们要用v-model代替,面以input举例:
vue
js
const name =ref("")
<el-input v-model="name"></el-input>
react
js
const [name,setName] =useState("")
<Input value={name} onChange={e=>setName(e.target.value)} />
如何高效实现vue代码和react代码转换
如果说要把react的代码,转成vue的,这些内容都要调整
- 复制jsx到template
- className修改成class
- 代码中部分双引号改成单引号
- 插值判断改成v-if
- 遍历改成v-for
- style={{}} 要改成:styles='{}'
- 属性传递前面要加
:
- useState相关初始化和更新要改成ref
- 单独的css文件放到标签下
- global重写样式改成:deep
- 算了,改不动了,毁灭吧 ....
其实jsx和template的内容,我们可以借用ai帮我们改,非常省力。
我这里使用的是通义灵码,看疗效:
react源码如下:
js
import SearchButton from "@/components/searchButton"
import Table from "./table"
import LabelTitle from "@/components/labelTitle"
import { FC, useEffect, useState } from "react"
import { SearchListModelItemType, TabModuleItemType } from "./type"
import Summary, { SummaryDataType } from "./summary"
import Tabs from "./tabs"
import { httpPost } from "http"
import { SearchTableParamsType, TableResultType } from "@/type"
import { DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE } from "@/constant"
type SearchListPropsType = {
tab?: {
options: TabModuleItemType[]
changeCallback?: (key: string) => void
}
summary?: SearchListModelItemType,
table?: SearchListModelItemType,
}
const DEFAULT_CONDITION = {
keyword: '',
pageNum: DEFAULT_PAGE_NUM,
pageSize: DEFAULT_PAGE_SIZE
}
const SearchList: FC<SearchListPropsType> = ({ summary, table, tab }) => {
const { api: summarySearchApi, key: summaryDataKey, label: summaryLabel, columns: summaryColumns } = summary || {}
const { api: tableSearchApi, label: tableLabel, columns: tableColumns, labelExtraRender, singlePointUrl, extraParams: tableExtraParams } = table || {}
const [tableLoading, setTalbeLoading] = useState(false)
const [summaryLoading, setSummaryLoading] = useState(false)
const [tableData, setTableData] = useState<TableResultType<any>>({})
const [summaryData, setSummaryData] = useState<SummaryDataType>({})
const [condition, setCondition] = useState<SearchTableParamsType>({
...DEFAULT_CONDITION,
...tableExtraParams||{}
})
const getSummaryData = async () => {
setSummaryLoading(true)
try {
const result = await httpPost(summarySearchApi as string, condition)
setSummaryData(result)
} catch (err) {
setSummaryData({})
} finally {
setSummaryLoading(false)
}
}
const getTableData = async () => {
setTalbeLoading(true)
setSummaryLoading(true)
try {
const result = await httpPost(tableSearchApi as string, condition)
if (summaryDataKey) {
setSummaryData(result[summaryDataKey])
}
setTableData(result)
} catch (err) {
setTableData({})
} finally {
setTalbeLoading(false)
setSummaryLoading(false)
}
}
useEffect(() => {
if (summarySearchApi) {
getSummaryData()
}
if (tableSearchApi) {
getTableData()
}
}, [condition])
useEffect(() => {
setTableData({})
setCondition({
...condition,
...DEFAULT_CONDITION
})
getTableData()
}, [tableSearchApi])
useEffect(() => {
if (tab?.options) {
setCondition({
...condition,
activeKey: tab.options[0].id
})
}
}, [])
return (
<div style={{ height: "100%", display: 'flex', flexDirection: "column" }}>
<SearchButton isShowAiTip
placeholder="请输入"
onSearch={(e) => {
setCondition({
...condition,
keyword: e
})
}} />
{tab && <Tabs
{...tab}
activeKey={condition.activeKey}
onChange={(e) => {
setCondition({
...condition,
activeKey: e
})
tab.changeCallback && tab.changeCallback(e)
}} />
}
{
summary &&
<>
{
summaryLabel && <LabelTitle title={summaryLabel} hasMarin />
}
{
summaryColumns && <Summary
{...summary}
loading={summaryLoading}
summaryData={summaryData}
condition={condition}
/>
}
</>
}
{
table && <>
{
tableLabel && <div style={{ display: "flex", justifyContent: "space-between" }}>
<LabelTitle title={tableLabel} hasMarin />
{labelExtraRender && labelExtraRender()}
</div>
}
{
tableColumns && <Table
loading={tableLoading}
singlePointUrl={singlePointUrl}
{...table} tableData={tableData} condition={condition} changeCondition={(params) => {
setCondition({
...condition,
...params
})
}} />
}
</>
}
</div>
)
}
export default SearchList
通义灵码启动:
ai生成代码如下:
js
<template>
<div :style="{ height: '100%', display: 'flex', flexDirection: 'column' }">
<SearchButton
:is-show-ai-tip="true"
placeholder="请输入"
@search="handleSearch"
/>
<Tabs
v-if="tab"
:active-key="condition.activeKey"
:options="tab.options"
@change="handleTabChange"
/>
<template v-if="summary">
<LabelTitle v-if="summaryLabel" :title="summaryLabel" :has-marin="true" />
<Summary
v-if="summaryColumns"
:loading="summaryLoading"
:summary-data="summaryData"
:condition="condition"
:api="summary.api"
:key="summary.key"
:columns="summary.columns"
/>
</template>
<template v-if="table">
<div
v-if="tableLabel"
:style="{ display: 'flex', justifyContent: 'space-between' }"
>
<LabelTitle :title="tableLabel" :has-marin="true" />
<slot name="label-extra-render" />
</div>
<Table
v-if="tableColumns"
:loading="tableLoading"
:single-point-url="table.singlePointUrl"
:api="table.api"
:label="table.label"
:columns="table.columns"
:table-data="tableData"
:condition="condition"
@change-condition="handleChangeCondition"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import SearchButton from "@/components/searchButton.vue";
import Table from "./table.vue";
import LabelTitle from "@/components/labelTitle.vue";
import Summary from "./summary.vue";
import Tabs from "./tabs.vue";
// Define props
const props = defineProps({
tab: {
type: Object,
default: () => ({}),
},
summary: {
type: Object,
default: () => ({}),
},
table: {
type: Object,
default: () => ({}),
},
});
// Define state
const tableLoading = ref(false);
const summaryLoading = ref(false);
const tableData = ref({});
const summaryData = ref({});
const condition = ref({
...DEFAULT_CONDITION,
...props.table.extraParams,
});
// Define methods
const handleSearch = (e: any) => {
condition.value.keyword = e;
};
const handleTabChange = (e: any) => {
condition.value.activeKey = e;
props.tab.changeCallback && props.tab.changeCallback(e);
};
const handleChangeCondition = (params: any) => {
Object.assign(condition.value, params);
};
// Fetch data
const getSummaryData = async () => {
summaryLoading.value = true;
try {
const result = await httpPost(props.summary.api as string, condition.value);
summaryData.value = result;
} catch (err) {
summaryData.value = {};
} finally {
summaryLoading.value = false;
}
};
const getTableData = async () => {
tableLoading.value = true;
try {
const result = await httpPost(props.table.api as string, condition.value);
if (props.summary.key) {
summaryData.value = result[props.summary.key];
}
tableData.value = result;
} catch (err) {
tableData.value = {};
} finally {
tableLoading.value = false;
}
};
// Watch conditions and fetch data
watch(
condition,
() => {
if (props.summary.api) {
getSummaryData();
}
if (props.table.api) {
getTableData();
}
},
{ deep: true }
);
onMounted(() => {
if (props.tab.options) {
condition.value.activeKey = props.tab.options[0].id;
}
});
</script>
大家结果结果怎么样,转换完,是否可以直接使用?
答案是肯定不行的,举了一些比较明显的问题如下:
- 函数组件外部定义的变量丢失
- react代码中对props的数据进行解构,但是转成vue3后丢失
- 一些引入的变量没有引入
虽然说存在一些问题,但是template的一些语法已经帮我们转义过来了,整体框架也没问题,我们可以在这ai的基础上进行加工。
js
<script setup lang="ts">
import { PropType } from "vue";
import { DEFAULT_CONDITION } from "./constant";
import { httpPost } from "http";
import Tabs from "./tabs.vue";
import Summary from "./summary.vue";
import Table from "./table.vue";
const props = defineProps({
searchPlaceholder: String,
tab: Object as PropType<any>,
summary: Object as PropType<any>,
table: Object as PropType<any>,
});
const { tab, summary, table } = toRefs(props);
const {
api: summarySearchApi,
key: summaryDataKey,
label: summaryLabel,
columns: summaryColumns,
} = summary?.value || {};
const {
api: tableSearchApi,
label: tableLabel,
columns: tableColumns,
labelExtraRender,
extraParams: tableExtraParams,
} = table?.value || {};
const { changeCallback: tabChangeCallback } = tab?.value || {};
const changeTab = (e: string) => {
tabChangeCallback?.(e);
};
const summaryLoading = ref(false);
const summaryData = ref({});
const tableLoading = ref(false);
const tableData = ref({});
const condition = ref({
...DEFAULT_CONDITION,
...(tableExtraParams || {}),
});
const getSummaryData = async () => {
summaryLoading.value = true;
try {
const result = await httpPost(summarySearchApi as string, condition.value);
summaryData.value = result;
} catch (err) {
summaryData.value = {};
} finally {
summaryLoading.value = false;
}
};
const getTableData = async () => {
tableLoading.value = true;
if (summaryDataKey) {
summaryLoading.value = true;
}
try {
const result = await httpPost(tableSearchApi as string, condition.value, {
errorHandler: (error) => {
console.log("getTableData", error);
},
});
if (summaryDataKey) {
summaryData.value = result[summaryDataKey];
}
tableData.value = result;
} catch (err) {
tableData.value = {};
} finally {
tableLoading.value = false;
if (summaryDataKey) {
summaryLoading.value = false;
}
}
};
watch(
condition,
() => {
if (summarySearchApi) {
getSummaryData();
}
if (tableSearchApi) {
getTableData();
}
},
{
immediate: true,
deep: true,
}
);
watch(
() => table?.value.extraParams,
(newVal) => {
condition.value = {
...condition.value,
...newVal,
};
}
);
</script>
<template>
<div :style="{ height: '100%', display: 'flex', flexDirection: 'column' }">
<SearchButton
:is-show-ai-tip="true"
autofocus
:placeholder="searchPlaceholder "
@search="
(e) => {
condition = {
...condition,
keyword: e,
};
}
"
/>
<template v-if="tab">
<Tabs
v-bind="tab"
:active-key="condition.activeKey"
@onChange="changeTab"
/>
</template>
<template v-if="summary">
<template v-if="summaryLabel">
<LabelTitle :title="summaryLabel" :has-marin="true" />
</template>
<template v-if="summaryColumns">
<Summary
v-bind="summary"
:loading="summaryLoading"
:summary-data="summaryData"
:condition="condition"
/>
</template>
</template>
<template v-if="table">
<template v-if="tableLabel">
<div :style="{ display: 'flex', justifyContent: 'space-between' }">
<LabelTitle :title="tableLabel" :has-marin="true" />
<div v-if="labelExtraRender">
<component :is="labelExtraRender()" />
</div>
</div>
</template>
<template v-if="tableColumns">
<Table
:loading="tableLoading"
v-bind="table"
:table-data="tableData"
:condition="condition"
@change-condition="(params:any)=>{
condition={
...condition,
...params
}
}"
/>
</template>
</template>
</div>
</template>
总结
做前端开发也5年了,慢慢的感觉其实不管vue还是react,对于我们来说都是实现业务的工具,大可不必非要把vue和react争论个第一和第二,要真说哪个好的话,我看很多后起框架比如solid的设计理念,比如vue和react都好。其实每个框架大家都可以了解一下,基本上深入了解一个后,再接触其他的,那学习成本就很低了。做技术的同时,也一定要考虑公司的情况,结合团队进行技术选型和探索实践。** **
如果真的有公司要求把已有的项目,react转成vue或者vue改成react。
本文纯属虚构,如有雷同,那恭喜有缘人!!!