3年Vue3,借Trae之力,实现转React的有缝连接

重要!!!

在看以下内容前,请提前知道一下,我刚开始接触React,工作中用的是Vue3,所以以下的很多对 React 的不理解和误解,大概率均是我的问题,多半是我的能力不足,而与React无关。但如果真是React也有不足的地方,那我也没什么可说的。

前言

在写了近3年的 Vue3 后,慢慢地感觉没什么意思了。发现随着原理了解的越多,和工作相关的就越少。目前我学的原理,几乎完全用不到,倒是实用性的技术,这边学,下次可能就可以用到。当然偶尔还是能有些帮助,比如同事遇到一个bug,基本会说:"真是见了鬼了,这bug莫名其妙"。我帮着解决过几次,其实并不莫名其妙,明显的是代码有问题。

上面的问题遇到过:

  1. 不要用 reactive,reactive不准,用ref,它比较准一些,reactive的值经常莫名其妙的值不对。
  2. 对 reactive 的值重新赋值
js 复制代码
let obj = reactive();
... 一些代码

const {data} = await getDetail();
obj = reactive(data)

Vue3的官网看了也有 七八遍了,相关的原理书也看了不少,目前只能说有的时候突然很想知道一个技术为什么是这样,然后想了解其原理,但大多时候看原理,已经提不起来兴趣了。这时候我就想学些新的技术了。

学些什么呢

其实当想到35岁这个坎时,我对学什么技术就提不起来兴趣了,总觉得自己到了那个年纪,找工作也很费劲。但躺平也解决不了问题,反而可能使之后处境更不好。因为在做这个行业之前就想过这个问题,想的是自己做一个产品。又认真想了一段时间,好吧,开始做吧。

思考了一段时间后,决定自己去做一个,我打算用2年时间实现,分别是 一个后台、一个手机端。前端就用 Reactjs 写、后端用 Nestjs 来写。

之所以不用Vue,是因为这个产品我是否会一直做下去还不一定,虽然我现在比较坚定,但没发生的事还是有各种各样的可能。所以如果我借着写自己的产品,还可以学到新的技术如:React、Nestjs,哪怕之后还是工作,求职的路上大概率也会比少会一些要好。

Trae 帮助了我

此次用 React 和 Nestjs 去写后台管理系统,为了加快进展,我就使用了 Trae 。之前一直用 Cursor,但中途也会时不时的用用 Trae,慢慢地发现 Trae 进步也很快。

一个是 Tab 功能已经支持了,这点就很不错了,而且 Tab 好像是可以基于光标学习,因为随着我用的多,它就越来越准了,虽然和 Cursor 还是有些差距。比如 Cursor 的 Tab 支持跨文件的预判(说实话,这点是真牛,但用的不多)。但 Trae 我现在是免费用,这好感度就增加了一些。不过,由于我很大一部分是基于学习的目的写这个项目,所以我只需要 Trae 帮我实现基本功能,剩下的我会看 React 官网 和 Nestjs 官网去学习,和通过优化 Trae 生成的代码去加快自己的学习速度。

不过它在快捷键上和Cursor不一样,也不知为啥,又让我适应了一段时间。而且我明明导入配置时,选择的是从Cursor导入,但是页面主题感觉也不一样,这也让我适应了一段时间。但整体还是好感的,因为第一次使用 Trae 的时候,是不太舒服的。

还有一点是智能体,我也比较喜欢,不过我主要是在 豆包 上创建过多个智能体,感觉这个很不错,但在 Trae 上还没有尝试,所以也不好介绍。

既然文章是讲用 React 的,而且是 有缝连接。那就来讲下这些感觉遇到的问题吧

使用 React 的不适应

路由

路由,如果直接在 组件或函数 外部使用(比如tsx的文件中直接用) const navigate = useNavigate(); 会报下面的错误

js 复制代码
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();

如果写在函数内部,就没有这个问题了。

不过可以想到,可能是 React 是把声明的 function AA 这个当做是组件,然后 export default AA; 出去,所以即使写在 tsx 文件中,它依然不属于组件内的。而 Vue 中是将 .vue 文件当做一个组件,所以即使写在 script 中也可以。可能主要还是要将 路由的操作 限制在组件内。

