## WHAT
### 核心原理
- React is a declarative, component-based library that builds user interfaces by re-rendering components in response to state changes.
- 你只需要描述“UI 应该长什么样”(而不是手动操作 DOM)
- React 帮你在状态变化时,自动算出 DOM 应该怎么变
#### Component = Function, input = pros+state, output = JSX
- React 的底层逻辑:`UI = f(state)`
- React re-renders the UI whenever state or props change.
- the component you write = a **function**
- input = `props + state`
- output = `JSX`(也就是 UI)
```jsx
function MyComponent({ name }) {
return <h1>Hello, {name}</h1>
}
```
> 只要 `name` 不一样,输出的 UI 就不一样。
所以当 `props` 或 `state` 变了,React 会干嘛?👇
#### Re-render the function, Virtual Dom, Diff
- `props` / `state` changed
- React calls your function component again
- passes in the latest `props` and `state`
- The function returns a new JSX tree
- it's a **Virtual DOM tree** — a lightweight copy of the actual DOM stored in memory.
- React compares the new Virtual DOM with the previous one.
- This process is called **"Reconciliation"** or **"Diffing"**.
- Based on the diff results, React applies only the necessary updates to the **actual DOM**.
- React 会重新执行组件函数(Re-render)
1. React 会重新调用你的函数组件
2. 把新的 `props` 和 `state` 传进去
3. 拿到返回的新 JSX 树
4. 和上一次的 JSX 树做“差异对比”(Diffing)
5. 最后最小化更新 DOM(用 Virtual DOM 实现的)
这个过程叫一次 **Reconciliation(协调过程)**
也叫 **Re-render(重新渲染)**
💡 所以“重新渲染”不等于“重新挂载组件”!组件的状态和 DOM 是可以被 React 保留和复用的,只是函数体重新跑一遍而已。
---
## 🧩 Step 3:为什么函数组件每次都“重新执行”?
这就是函数组件和类组件的最大区别:
- **类组件:** React 可以保留方法,不重新创建。
- **函数组件:** 每次渲染都是一次“全新函数调用”。
也就是说:
```tsx
function MyComponent() {
const [count, setCount] = useState(0)
const handleClick = () => {
console.log("clicked")
}
return <button onClick={handleClick}>click</button>
}
```
每次 `count` 变化,整个 `MyComponent()` 函数就会重新执行:
- `handleClick` 是个普通的 function declaration
- 所以每次都会重新生成一个 function(新地址)
- 如果你把这个函数传给子组件,那每次 `Child` 都以为 props 变了...
这就造成了👇
---
## 🧨 Step 4:为什么子组件会被“无意义”地重新渲染?
来看这段代码:
```tsx
const Child = React.memo(({ onClick }) => {
console.log("👶 Child render")
return <button onClick={onClick}>click me</button>
})
function Parent() {
const [count, setCount] = useState(0)
const handleClick = () => console.log('clicked')
return (
<>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}
```
### 结果是:
每点一下 `+1`,`Child` 都会重新渲染。
**为什么?**
- 每次 Parent 重新渲染 → `handleClick` 是一个新 function
- props 变了 → `React.memo()` 判断不了“其实没变”
- 所以 Child 也跟着重新渲染
💡 这是性能问题,不是 bug!但我们可以优化它👇
---
## 🛠 Step 5:React 给你的优化工具(Hooks)
|场景|用什么|作用|
|---|---|---|
|保证函数引用不变|`useCallback`|缓存函数|
|保证计算结果不变|`useMemo`|缓存值|
|不要子组件无意义渲染|`React.memo()`|组件层缓存|
|避免不必要更新组件内部逻辑|`useEffect` 的依赖控制|控制副作用|
---
## 🔁 所以用 `useCallback` 的意义在于:
```tsx
const handleClick = useCallback(() => {
console.log("clicked")
}, [])
```
这个 `handleClick`:
- 不会在每次 render 时重新创建函数
- 所以传给子组件时,子组件不会误以为 props 变了
- 和 `React.memo()` 一起用 → 避免无意义重渲染
---
## ✅ 总结一句话版
> React 会重新执行组件函数,是因为它要“重新生成 UI”;
> 为了性能优化,我们可以通过 `memo` + `useCallback` + `useMemo` 来缓存“不会变的东西”,避免不必要的渲染。
---
需要我继续写成一个 Obsidian 卡片?或者举个实际项目里的例子?比如表单里点击按钮、搜索过滤列表、子组件更新慢这些?
### 三大特色
#### 1.声明式 UI(Declarative UI)
- **声明式 vs. 命令式**:Declarative 意味着开发者只需描述界面应该是什么样子,而不是逐步描述如何实现这些样式。
- 在传统的 JavaScript 开发中,开发者需要写很多代码来操作 DOM,添加、删除元素和修改样式,这种命令式的方式容易出错且复杂。
- **结果导向**:在 React 中,你定义组件的期望结果。React 会根据状态自动更新视图,而不需要你关心如何具体去做。这使得管理复杂的用户界面变得简单,因为你不需要跟踪所有 DOM 操作。
- 你不需要写 `document.createElement` 或 `innerHTML`,你只写:
```tsx
const App = () => <h1>Hello {user.name}</h1>
```
- React 会自动根据 `user.name` 的变化更新页面
#### 2. 组件化(Component-based)
- 一切 UI 都是组件(Component)
- 组件 = 函数
- 组件之间可以嵌套、复用、组合
```tsx
const App = () => (
<Layout>
<Header />
<Main />
</Layout>
)
```
#### 3. 单向数据流(Unidirectional Data Flow)
- 数据只能从父组件 → 子组件 传递(通过 `props`)
- 子组件不能“直接改变”父组件的数据,而是通过 `callback` 通知父组件修改
这保证了数据流动的可控性,方便调试和维护。
## 🔧 React 的底层工作机制:从写代码到页面更新
### 一次状态更新会经历什么?
你写的代码:
```tsx
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
点击按钮后,发生以下过程:
1. setCount 触发状态变更
2. React 把 Counter() 这个函数重新执行一遍
3. 拿到新的 JSX(其实是一个 JS 对象)
4. 和“上一次的 JSX”做对比(Diffing)
5. 找出变化的部分 → 更新 DOM(最小单位)
页面显示的新 count 出来了!