老板的一句话,同事为了vue和react打起来了...

前情提要

在一个阳光明媚的下午,公司老板尤总在网上冲浪,一个不小心发现了一篇团队前端技术栈不统一导致公司倒闭的推文。随即立刻召集全部开发人员,进行了一场关于前端技术栈的研讨会。

老板:最近公司项目越来越多,我想了解一下,我们现在前端项目都在用哪些技术栈啊。

前端负责人:目前我们这边的项目,主要是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个不同的apiwatchcomputed,而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的,这些内容都要调整

  1. 复制jsx到template
  2. className修改成class
  3. 代码中部分双引号改成单引号
  4. 插值判断改成v-if
  5. 遍历改成v-for
  6. style={{}} 要改成:styles='{}'
  7. 属性传递前面要加:
  8. useState相关初始化和更新要改成ref
  9. 单独的css文件放到标签下
  10. global重写样式改成:deep
  11. 算了,改不动了,毁灭吧 ....

其实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>

大家结果结果怎么样,转换完,是否可以直接使用?

答案是肯定不行的,举了一些比较明显的问题如下:

  1. 函数组件外部定义的变量丢失
  2. react代码中对props的数据进行解构,但是转成vue3后丢失
  3. 一些引入的变量没有引入

虽然说存在一些问题,但是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。

本文纯属虚构,如有雷同,那恭喜有缘人!!!

相关推荐
Myli_ing6 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维23 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
I_Am_Me_37 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
雯0609~44 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
星星会笑滴1 小时前
vue+node+Express+xlsx+emements-plus实现导入excel,并且将数据保存到数据库
vue.js·excel·express
前端百草阁1 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html