React
React 是一个由 Facebook 开发的流行的 JavaScript 库,用于构建交互式用户界面。它采用了一种基于组件化的开发模式,使得开发人员可以将 UI 拆分为独立的、可复用的组件,并由这些组件构建复杂的用户界面。
React 的主要特点包括:
组件化开发:React 将 UI 拆分为独立的、可复用的组件,开发人员可以将这些组件组合在一起构建复杂的用户界面;
虚拟 DOM:React 采用虚拟 DOM 技术来优化 UI 更新性能,通过比较前后状态的差异来最小化 DOM 操作;
单向数据流:React 中的数据流是单向的,数据由父组件传递给子组件,子组件不能直接修改父组件的数据;
JSX:React 支持使用 JSX 语法,将组件的结构和样式与 JavaScript 代码结合在一起,使得代码更加简洁和易于理解。
React 生态系统非常丰富,包括许多与 React 相关的库和工具,如 Redux、React Router、Webpack 等,可帮助开发人员更好地使用 React 构建应用程序。
React 在 Web 开发、移动应用开发和桌面应用开发等领域得到了广泛应用,并且在社区中有着非常活跃的开发者和贡献者。如果您想要学习构建交互式用户界面的技术,React 是一个非常不错的选择。

查看更多相关内容
Qwik 和 React 有什么区别?Qwik 和 React 在架构上有几个关键区别,主要体现在加载策略、状态管理和性能优化方面:
## 1. 加载策略
**Qwik**:
- 采用"按需加载"策略,所有 JavaScript 代码默认都是延迟加载的
- 只有当用户与页面交互时,才会加载和执行相关的代码
- 不需要下载整个应用程序包,而是按需加载单个函数或组件
**React**:
- 通常需要下载整个应用程序包(或多个 chunk)
- 使用代码分割(Code Splitting)来实现懒加载,但需要手动配置
- 即使使用 SSR,仍需要下载 hydration 代码
## 2. 水合(Hydration)
**Qwik**:
- 不需要传统的水合过程
- 通过恢复性(Resumability)直接从 HTML 中恢复状态和功能
- 事件监听器通过 HTML 属性直接附加,无需 JavaScript 执行
**React**:
- 必须进行水合过程,重新执行 JavaScript 来附加事件监听器
- 水合过程需要执行大量 JavaScript,影响首屏性能
- 使用 React 18 的 Selective Hydration 可以部分优化,但仍不如 Qwik
## 3. 状态管理
**Qwik**:
- 使用 `useSignal` 和 `useStore` 进行状态管理
- 状态变化会自动触发细粒度的更新
- 状态序列化到 HTML 中,可以在客户端直接恢复
**React**:
- 使用 `useState`、`useReducer`、Context API 等进行状态管理
- 状态变化会触发组件重新渲染
- 需要额外的状态管理库(如 Redux、Zustand)来管理复杂状态
## 4. 性能优化
**Qwik**:
- 编译器自动优化代码分割和加载
- 零 JavaScript 启动成本
- 自动实现细粒度的更新,避免不必要的重新渲染
**React**:
- 需要手动优化性能(如 `useMemo`、`useCallback`)
- 使用 `React.memo` 来避免不必要的重新渲染
- 需要开发者有深入的性能优化知识
## 5. 开发体验
**Qwik**:
- 语法与 React 相似,学习曲线较平缓
- 编译器处理大部分优化工作
- 不需要关心代码分割和加载策略
**React**:
- 生态系统成熟,有丰富的第三方库
- 社区支持强大,文档完善
- 需要开发者手动处理性能优化
## 6. 适用场景
**Qwik**:
- 适合需要极致性能的应用
- 内容密集型网站
- 需要良好 SEO 的应用
- 大型企业级应用
**React**:
- 适合各种规模的应用
- 团队已经熟悉 React 生态
- 需要丰富的第三方库支持
- 快速原型开发
总结:Qwik 通过其独特的恢复性架构,在性能方面优于 React,特别是在首屏加载和交互响应方面。但 React 的生态系统和社区支持更加成熟,适合更广泛的应用场景。
前端 · 2月21日 15:37
如何在 React 中使用 MobX 的 observer?在 MobX 中,`observer` 是一个高阶组件(HOC),用于将 React 组件转换为响应式组件。当组件使用的数据发生变化时,组件会自动重新渲染。
## observer 的基本用法
### 1. 类组件中使用 observer
```javascript
import React from 'react';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
class Store {
@observable count = 0;
}
const store = new Store();
@observer
class Counter extends React.Component {
render() {
return (
<div>
<p>Count: {store.count}</p>
<button onClick={() => store.count++}>Increment</button>
</div>
);
}
}
```
### 2. 函数组件中使用 observer
```javascript
import React from 'react';
import { observer } from 'mobx-react-lite';
import { observable } from 'mobx';
class Store {
@observable count = 0;
}
const store = new Store();
const Counter = observer(() => {
return (
<div>
<p>Count: {store.count}</p>
<button onClick={() => store.count++}>Increment</button>
</div>
);
});
```
## observer 的工作原理
### 1. 组件挂载时
- MobX 创建一个 reaction 来追踪组件 render 函数中访问的所有 observable
- 建立组件与 observable 之间的依赖关系
### 2. 状态变化时
- 当 observable 被修改时,MobX 检测到依赖变化
- 将组件标记为需要重新渲染
- 在下一个事件循环中,触发组件的重新渲染
### 3. 组件卸载时
- 自动清理 reaction 和依赖关系
- 避免内存泄漏
## observer 的优化特性
### 1. 细粒度更新
observer 只会重新渲染真正需要更新的组件:
```javascript
@observer
class Parent extends React.Component {
render() {
return (
<div>
<ChildA />
<ChildB />
</div>
);
}
}
@observer
class ChildA extends React.Component {
render() {
// 只依赖 store.count
return <div>Count: {store.count}</div>;
}
}
@observer
class ChildB extends React.Component {
render() {
// 只依赖 store.name
return <div>Name: {store.name}</div>;
}
}
```
当 `store.count` 变化时,只有 ChildA 会重新渲染,ChildB 不会。
### 2. shouldComponentUpdate 优化
observer 会自动实现 `shouldComponentUpdate`,避免不必要的渲染:
- 只有当组件依赖的 observable 真正变化时才重新渲染
- 即使父组件重新渲染,子组件也可能不会重新渲染
### 3. 批量更新
多个状态变化会被批量处理,只触发一次重新渲染:
```javascript
runInAction(() => {
store.count++;
store.name = 'New Name';
});
```
## observer 的最佳实践
### 1. 只在需要的地方使用 observer
不是所有组件都需要 observer,只在需要响应状态变化的组件上使用:
```javascript
// 不需要 observer
const Header = () => <h1>My App</h1>;
// 需要 observer
const Counter = observer(() => {
return <div>Count: {store.count}</div>;
});
```
### 2. 避免在 render 中创建新对象
在 render 中创建新对象会导致不必要的重新渲染:
```javascript
// 不好的做法
const BadComponent = observer(() => {
const style = { color: 'red' }; // 每次渲染都创建新对象
return <div style={style}>{store.count}</div>;
});
// 好的做法
const style = { color: 'red' }; // 在组件外部定义
const GoodComponent = observer(() => {
return <div style={style}>{store.count}</div>;
});
```
### 3. 使用 computed 优化计算
在组件外部使用 computed 来优化计算逻辑:
```javascript
// 不好的做法
const BadComponent = observer(() => {
const fullName = `${store.firstName} ${store.lastName}`;
return <div>{fullName}</div>;
});
// 好的做法
class Store {
@observable firstName = 'John';
@observable lastName = 'Doe';
@computed get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const GoodComponent = observer(() => {
return <div>{store.fullName}</div>;
});
```
### 4. 使用 React.memo 配合 observer
对于纯展示组件,可以结合 React.memo 使用:
```javascript
const PureComponent = React.memo(observer(() => {
return <div>{store.count}</div>;
}));
```
## 常见问题
### 1. 组件不更新
确保:
- 组件被 observer 包装
- 访问的是 observable 而不是普通对象
- 状态修改在 action 中进行
### 2. 过度渲染
如果组件过度渲染,检查:
- 是否在 render 中创建了新对象
- 是否使用了 computed 来优化计算
- 是否可以拆分组件以减少依赖
### 3. 内存泄漏
确保:
- 组件卸载时 observer 会自动清理
- 手动创建的 reaction 需要手动清理
## 总结
observer 是 MobX 与 React 集成的核心,它通过细粒度的依赖追踪实现了高效的响应式更新。正确使用 observer 和遵循最佳实践,可以构建高性能的 React 应用。
服务端 · 2月19日 17:58
React 如何做性能优化?有哪些常见手段?React 在性能优化方面提供了多种策略和工具,以确保用户界面高效、平滑且响应迅速。以下是一些常用的性能优化手段:
### 1. 使用 `shouldComponentUpdate` 和 `React.PureComponent`
在类组件中,通过实现 `shouldComponentUpdate` 方法可以控制组件是否需要更新。当组件的状态或属性改变时,此方法会被调用,并根据返回的布尔值决定是否进行渲染。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">shouldComponentUpdate</span><span class="token">(</span><span class="token parameter">nextProps</span><span class="token parameter">,</span><span class="token parameter"> nextState</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 只有当特定的属性或状态改变时才更新组件</span><span>
</span><span> </span><span class="token control-flow">return</span><span> nextProps</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">props</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">||</span><span> nextState</span><span class="token">.</span><span class="token property-access">count</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">state</span><span class="token">.</span><span class="token property-access">count</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
对于那些拥有不可变的属性和状态的组件,可以使用 `React.PureComponent`,它通过浅比较 props 和 state 来减少不必要的渲染。
### 2. 使用 Hooks(如 `React.memo` 和 `useMemo`)
对于函数组件,`React.memo` 是一个高阶组件,它仅在组件的 props 发生变化时才会重新渲染组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">MyComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">memo</span><span class="token">(</span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token parameter">props</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">/* 只有props改变时,组件才会重新渲染 */</span><span>
</span><span></span><span class="token">}</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
`useMemo` 和 `useCallback` 钩子可以用来缓存复杂计算的结果和回调函数,避免在每次渲染时都重新计算和创建新的函数实例。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> memoizedValue </span><span class="token">=</span><span> </span><span class="token">useMemo</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">computeExpensiveValue</span><span class="token">(</span><span>a</span><span class="token">,</span><span> b</span><span class="token">)</span><span class="token">,</span><span> </span><span class="token">[</span><span>a</span><span class="token">,</span><span> b</span><span class="token">]</span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">const</span><span> memoizedCallback </span><span class="token">=</span><span> </span><span class="token">useCallback</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 一个依赖特定props的回调函数</span><span>
</span><span></span><span class="token">}</span><span class="token">,</span><span> </span><span class="token">[</span><span>props</span><span class="token">]</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
### 3. 避免不必要的 DOM 更新
当操作 DOM 时,应尽量减少更新次数和范围。可以使用虚拟列表(比如 `react-window` 或 `react-virtualized`)来仅渲染可视区域的元素,从而提高长列表的性能。
### 4. 懒加载组件和路由
使用 `React.lazy` 可以实现组件级别的代码拆分,这样可以将不同的组件打包成单独的代码块,并在需要时才加载它们。
同时,结合 `React Router` 的 `Suspense` 组件,可以实现路由级别的懒加载,仅当路由被访问时才加载对应的组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">lazy</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token module">import</span><span class="token">(</span><span class="token">'./OtherComponent'</span><span class="token">)</span><span class="token">)</span><span class="token">;</span><span>
</span>
<span></span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token control-flow">return</span><span> </span><span class="token">(</span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span> fallback</span><span class="token">=</span><span class="token">{</span><span class="token"><</span><span>div</span><span class="token">></span><span class="token maybe-class-name">Loading</span><span class="token spread">...</span><span class="token"><</span><span class="token">/</span><span>div</span><span class="token">></span><span class="token">}</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">/</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token">/</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span class="token">></span><span>
</span><span> </span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
### 5. 使用 Web Workers
对于复杂或计算密集型任务,可以使用 Web Workers 在后台线程中执行,避免阻塞主线程导致用户界面卡顿。
### 6. 优化条件渲染
避免不必要的渲染,例如,可以将条件渲染逻辑移到可能更改状态的事件处理函数中,而不是在渲染方法中进行。
### 7. 状态升级
将子组件的本地状态提升到父组件中,这样可以减少不必要的子组件渲染,因为状态的变化会集中处理。
### 8. 使用不可变数据结构
使用不可变数据可以更容易地检测到状态和属性的变化,这使得组件的更新检查更高效。库如 `Immutable.js` 可以用来帮助创建不可变数据。
### 9. 使用生产版本的 React
开发中通常使用的是开发版本的 React,它包含了许多有用的警告和错误信息。但在生产中,应该使用经过压
缩和优化的生产版本,它删除了这些额外的警告和检查,以减少库的大小并提升性能。
### 10. 分析和监控
使用性能分析工具,如 React DevTools 中的 Profiler,可以帮助识别渲染性能瓶颈。它可以记录组件的渲染时间,并帮助你找到可以优化的部分。
### 11. 避免内联对象和数组的传递
对于那些接收对象或数组作为 props 的组件,应避免在渲染方法中直接创建新的内联对象或数组,因为这会导致 props 始终不相等,从而触发不必要的渲染。
```javascript
<MyComponent items={[1, 2, 3]} /> // 每次渲染都会创建一个新的数组,不推荐这样做
// 更好的做法是在组件外部定义这个数组
const items = [1, 2, 3];
<MyComponent items={items} />
```
### 12. 使用键(keys)来优化列表渲染
当渲染列表时,应该为每个列表项指定一个唯一的 key。这有助于 React 确定哪些项已更改、添加或删除,从而提高列表渲染的效率。
```javascript
data.map((item) => <ListItem key={item.id} {...item} />)
```
### 13. 使用 Context 时的优化
当使用 React Context API 时,应该注意其可能对性能的影响。Context 的变动会导致所有消费该 Context 的组件重新渲染。为了避免不必要的渲染,可以分割 Context 或是使用 `useMemo` 跟 `useCallback` 来传递稳定的上下文值。
### 14. 避免过度渲染和过度传递 props
审视组件间的 props 传递,确保不会传递额外的 props。如果一个组件不需要某个 prop,那么就不应该传递它,因为这可能会导致不必要的组件渲染。
### 15. 服务器端渲染 (SSR)
服务器端渲染可以加快首次页面加载时间,并提升搜索引擎优化(SEO)。通过在服务器上生成 HTML,可以减少客户端的工作量,从而提升性能。
### 实施示例
假设我们有一个用户列表组件,其中包含大量用户数据。我们可以应用以下优化:
- 使用 `React.memo` 封装用户列表项组件,仅在 props 变化时重新渲染。
- 通过 `useMemo` 缓存用户列表计算,避免在每次渲染时重新计算。
- 使用虚拟列表库,如 `react-window`,仅渲染可视区域内的用户,以优化长列表性能。
- 如果用户列表是通过路由导航到达的,可以使用 `React.lazy` 和 `Suspense` 实现路由懒加载。
- 通过 React DevTools 的 Profiler 分析用户列表的渲染性能,找出任何潜在的性能瓶颈进行优化。
通过应用这些优化技巧,我们可以显著提高大型 React 应用的性能和用户体验。
前端 · 2024年8月5日 12:52
React hook 使用需要注意哪些?在使用 React Hooks 时需要遵循一些最佳实践和注意事项,以确保代码的可维护性与功能的正确性。以下是一些关键点:
### 1. 遵守Hooks规则
#### 不要在循环、条件或嵌套函数中调用Hooks
Hooks 应该始终在组件的顶层被调用,这样可以保证 Hooks 在每次渲染时都以相同的顺序被调用,这对于 React 的内部状态追踪机制非常重要。
#### 只在React函数中调用Hooks
应该仅在React的函数组件或自定义 Hooks 中调用 Hooks。不要在普通的 JavaScript 函数中调用。
### 2. 使用 `useState`时的注意事项
#### 初始化状态
对于复杂的状态逻辑,可以通过传递一个函数给 `useState` 来惰性初始化,这样可以避免在每次渲染时重新创建初始状态。
```jsx
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
```
#### 状态更新函数的身份稳定
`setState` 函数是身份稳定的,这意味着你可以在其他 Hooks 中安全地引用它,而不用担心它会在重新渲染时改变。
### 3. 使用 `useEffect`时的注意事项
#### 清理副作用
在 `useEffect` 中创建的订阅、定时器、监听事件等副作用,应该在返回的清理函数中进行清除,以避免内存泄漏。
```jsx
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清理订阅
subscription.unsubscribe();
};
}, [props.source]);
```
#### 依赖列表的完整性
确保依赖列表包含了所有外部作用域中被 `useEffect` 使用到的值,这样才能正确响应这些值的变化。如果忽略了依赖,可能会导致旧的闭包中的值被捕获,从而引发错误。
```jsx
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // 确保所有使用到的变量都被包含在依赖列表中
```
### 4. 避免在 `useEffect`中进行不必要的操作
#### 节流和防抖
如果 `useEffect` 中的操作非常昂贵,考虑使用节流(throttling)或防抖(debouncing)技术来减少操作的频率。
### 5. 自定义Hooks
#### 代码复用
当你发现需要在不同组件之间复用状态逻辑时,可以将其抽离成自定义 Hooks。这有助于减少代码冗余并增强逻辑的可维护性。
例如,使用自定义 `useForm` Hook 来处理表单:
```jsx
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (event) => {
setValues({
...values,
[event.target.name]: event.target.value,
});
};
return [values, handleChange];
}
```
### 6. 性能优化
#### `useMemo` 和 `useCallback`
在有必要的情况下,使用 `useMemo` 和 `useCallback` 来避免不必要的渲染或计算。`useMemo` 可以用来缓存复杂计算的结果,`useCallback` 可以用来缓存函数,这在将函数传递给子组件时特别有用,可以避免不必要的子组件重渲染。
前端 · 2024年8月5日 12:50
什么是 React 的受控组件和非受控组件?在React中,**受控组件(Controlled Components)**和**非受控组件(Uncontrolled Components)**都是处理表单输入的方式,但它们处理数据的方式不同。
### 受控组件(Controlled Components)
受控组件是React的一种模式,在这种模式下,表单数据是由React组件的状态管理的。这意味着每次字段的值发生变化时,我们都会通过一个事件处理函数(通常是 `onChange`)来更新组件的状态。然后,组件的状态被用作输入字段的值,确保组件的状态是数据的唯一来源。
**示例:**
假设我们有一个受控的 `<input>`元素:
```jsx
class ControlledComponent extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
render() {
return (
<form>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
);
}
}
```
在上面的例子中,`<input>`的值始终由 `this.state.value`决定,而且每当用户输入时,`handleChange`函数都会被调用,更新状态,因此界面显示的内容总是和状态同步。
### 非受控组件(Uncontrolled Components)
非受控组件是另一种模式,在这种模式下,表单数据是由DOM本身处理的,而不是由React状态管理。这就像传统的HTML表单工作方式。在非受控组件中,我们通常使用 `ref`来从DOM节点获取表单数据,而不是为每个状态变化编写事件处理函数。
**示例:**
下面是一个非受控组件的例子:
```jsx
class UncontrolledComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.inputRef.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.inputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
}
}
```
在上面的例子中,`<input>`不是通过状态来控制其值,而是通过 `ref`来访问DOM节点获取其值。
### 总结
- 受控组件允许你更好地控制表单的行为,因为组件的状态充当了数据的真实来源。
- 非受控组件舍弃了对表单状态的即时控制,使得组件的代码更简洁,但可能会更难管理表单的状态,尤其是在复杂的表单交互时。
在实际的开发实践中,受控组件通常是首选方法,因为它们更加符合React的数据流概念,使得状态的管理更加清晰和可预测。然而,对于一些简单的表单或者集成第三方DOM库时,非受控组件也可能是一个不错的选择。
前端 · 2024年8月5日 12:48
React 如何使用异步组件以及异步组件的使用场景React 的异步组件(通常被称为懒加载组件)主要是通过动态 `import()` 语法和 React 的 `React.lazy` 函数来实现的。它们用于在需要时才加载组件,可以显著提高应用程序的性能,尤其是当应用程序很大并且有许多不同的页面和组件时。接下来,我会详细介绍如何使用异步组件以及它们的使用场景。
### 如何使用异步组件
使用 React 异步组件的基本步骤如下:
1. 使用 `React.lazy` 函数分别导入组件。这个函数允许你定义一个动态导入的组件。该函数接受一个函数,这个函数必须调用一个 `import()`,它返回一个 `Promise`,该 `Promise` 解析为一个有 `default` 导出的模块。
```jsx
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
```
2. 将 `React.lazy` 返回的组件与 `React.Suspense` 组件结合使用。`Suspense` 组件允许你指定加载指示器(例如:加载中的旋转器),在等待异步组件加载时显示给用户。
```jsx
import React, { Suspense } from 'react';
// 异步导入组件
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</div>
);
}
```
### 使用场景
1. **性能优化:** 对于大型应用程序,将不同的页面或功能分割成独立的代码块,然后只在用户需要时才加载,可以减少应用程序的初始负载时间。
2. **条件渲染组件:** 当一个组件只在某些条件下才需要时,例如特定的用户角色或权限,可以使用异步组件按需加载,从而节省资源。
3. **路由懒加载:** 在使用如 React Router 这样的库进行路由管理时,可以结合 `React.lazy` 和 `Suspense` 来实现路由级别的懒加载。
```jsx
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense } from 'react';
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
```
4. **组件库懒加载:** 如果你的应用程序使用了庞大的第三方组件库,而只有少数组件被频繁使用,可以选择仅懒加载那些较少使用的组件,以减少初始包的大小。
使用异步组件的主要目标是提升用户体验,减少页面加载时间,并且按需加载资源,避免浪费客户端的计算和带宽资源。React 的懒加载功能是实现上述目标的重要手段之一。
前端 · 2024年8月5日 12:48
React Router 是如何配置组件的懒加载?React Router 可以通过配合 React 的 `React.lazy()` 和 `Suspense` 组件来配置组件的懒加载。以下是使用 React Router 实现懒加载的基本步骤:
1. **使用 `React.lazy` 实现动态导入**: `React.lazy()` 是一个允许你动态加载组件的函数。它可以让你定义一个动态导入的组件,并且这个组件会在首次渲染时自动加载。
```jsx
const LazyComponent = React.lazy(() => import('./LazyComponent'));
```
2. **使用 `Suspense` 组件包裹路由**: 在你的应用中,你需要使用 `Suspense` 组件来包裹懒加载的路由。`Suspense` 可以指定一个加载指示器(比如一个 spinner),它会在懒加载组件加载完成之前显示。
```jsx
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/lazy" component={LazyComponent} />
{/* 其他路由 */}
</Switch>
</Suspense>
</Router>
);
}
```
3. **为懒加载组件创建独立的 chunk**: 当你使用 `create-react-app` 或其他构建工具时,它会为每个用 `React.lazy()` 引入的组件自动创建一个独立的 JavaScript chunk 文件。这意味着这些代码只会在用户需要时才会被加载。
举个例子,假设你有一个很大的组件 `BigComponent`,你不希望它在应用首次加载时就加载进来,而是希望当用户真正访问到该组件对应的路由时再加载,你可以这样设置:
```jsx
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense } from 'react';
const BigComponent = React.lazy(() => import('./BigComponent'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading Big Component...</div>}>
<Switch>
<Route path="/big-component" component={BigComponent} />
{/* 其他路由 */}
</Switch>
</Suspense>
</Router>
);
}
```
在上述例子中,当用户访问 `/big-component` 路径时,`BigComponent` 会被动态加载。用户会看到 "Loading Big Component..." 的文本,直到 `BigComponent` 加载完成并准备好渲染。这样可以减少应用的初始加载时间,并且按需加载资源,提高性能。
前端 · 2024年8月5日 12:48
React 函数组件和 class 组件之间的区别React 函数组件和类组件是 React 中创建组件的两种不同方式。它们有几个主要区别:
1. **语法**:
- **函数组件**:使用 JavaScript 函数(或箭头函数)定义,它接收一个 `props` 参数并返回 JSX。函数组件通常更简洁。
```jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
```
- **类组件**:使用 ES6 类来定义,它扩展自 `React.Component`,必须包含一个 `render()` 方法,该方法返回 JSX。
```jsx
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
```
2. **状态管理**:
- **函数组件**:在 React 16.8 之前,函数组件不具备状态(state)和生命周期方法。但随着 React Hooks 的引入,函数组件可以使用 `useState` 和其他 Hooks 来管理状态和生命周期。
- **类组件**:具有内置的状态和生命周期方法。它们使用 `this.state` 和 `this.setState` 来管理组件的状态,以及一系列的生命周期函数(如 `componentDidMount`,`componentShouldUpdate` 等)来执行副作用操作。
3. **生命周期方法**:
- **函数组件**:通过使用 React Hooks(如 `useEffect`),函数组件可以执行与类组件生命周期方法相似的操作,但它们不直接拥有生命周期方法。
- **类组件**:具有完整的生命周期方法,可以在组件的不同阶段执行代码。
4. **`this` 关键字**:
- **函数组件**:不使用 `this` 关键字。所有的数据(包括 props 和 state)都通过函数参数或 Hooks 访问。
- **类组件**:需要使用 `this` 关键字来访问 props、state 和类方法。
5. **优化性能**:
- **函数组件**:因为它们没有类实例,理论上可以更轻量。并且可以通过使用 `React.memo` 进行性能优化。
- **类组件**:可以使用 `shouldComponentUpdate` 或 `PureComponent` 来优化性能,但这些通常比函数组件中的优化方法更复杂。
6. **钩子(Hooks)**:
- **函数组件**:可以使用 Hooks,如 `useState`、`useEffect` 等,使得在不使用类的情况下也能拥有类似的功能。
- **类组件**:无法使用 Hooks,必须依靠类本身的特性和生命周期。
7. **部署和维护**:
- **函数组件**:通常来说,由于它们更加简洁,函数组件更容易编写和维护。它们也更容易分割成更小的函数。
- **类组件**:可能会更加冗长,特别是当涉及到多个生命周期方法和状态管理时,这可能使得维护和重构变得更加困难。
8. **代码复用**:
- **函数组件**:可以通过自定义 Hooks 实现逻辑的复用。
- **类组件**:通常通过高阶组件(HOCs)或渲染道具(Render Props)来实现逻辑的复用。
前端 · 2024年8月5日 12:43
React 的合成事件的原理是什么?React 的合成事件(SyntheticEvent)是 React 为了跨浏览器兼容性而实现的一个事件封装。合成事件的原理可以总结为以下几个关键点:
### 1. 事件封装
React 为了解决原生事件在不同浏览器中的兼容问题,实现了一套自己的事件系统。这个系统模拟原生事件系统,但是提供了一致的接口和行为。当事件发生时(如用户点击一个按钮),React 会创建一个合成事件的实例,这个实例包含了所有事件的信息,无论在哪个浏览器上。
### 2. 事件冒泡
在 React 中,所有的事件都会自动应用事件冒泡(bubbling),即事件会从触发它的最深的节点开始,逐层向上传播到最外层的节点。合成事件同样遵循这个机制,这意味着您只需要在一个高层节点上监听事件,就能处理下层节点的相应事件。
### 3. 事件委托
React 并不会将事件处理器直接绑定到真实的 DOM 元素上,而是使用了一种叫做事件委托的技术。React 在最顶层的文档节点上(通常是`document`)添加了一个单一的事件监听器,用来监听所有支持的事件类型。当一个事件发生时,React 会根据这个事件的目标和冒泡路径,来决定哪些注册的事件处理器需要被调用。
### 4. 合成事件对象池
出于性能考虑,React 实现了一个合成事件的对象池。每当一个事件发生并且事件处理器被调用时,React 从池中分配一个合成事件对象,并填充事件的相关信息。一旦事件处理器被调用,这个对象会被清空,并回收到池中以供后续的事件重复使用。这个过程减少了垃圾回收的压力和合成事件对象的创建成本。
### 5. 与原生事件的关系
尽管 React 使用了合成事件,但它仍然是基于原生事件的。当原生事件被触发时,React 的事件委托机制会处理这个事件,并创建一个合成事件传递给相应的事件处理器。开发者在编写事件处理函数时,操作的是由 React 提供的合成事件,而不是直接操作原生 DOM 事件。
### 实例
假设我们有一个按钮的点击事件,我们希望在点击时打印出事件对象:
```jsx
class MyComponent extends React.Component {
handleClick = (event) => {
console.log(event); // 这里的 event 是一个 SyntheticEvent 的实例
console.log(event.nativeEvent); // 这里可以访问原生的 DOM 事件对象
};
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
```
这段代码中,`handleClick` 方法接收一个合成事件的实例。我们可以访问此对象的所有属性和方法,如同处理一个原生事件一样。但由于它是合成的,它在所有浏览器中的行为都是一致的。
综上所述,React 的合成事件系统提供了一种高效且一致的方式来处理浏览器间的事件差异,并优化了性能,同时简化了事件处理的复杂性。
前端 · 2024年8月5日 12:43
React 的 vdom 是什么?以及虚拟DOM 是如何做 diff 算法的?React 的虚拟DOM(VDOM)是React用于提升应用性能的核心概念之一。它是对真实DOM的一个轻量级抽象。虚拟DOM本质上是一个JavaScript对象,它是真实DOM结构的一个简化版本。React使用虚拟DOM来模拟真实DOM的更新,这样就可以最小化对真实DOM的操作,因为真实DOM操作的开销通常比较大。
当组件的状态变化时,React会创建一个新的虚拟DOM树并将其与上一次的虚拟DOM树进行比较。这个过程被称为Diff算法。通过Diff算法,React可以确定实际DOM需要进行的最小更新。以下是Diff算法的简要步骤:
1. **树的比较**:React首先比较两棵树的根节点,如果根节点的类型不同(例如从`<div>`变到`<span>`),React会销毁旧树并建立一棵新树。如果类型相同,则保留根节点,并继续进行递归比较。
2. **组件的比较**:如果是React组件节点,React会检查组件的类型是否相同。如果相同,组件将接收新的props并重新渲染。然后,React会比较返回的虚拟DOM。
3. **子元素的比较**:当比较两个相同类型的元素时,React会继续比较它们的子元素。React有两种不同的策略来比较子元素:
- **同层比较**:React只比较同一层级的子元素。如果在不同层级有相同的元素,React不会尝试复用这些元素。
- **key属性**:当开发者提供了key属性时,React会使用这个key来匹配旧的虚拟DOM树中的元素和新的虚拟DOM树中的元素。这有助于保持状态和提高性能,特别是在处理列表时。
4. **更新DOM**:一旦Diff算法确定了需要变更的最小部分,React会批量执行这些更新,尽量减少对真实DOM的操作,从而提高性能。
**例子**:
假设有一个列表,列表项组件`<ListItem />`有一个唯一的`key`属性,并且列表的状态更新导致列表项的顺序颠倒。由于每个`<ListItem />`都有唯一的`key`,React能够识别出这些组件只是顺序改变了,而不是完全不同的组件。因此,React仅会改变DOM中这些列表项的顺序,而不是销毁整个列表并重新创建,这大大提高了性能。
**总结**:
React的虚拟DOM和Diff算法共同工作,以提供高效的更新机制。虚拟DOM使得React可以在内存中进行计算,而Diff算法确保只对真实DOM做必要的、最小的修改。这种机制使得React在处理大型、动态的应用时能够保持良好的性能。
前端 · 2024年8月5日 12:43