但我的 Trae 装了 圈复杂度 的插件,推荐的是尽量不要超过 10,而 React 又以函数作为组件,所以一个组件 轻轻松松的就有 20+ 的 圈复杂度。

纯函数

React 推荐使用纯函数,就是不修改外部变量,这样的好处就是无论这个函数执行了多少次,由于所操作的数据都在自己的作用域内,所以无论重复执行了多少次,其结果是一样的。不会因为每次执行都会得出不一样的结果。

js 复制代码
// 纯函数
function getVal(val){
    return val + 5;
}

// 非纯函数
let val;
function getVal(){
    val+=5;
    return val;
}

React 官方是建议数据通过参数传递,这样多个地方用到的数据,就需要来回传递。而目前我除了静态数据是直接放在 tsx 文件的根中,其他数据都是在某个函数中定义的,但不少的变量可能在多个地方使用,要么多个函数都写在一个组件中,这样就可以把变量声明在组件中,其中的函数就可以直接访问了。但代码多了后,我还是习惯把一些函数放在与 组件同级的位置,这样就又需要问题传参。

有时还需要传两层,当然了,官网给出了如下的解决方法

js 复制代码
function Profile(props) {  
    return (  
        <div className="card">  
            <Avatar {...props} />  
        </div>  
    );  
}

通过 扩展运算符 将数据平铺后传入组件。但在一个组件中,需要用到多个函数,而函数之间不断的传参就变得很频繁了。

父组件调用子组件的方法

首先需要在父组件定义 ref

js 复制代码
父组件
function BookList(){
    在父组件定义 ref
 const bookFormModelRef = useRef<typeof BookFormModel>(null);
 
 <BookTable ref={bookFormModelRef} onClickEdit={showEditModal} />
 
 ...
 
 }
 
 子组件接收 ref 
function BookTable({ ref }){
  再使用 useImperativeHandle函数,传入 ref,再传入需要向父组件暴露的数据
  useImperativeHandle(ref, () => ({
    loadBooks,
  }));
 
 ...
 
 }

不太舒服的是,明明是父组件要获取子组件的方法,怎么子组件还要先接收父组件的ref。初步猜测是 在 useImperativeHandle 方法中,将 第二个参数的返回值赋值给第一个参数ref,但还没有来得及看原理。

我的代码,看下有问题不

下面是用 ant-design 写的一个表格(react初学者,写的不好请见谅)。一段代码中又有js 又有 html。动不动一个函数就上百行。觉得太多了也可以折成多个函数,然后参数一传就是好几个。

tsx 复制代码
const ActionButton = ({
  record,
  showModal,
  handleDelete,
}: {
  record: Paragraph;
  showModal: (record: Paragraph) => void;
  handleDelete: (id: number) => void;
}) => {
  return (
    <Space>
      <Button
        type="text"
        icon={<EditOutlined />}
        onClick={() => showModal(record)}
      >
        编辑
      </Button>
      <Popconfirm
        title="确定要删除这个段落吗?"
        onConfirm={() => handleDelete(record.paragraph_id)}
        okText="确定"
        cancelText="取消"
      >
        <Button type="text" danger icon={<DeleteOutlined />}>
          删除
        </Button>
      </Popconfirm>
    </Space>
  );
};

// 渲染表格
const ParagraphTable = ({
  paragraphs,
  pagination,
  total,
  handleTableChange,
  showModal,
  handleDelete,
}: {
  paragraphs: Paragraph[];
  pagination: Pagination;
  total: number;
  handleTableChange: (pagination: Pagination) => void;
  showModal: (record: Paragraph) => void;
  handleDelete: (id: number) => void;
}) => {
  // 表格列定义
  const columns = [
    {
      title: "序号",
      dataIndex: "index",
      key: "index",
      width: 80,
      render: (_: any, _record: Paragraph, index: number) => index + 1,
    },
    {
      title: "排序",
      dataIndex: "paragraph_order",
      key: "paragraph_order",
      width: 80,
    },
    {
      title: "类型",
      dataIndex: "type",
      key: "type",
      width: 80,
    },
    {
      title: "内容",
      dataIndex: "content",
      key: "content",
      render: (text: string, record: Paragraph) => {
        if (record.type === "image") {
          // 使用imagePath属性显示图片
          return (
            <img
              src={record.imagePath}
              alt="段落图片"
              style={{ maxWidth: "100px", maxHeight: "100px" }}
            />
          );
        }
        return text;
      },
    },
    {
      title: "操作",
      key: "action",
      width: 180,
      render: (_: any, record: Paragraph) => (
        <ActionButton
          record={record}
          showModal={showModal}
          handleDelete={handleDelete}
        />
      ),
    },
  ];
  return (
    <Table
      dataSource={paragraphs}
      columns={columns}
      rowKey="paragraph_id"
      pagination={{
        current: pagination.current,
        pageSize: pagination.pageSize,
        total,
      }}
      onChange={handleTableChange}
    />
  );
};

