2025-10-28 16:57:36 +08:00

607 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: React
date: 2025-07-23 15:56:46
tags:
---
# Vue 转 React 指南
## JSX
先介绍 React 唯一的一个语法糖JSX。
理解 JSX 语法并不困难,简单记住一句话,遇到 `{}` 符号内部解析为 JS 代码,遇到成对的 `<>` 符号内部解析为 HTML 代码。
当你写下这个 React 组件时:
```jsx
import React from 'react';
function MyComponent(props) {
return <div>{props.hello}</div>
}
```
最终会被自动工具翻译成:
```jsx
import React from 'react';
function MyComponent(props) {
return React.createElement('div', null, props.hello);
}
```
React 就是通过这个小小语法糖,实现在 JS 里面写 HTML可能有小伙伴会说 HTML 与 JS 分离不是更好吗?责职分明,混合只会更乱。但当你体验到代码自动提示,自动检查,以及调试时精确定位到一行代码的好处时,就清楚 React 和 Vue 的差距了。
## 文本插值
**vue种采用双括号**
```vue
<span>Message: {{ msg }}</span>
```
**react采用单括号**
```jsx
function MyComponent(props) {
let msg = 'XXX'
return <div>{ msg }</div>
}
```
## Attribute 绑定
**vue中 想要响应式地绑定一个 attribute应该使用 `v-bind` 指令**
```vue
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
```
**react中使用单引号或者使用单括号包裹表示动态绑定**
```jsx
function App () {
let tmpID = '12'
return (
<div className='App'>
<div id='12'>id</div>
<div id={tmpID}>id</div>
</div>
);
}
```
动态绑定多值:
```jsx
function App () {
let tmpObject = {
id: 13,
className: 'wrapper'
}
return (
<div className='App'>
<div {...tmpObject}>id</div>
</div>
);
}
<div id="13" class="wrapper">id</div>
```
## 参数 Arguments
**某些指令会需要一个“参数”Vue在指令名后通过一个冒号隔开做标识。例如用 `v-bind` 指令**
```vue
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
```
**React中则没有指令一说而是采用如下方式**
```jsx
// href跳转
function App () {
let tmpURL = 'https://www.XXXXXXXX'
return (
<div className='App'>
<a href={tmpURL}></a>
</div>
);
}
```
## 使用 JS 表达式
**React 遇到 `{}` 符号内部解析为 JS 代码**
```jsx
function App () {
let tmpString = '--';
return (
<div className='App'>
<div >{1 + 1}</div>
<div >{'a' + 'b'}</div>
<div >{`1${tmpString}1`}</div>
</div>
);
}
```
即:
```html
<div>2</div>
<div>ab</div>
<div>1--1</div>
```
## 事件处理
**Vue中绑定事件处理**
```vue
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
```
**React可以通过在组件中声明 事件处理 函数来响应事件**
React中点击事件使用小驼峰形式onClick
在标签上写函数:
```jsx
function App () {
return (
<div className='App'>
<div onClick={() => alert('点击出现弹框')}>按钮</div>
</div>
);
}
```
提前声明函数:
```jsx
function App () {
function myFun () {
alert('点击出现弹框')
}
return (
<div className='App'>
<div onClick={myFun}>按钮</div>
</div>
);
}
```
注意,`onClick={handleClick}` 的结尾没有小括号!不要 **调用** 事件处理函数:你只需 **传递给事件** 即可。当用户点击按钮时React 会调用你的事件处理函数。
函数传参:
```jsx
function App () {
function myFun (str) {
alert(str)
}
return (
<div className='App'>
<div onClick={() => myFun('点击出现弹框')}>按钮</div>
</div>
);
}
```
## 动态参数
**Vue在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:**
```vue
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
```
举例来说,如果你的组件实例有一个数据属性 `attributeName`,其值为 `"href"`,那么这个绑定就等价于 `v-bind:href`
**React 也可以通过动态参数绑定**
```jsx
function App () {
const obj = {
onClick: () => alert('点击出现弹框'),
// ...还可以写更多事件
}
return (
<div className='App'>
<div {...obj}>按钮</div>
</div>
);
}
```
## 修饰符 Modifiers
vue 修饰符是以点开头的特殊后缀
表明指令需要以一些特殊的方式被绑定。例如 `.prevent` 修饰符会告知 `v-on` 指令对触发的事件调用 `event.preventDefault()`
```vue
<form @submit.prevent="onSubmit">...</form>
```
React 则是依靠于JS基础
```jsx
function App () {
function onSubmit(e){
e.preventDefault();
e.stopPropagation();
}
return (
<div className='App'>
<form onSubmit={onSubmit}>
<button type='submit'></button>
</form>
</div>
);
}
```
## 响应式
**为了实现视图更新VUE中响应式是一个重要的概念**
**而 React 中并没有响应式这个概念,要实现视图更新,需要在 React 引入 `useState`**
通常,你会希望你的组件 “记住” 一些信息并展示出来。例如,也许你想计算一个按钮被点击的次数。要做到这一点,你需要在你的组件中添加 **state**
首先,从 React 引入 `useState`
```jsx
import { useState } from 'react';
```
现在你可以在你的组件中声明一个 **state 变量**
```jsx
function MyButton() {
const [count, setCount] = useState(0);
// ...
```
你将从 `useState` 中获得两样东西:当前的 state`count`),以及用于更新它的函数(`setCount`)。你可以给它们起任何名字,但按照惯例,需要像这样 `[something, setSomething]` 为它们命名。
第一次显示按钮时,`count` 的值为 `0`,因为你把 `0` 传给了 `useState()`。当你想改变 state 时,调用 `setCount()` 并将新的值传递给它。点击该按钮计数器将递增:
```jsx
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
```
React 将再次调用你的组件函数。这次,`count` 会变成 `1`。接着,变成 `2`。以此类推。
如果你多次渲染同一个组件,每个组件都会拥有自己的 state。你可以尝试点击不同的按钮
## 计算属性
Vue中使用 computed 来实现计算属性(缓存计算的结果)
**React 在组件的顶层调用 `useMemo` 来缓存每次重新渲染都需要计算的结果**
```jsx
import { useState } from 'react';
import { useMemo } from 'react';
function App () {
const [user] = useState({ firstname: 'a', lastname: 'b' })
const fullname = useMemo(() => {
return user.firstname + user.lastname;
}, [user.firstname, user.lastname])
return (
<div className='App'>
{fullname}
</div>
);
}
```
**useMemo(calculateValue, dependencies)**
参数
- `calculateValue`要缓存计算值的函数。它应该是一个没有任何参数的纯函数并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 `dependencies` 没有发生变化React 将直接返回相同值。否则,将会再次调用 `calculateValue` 并返回最新结果,然后缓存该结果以便下次重复使用。
- `dependencies`:所有在 `calculateValue` 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 [配置了 React](https://react.docschina.org/learn/editor-setup#linting),它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 `[dep1, dep2, dep3]` 这种形式。React 使用 [`Object.is`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 将每个依赖项与其之前的值进行比较。
## 绑定 HTML class
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 `class``style` 都是 attribute
Vue中可以给 `:class` (`v-bind:class` 的缩写) 传递一个对象来动态切换 class
```vue
<div :class="{ active: isActive }"></div>
```
上面的语法表示 `active` 是否存在取决于数据属性 `isActive` 的真假值。
React实现方式基于js语法其实有多种实现方式列举三元运算符方式如下
```jsx
function App () {
let showColor = false
return (
// 现有box-show、box-hide两个class样式
<div className={showColor ? 'box-show' : 'box-hide'}></div>
);
}
```
## 语法糖转换
习惯 Vue 的同学都知道很多语法糖,比如 `v-if``v-for``v-bind``v-on` 等,相比 VueReact 只有一个语法糖,那就是 jsx/tsx。`v-if` 这些功能在 React 上都是通过原生 javascript 实现的,慢慢你会发现,其实你学的不是 React而是 JavasciptReact 赋予你通过 js 完整控制组件的能力,这部分明显比 Vue 的语法糖更加灵活糖太多容易引来虫子Bug
条件渲染
vue 中写法是这样:
```vue
<div>
<h1 v-if="ishow">Vue is awesome!</h1>
<h1 v-else>else</h1>
</div>
```
在 React 函数组件中,只需使用 js 三目运算符语法即可完成条件渲染的功能。或者使用 && 逻辑,记住下面一句话就能过理解了:
> 遇到 `{}` 符号内部解析为 JS 代码,遇到成对的 `<>` 符号内部解析为 HTML 代码
```jsx
function App () {
const ishow = false
return (
<div>
{ishow ? <div>awesome</div> : <div>else</div>}
{ishow && <h1>React!</h1>}
</div>
);
}
```
## 列表渲染
Vue中通过v-for进行列表渲染
**React 通过 js 的数组语法 map将数据对象映射为 DOM 对象**。只需学会 js无需记住各种指令如果要做列表过滤直接使用 `items.filter(...).map(...)` 链式调用即可,语法上更加灵活,如果为了提高渲染性能,使用 useMemo 进行优化即可,类似 Vue 的 computed。
```jsx
function App () {
const arr = [{ message: 'react' }, { message: 'JS' }]
return (
<div>
{arr.map((items, index) => <li key={index}>{items.message}</li>)}
</div >
);
}
```
## 侦听器
Vue中使用 watch监听数据变化触发回调
React中可以使用 useEffect 实现
```jsx
function App () {
const [user, setUser] = useState({
firstname: 'a',
lastname: 'b'
})
useEffect(() => {
console.log("1111")
}, [user.firstname])
return (
<div>
<button onClick={() => setUser({ ...user, firstname: 'a2' })}></button>
</div >
);
}
```
# 父子组件传递事件
## 子组件是模态框确定按钮需要增加loading状态
### **子组件:增加 `confirmLoading` 状态并优化 `handleOk` 方法**
```tsx
import React, { useState } from 'react';
import { Modal, Checkbox, Input, message } from 'antd';
import { useTranslation } from 'react-i18next'
const { TextArea } = Input;
interface FeedbackModalProps {
open: boolean;
onOk: (selectedOption: number | null, feedbackText: string) => Promise<void>;
onCancel: () => void;
}
const FeedbackModal: React.FC<FeedbackModalProps> = ({ open, onOk, onCancel }) => {
const [selectedOption, setSelectedOption] = useState<number | null>(null);
const [feedbackText, setFeedbackText] = useState<string>('');
const { t } = useTranslation()
const [confirmLoading, setConfirmLoading] = useState(false);
const handleOk = async () => {
if (!feedbackText) {
message.warning('请填写反馈建议');
return;
}
setConfirmLoading(true)
try {
await onOk(selectedOption, feedbackText);
} finally {
setConfirmLoading(false)
}
};
return (
<Modal
title="反馈"
open={open}
onOk={handleOk}
onCancel={onCancel}
okText={`${t('common.operation.confirm')}`}
cancelText={`${t('common.operation.cancel')}`}
confirmLoading={confirmLoading}
>
{/* <div>
<Checkbox
checked={selectedOption === 0}
onChange={() => setSelectedOption(0)}
>
新增
</Checkbox>
<Checkbox
checked={selectedOption === 1}
onChange={() => setSelectedOption(1)}
>
修改
</Checkbox>
</div> */}
<TextArea
rows={4}
placeholder="请输入您的反馈建议"
value={feedbackText}
onChange={(e) => setFeedbackText(e.target.value)}
/>
</Modal>
);
};
export default FeedbackModal;
```
### **父组件:确保 `handleFeedbackSubmit` 返回 Promise**
```tsx
const handleFeedbackSubmit = useCallback(
async (selectedOption: number | null, feedbackText: string) => {
console.log(currentMessageId);
console.log(currentFeedback);
try {
// 构造请求参数
const requestBody = {
operationType: currentFeedback!.rating, // 0 或 1
feedbackText, // 用户反馈建议
conversationId: currentConversationId, // 会话 ID
messageId: currentMessageId, // 消息 ID
username: getCookieValue('username'), // 用户名
};
// 调用 Java 接口
const javaResponse = await fetch(
`/dev-api/api/conversation/feedback`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
}
);
// 调用原有的 updateFeedback 函数
await updateFeedback(
{
url: `/messages/${currentMessageId}/feedbacks`,
body: { rating: currentFeedback!.rating },
},
// isInstalledApp,
// appId
);
// 显示成功通知
notify({ type: "success", message: t("common.api.success") });
if (resolveFeedback) {
resolveFeedback(true); // 用户取消了反馈
setResolveFeedback(null);
}
// 关闭对话框
setIsFeedbackModalVisible(false);
} catch (error) {
console.error("Error:", error);
notify({ type: "error", message: t("common.api.failed") });
} finally {
setIsSubmittingNow(false);
}
},
[
currentMessageId,
currentFeedback,
currentConversationId,
isInstalledApp,
appId,
notify,
t,
]
);
```
父组件调用子组件部分
```tsx
<FeedbackModal
open={isFeedbackModalVisible}
onOk={handleFeedbackSubmit}
onCancel={() => {
if (resolveFeedback) {
resolveFeedback(false); // 用户取消了反馈
setResolveFeedback(null);
}
setIsFeedbackModalVisible(false)
}}
/>
```
### **关键点说明**
1. **子组件内部管理加载状态**
通过 `useState` 创建 `isLoading` 状态,并在 `handleOk` 中控制其值。点击按钮时,`isLoading` 变为 `true`,请求结束后变为 `false`
2. **`okButtonProps` 绑定加载状态**
Ant Design 的 `Modal` 组件支持通过 `okButtonProps` 自定义按钮属性,这里将 `loading` 绑定到 `isLoading` 状态。
3. **`onOk` 作为异步函数传递**
父组件的 `handleFeedbackSubmit``async` 函数,返回 Promise。子组件通过 `await onOk()` 确保加载状态在请求结束后更新。
4. **错误处理不影响加载状态**
使用 `try...finally` 确保无论请求成功或失败,`isLoading` 都会被重置。