组件
在 React 中,组件(Component) 是 UI 的基本构建块。可以把它理解为一个独立的、可复用的 UI 单元,类似于函数,它接受输入(props),然后返回 React 元素来描述 UI。
组件的简单使用
在 React 中,组件主要分为两种:类组件和函数组件。但是在 React 18+ 之后,以不推荐使用类组件,而是推荐使用函数组件。
函数组件本质上是一个 Javascript 函数,然后返回一个 JSX 结构。函数组件的名字必须以大写字母开头,否则 React 会将其视为一个 HTML 标签。
函数组件更简单,它没有 this, 编写和阅读更简单,而状态则是通过 React Hooks 来实现的。下面是一个简单的函数组件的例子:
// 定义组件
function Button() {// 组件内部的逻辑return (<button>Click me</button>);
}
这样我们就创建了一个 React 组件,接下来,我们可以在 App 组件中使用它:
function App() {return (<div><Button /></div>)
}
除了 Button 组件,App 组件本身也是组件,也就是说组件可以嵌套使用。
组件的特点
- 组件必须返回一个 JSX 结构(或 null)
- 组件名称必须大写(如 MyComponent,不能写 myComponent)
- 可以组合嵌套(组件可以包含其他组件)
- 可以接收 props(用于传递数据)
组件的状态
在 React 组件中,状态(State) 是指组件内部可变的数据,用于控制组件的行为和 UI 的变化。当 state 发生变化 时,React 会自动重新渲染组件,更新 UI。
为什么需要状态?
有些数据不会变,比如 props 传递过来的值,但有些数据是会随用户交互变化的,比如:
- 计数器的数值
- 按钮的开关状态
- 输入框中的内容
这些会变化的数据 就应该存到组件的 state 中。例如:
import { useState } from "react";function Counter() {// useState(0) 表示 count 初始值为 0const [count, setCount] = useState(0);return (<div><p>当前计数:{count}</p>{/* 点击按钮时,修改 count 值 */}<button onClick={() => setCount(count + 1)}>+1</button></div>);
}export default Counter;
当点击 +1 按钮时,会调用 setCount 方法,将 count 的值加 1,然后 React 会重新渲染组件,更新 UI。具体来说当组件的 state 发生变化时,React 执行以下流程:
- 调用 setState 或 useState 进行状态更新
- React 触发组件的重新渲染(函数组件会重新执行,类组件会触发 render 方法)
- 生成新的 Virtual DOM(虚拟 DOM)
- 比较新的 Virtual DOM 和旧的 Virtual DOM(Diffing 算法)
- 计算差异后更新真实 DOM(Reconciliation 过程)
React 通过 useState(或 setState)触发状态更新,然后采用 调度机制(Scheduler)+ 批量更新(Batching) 来通知浏览器进行重新渲染。这种机制类似于 Qt 的信号槽或者是发布-订阅模式。但是 React 的更新机制与 Qt 的信号槽不同的是,React 的更新机制是异步的,而 Qt 的信号槽是同步的。
Virtual DOM 机制
React 并不会直接操作 真实 DOM(Real DOM),而是使用 Virtual DOM 来提升性能。
虚拟 DOM(Virtual DOM) 是 React 在内存中的 JavaScript 对象,它描述了 UI 结构。
每次 state 变化时,React 会重新创建一个新的 Virtual DOM。
Diffing 算法(Diff 算法)
React 通过 Diffing 算法 比较「新的 Virtual DOM」和「旧的 Virtual DOM」,找出不同点,只更新发生变化的部分,避免整个页面的重新渲染。
Diff 算法流程
-
树形对比
如果新的 Virtual DOM 和旧的 Virtual DOM 不是相同的组件,则直接销毁旧组件,创建新组件。如果它们是同一个组件,则继续向下比较子元素。
-
属性对比(Props Diffing)
如果 props 发生变化,则更新对应的 DOM 属性。
-
子元素对比(Children Diffing)
使用Key 机制优化列表渲染(key 帮助 React 识别哪些元素是新增、删除或移动的,我们在列表渲染中接触过 key 机制)。
Reconciliation(协调过程)
React 在 Diffing 之后,使用 Reconciliation(协调过程) 将「最小的变更」应用到真实 DOM:
- 仅修改需要更新的 DOM 节点
- 不会重新创建整个 DOM 结构
- 性能更高,避免不必要的操作
React 如何知道状态发生了变化?
当我们调用 setState(类组件)或 useState(函数组件)时,React 内部执行以下操作:
-
记录状态变更
React 维护着一个 state 和 nextState。
当调用 setState(newState) 时,React 不会立即修改 state,而是先把 newState 放入更新队列。
-
触发调度(Scheduler 机制)
React 不是立刻重新渲染,而是将更新任务提交给 Scheduler(调度器),并进行批处理优化。
-
标记 Fiber 节点为「需要更新」
React 使用 Fiber 架构,每个组件对应一个 Fiber 节点,setState 会让 Fiber 节点进入「更新状态」,等待下一次渲染。
为什么 React 不是立即更新?
React 采用 批量更新(Batching) 机制:
- 同一个事件循环内的多个 setState 只会触发一次渲染,减少不必要的更新,提高性能。
- React 18 引入 Concurrent Mode,可以延迟低优先级的更新,提高流畅度。
useState 修改状态
我们还是以一个简单的 Demo 为例,展示如何使用 useState 修改状态:
import { useState } from "react"; // 引入 useState
function Counter() {const [count, setCount] = useState(0); // 初始化 count 为 0return (<div><p>当前计数:{count}</p><button onClick={() => setCount(count + 1)}>+1</button><button onClick={() => setCount(count - 1)}>-1</button></div>)
}
const [count, setCount] = useState(0);
这行代码就类似于发布订阅模式中,订阅者注册自己所需要关注的事件或消息。这里可以理解为订阅者注册了 count 变化时,会收到通知,然后更新 count 的值。
在 React 中,useState 是一个 Hook,用于在函数组件中添加状态。它返回一个数组,数组的第一个元素是当前状态的值,第二个元素是一个函数,用于更新状态。
直接改变 count 的值,并不会触发组件的重新渲染,因为 React 是通过 setState 来触发组件的重新渲染的。因此需要调用 setCount 来更新 count 的值,然后 React 会重新渲染组件,更新 UI。
多个状态值管理
你可以在 useState 中存储多个值,例如:
function UserInfo() {const [user, setUser] = useState({ name: "Alice", age: 25 });return (<div><p>姓名:{user.name}</p><p>年龄:{user.age}</p><button onClick={() => setUser({ ...user, age: user.age + 1 })}>生日+1</button></div>);
}
这里使用了 …user(展开运算符)来避免覆盖 name,仅修改 age。
组件样式控制
在 React 中,控制组件的样式,可以直接在行内样式中设置,也可以使用 className 属性来设置类名,然后在 CSS 文件中定义样式。在工程化中,一般是使用 className 来控制样式。
行内样式
在 React 中,你可以在行内样式中设置样式,例如:
<div style={{ color: 'red', fontSize: '16px' }}>Hello World</div>
在上面的例子中,style 属性是一个对象,其中包含了 CSS 属性及其值。你可以使用驼峰命名法来设置 CSS 属性,例如 color 和 fontSize。如果觉得 style 属性太长,可以定义一个变量来存储样式对象,然后使用变量来设置样式。例如:
const style = {color: 'red',fontSize: '16px'
}
<div style={style}>Hello World</div>
通过 className 来控制样式
在 React 中,你可以使用 className 属性来控制组件的样式,例如:
在 CSS 文件中,你可以定义一个类名为 my-class 的样式,例如:
.my-class {color: red;font-size: 16px;
}
在组件中,你可以使用 className 属性来设置类名,例如:
import './App.css';
<div className="my-class">Hello World</div>