export default ParagraphTable;

以上就是一个常规的表格,在我写Vue时,也会将 el-table 进行二次封装。之后和 Ant-Design 比较像,是通过配置项的方式进行传入,如果是遇到要写模板内容的,则会通过插槽实现。如下

js 复制代码
[
  {
    prop: 'publishTime',
    label: '发布时间',
    width: 180
  },
  {
    prop: 'status',
    label: '发布标志',
    dict: 'BIDDING_STATUS',
    width: 100,
    fixed: 'right'
  },
  {
    label: '操作',
    width: 140,
    slot: 'operation'
  }
]

而 .vue 页面在模板中则可以通过 插槽的方式注入

js 复制代码
 <Table @register="tableRegister">
        <template #operation="{ row }">
             这里可以写功能按钮了
        </template>
 </Table>

这样习惯下来后,感觉还挺清爽的。模板和js分开来写,每个地方的代码量也比较好找。

对 useState 的猜测

这里不是说用 useState 的不适应了。只是想讲下刚接触到 useState 的想法。

ts 复制代码
import { useState } from 'react';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <p>
        {index}
      </p>
    </>
  );
}

这是 React 使用 useState 的写法,主要是为了让 index 数据在 Gallery 组件多次渲染时,依然记得 index 的值。 就是说 第一次 index 是1,第二次就是 2 了。

但在 js 中,每个函数都有自己的作用域和上下文,虽然函数的作用域是在函数定义的时候就确定的(由其词法环境决定)。但每当函数执行完后,其执行上下文中的相关亦是会被垃圾回收,释放内存,下次再调用时,重新生成其 执行上下文与相关变量。

但闭包可以保存其变量,使其函数执行后,该变量依然保存在 内存中。

而 React 的 useState 给我的感觉就是这个作用,那我直接把 index 放在组件外定义不就好了吗? 当然,肯定还有我不知道的原因,React 官网已经看过一遍了(教程部分,还没有看 api 部分,那部分量应该会比较大),目前还不知道 useState 的特别之处。

结语

就像我一开始说的,上面的内容大多可能是我的问题,而不是 React 的问题,毕竟还有这么多公司在用它,而且还有面试官和我说,会 React 的工资要比 Vue 高一点。所以 React 应该还是很优秀的。那我就多向它学习吧 !

相关推荐
前端工作日常42 分钟前
我理解的`npm pack` 和 `npm install <local-path>`
前端
李剑一1 小时前
说个多年老前端都不知道的标签正确玩法——q标签
前端
嘉小华1 小时前
大白话讲解 Android屏幕适配相关概念(dp、px 和 dpi)
前端
姑苏洛言1 小时前
在开发跑腿小程序集成地图时,遇到的坑,MapContext.includePoints(Object object)接口无效在组件中使用无效?
前端
奇舞精选1 小时前
Prompt 工程实用技巧:掌握高效 AI 交互核心
前端·openai
Danny_FD1 小时前
React中可有可无的优化-对象类型的使用
前端·javascript
用户757582318551 小时前
混合应用开发:企业降本增效之道——面向2025年移动应用开发趋势的实践路径
前端
P1erce2 小时前
记一次微信小程序分包经历
前端
LeeAt2 小时前
从Promise到async/await的逻辑演进
前端·javascript
等一个晴天丶2 小时前
不一样的 TypeScript 入门手册
前端