## 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 出来了!