面试回顾
1. 说一下vue和react两个框架的区别
Vue 和 React 是两个流行的前端框架,它们有许多相同点和不同点,让我们来一一分析:
相同点:
组件化:
- Vue 和 React 都支持组件化开发思想,将界面拆分为独立可复用的组件,使得代码结构更清晰、可维护性更强。
虚拟DOM:
- 两者都使用虚拟DOM(Virtual DOM)来提高渲染效率,通过比较虚拟DOM树的差异,最小化实际DOM操作,从而提升性能。
响应式数据绑定:
- Vue 和 React 都支持响应式数据绑定,当数据发生变化时,自动更新视图,简化了状态管理和视图更新的复杂性。
单文件组件:
- Vue 和 React 都支持单文件组件(Single File Components),将组件的结构、样式和逻辑封装在一个文件中,提高了开发效率和组件复用性。
社区和生态系统:
- 两者都拥有活跃的社区和丰富的生态系统,提供了大量的第三方库、插件和工具,支持开发者快速构建复杂的单页面应用(SPA)和大型应用。
不同点:
学习曲线:
- Vue 的学习曲线相对较低,因其设计理念更贴近传统的HTML、CSS和JavaScript开发方式,易于上手。
- React 则需要更深入理解其虚拟DOM、JSX语法等概念,对于初学者可能有一定的学习门槛。
灵活性:
- React 更加灵活,更多的决策留给开发者,例如状态管理可以选择使用 Context API、Redux 等,路由可以选择 React Router 等。
- Vue 在设计上提供了更多的约定和内置功能,降低了开发者在做技术选型上的负担,有利于快速开发。
生态系统和工具链:
- React 生态系统更加庞大和成熟,拥有大量的第三方库和工具支持,适合构建大型应用和复杂的组件库。
- Vue 生态系统虽然也很活跃,并且有自己的一些优秀工具(如Vue Router、Vuex等),但整体上规模较React稍小。
模板语法 vs JSX:
- Vue 使用基于HTML的模板语法,可以更快速地编写和理解模板,尤其适合传统前端开发者。
- React 使用JSX(JavaScript XML),允许在JavaScript中直接编写类HTML代码,提供了更高的灵活性和表达能力。
状态管理:
- React 中常用的状态管理工具包括 Context API 和 Redux,可以选择更适合项目规模的状态管理方案。
- Vue 原生支持响应式数据和Vuex状态管理库,使得状态管理在大多数场景下更加简洁和直观。
总体而言,选择 Vue 还是 React 取决于项目需求、团队技术栈和开发者个人偏好。Vue 更加直观和容易上手,适合中小型项目和快速原型开发;React 则更灵活和适合构建复杂、大规模应用。
2. 如何使用 useRef 和 useEffect 来模拟 componentWillUpdate
import React, { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 使用 useRef 来存储之前的 count 值
const prevCountRef = useRef<number | undefined>();
// useEffect 在组件渲染后执行,这里用来更新 prevCountRef 的值
useEffect(() => {
prevCountRef.current = count;
});
// 获取之前的 count 值
const prevCount = prevCountRef.current;
useEffect(() => {
if (prevCount !== undefined) {
console.log('Component will update');
console.log('Previous count:', prevCount);
console.log('Current count:', count);
}
}, [count]); // 依赖数组包含 count,表示 count 变化时调用此 useEffect
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default MyComponent;
3. HTML 渲染是一个复杂的过程,涉及多个阶段,从浏览器接收到 HTML 文件到最终在屏幕上显示内容。下面是详细的渲染全过程:
1.加载阶段
浏览器首先加载HTML文件,并开始解析它。
- 浏览器发起HTTP请求,获取HTML文件。
- 浏览器将HTML文件解析为DOM树。
2.构建DOM树
解析阶段完成后,浏览器将DOM树构建为一棵完整的树结构。
- 浏览器根据HTML文件中的标签、属性和内容,创建相应的DOM节点。
- 构建完成后,DOM树的结构如下:
<html>
<head>
<title>My Website</title>
<link href="xxx.css">
<style>
.box1 {
color: red;
}
</style>
</head>
<body>
<div id='box' class='box1'>我是一个盒子</div>
</body>
<script>
// 执行一些JavaScript代码
</script>
</html>
3.解析CSS
在解析HTML的过程中,浏览器还会解析CSS、
- 下载css
- 当浏览器遇到
<link>
标签或<style>
标签时,会下载外部CSS文件或解析内部CSS
- 当浏览器遇到
- 构建CSSOM树
- CSS被解析成CSSOM(CSS Object Model)树,表示样式信息
4.解析JavaScript
javaScript可以改变HTML和CSS
- 下载JavaScript
- 当浏览器遇到
<script>
标签时,会下载外部JavaScript文件或内部脚本
- 当浏览器遇到
- 阻塞渲染
- 默认情况下,JavaScript的执行会阻塞HTML的解析和DOM树的构建,因为脚本可能会修改DOM树
- 执行脚本
- 执行JavaScript代码,可能会修改DOM和CSSOM
5.构建渲染树
解析阶段完成后,浏览器会构建渲染树,将DOM树和CSSOM树结合起来,形成一棵渲染树。
- 生成渲染树:
- 渲染树由可见的DOM树节点和对应的CSSOM样式节点组成
- 不可见的节点(如:
head
和display: none
的元素)不会出现在渲染树中
6. 布局(Layout)
浏览器计算每个节点的大小和位置
- 计算几何属性
- 布局阶段: 浏览器会根据渲染树计算每个元素的几何属性(位置、大小、边距等)
- 流式布局:
- 大多数情况下,浏览器采用流式布局模型,自上而下,从左到右地计算布局
7. 绘制(Paint)
浏览器将渲染树的内容绘制到屏幕上
- 绘制阶段
- 浏览器将各个节点的几何属性和样式转换成像素,绘制到屏幕上的多个层(layers)
8. 合成
合成阶段是在绘制阶段之后,浏览器将多个层合成成一个层,以提高性能和用户体验
- 合成阶段
- 浏览器将多个层合并成一个层,以提高性能和用户体验
- 这一步通常由GPU完成,特别是对于复杂的动画和变换
9. 显示
显示阶段是在合成阶段之后,浏览器将最终的图像显示在屏幕上
10. 总结
具体的渲染过程总结:
- 加载HTML文件:浏览器发起HTTP请求,获取HTML文件。
- 构建DOM树:浏览器根据HTML文件中的标签、属性和内容,创建相应的DOM节点。
- 解析CSS:浏览器下载外部CSS文件或解析内部CSS,构建CSSOM树。
- 解析JavaScript:下载并执行JavaScript代码,可能会修改DOM和CSSOM。
- 构建渲染树:结合DOM和CSSOM,生成渲染树。
- 布局:计算渲染树中的每个节点的几何属性。
- 绘制:将节点转换成像素,绘制到屏幕上。
- 合成:将多个层合并成一个层,形成最终的屏幕图像。
渲染过程中需要注意的点
- 渲染阻塞:JavaScript和CSS的下载和解析可能会阻塞HTML的解析,影响页面渲染速度。
- 异步加载:可以使用
async
和defer
属性异步加载JavaScript
,优化渲染性能。 - 优化布局:减少重排和重绘,使用有效的CSS选择器,优化DOM操作,避免频繁的样式修改。
4. 渲染阻塞:JavaScript 和 CSS 的下载和解析如何阻塞 HTML 的解析
JavaScript 的阻塞行为
JavaScript 的下载和解析会阻塞 HTML 的解析,因为 JavaScript 可以通过 DOM API 操作和修改 HTML 和 CSS。因此,浏览器在遇到 <script>
标签时,必须暂停解析 HTML,直到 JavaScript 被下载、解析并执行完毕。
默认情况下:
- 当浏览器遇到一个
<script>
标签时,会停止 HTML 的解析,并发起 HTTP 请求下载 JavaScript 文件。 - 下载完成后,浏览器会解析并执行 JavaScript 代码。
- 只有当 JavaScript 执行完毕后,浏览器才会继续解析剩余的 HTML。
- 当浏览器遇到一个
原因:
- JavaScript 可能会修改 DOM 结构(例如,添加或删除元素)。
- JavaScript 可能会修改 CSS 样式(例如,通过更改类名或内联样式)。
- 因此,为了确保页面的正确渲染,浏览器必须在执行 JavaScript 之前完成这些操作。
CSS 的阻塞行为
CSS 的下载和解析会阻塞页面的渲染,但不会阻塞 HTML 的解析。浏览器在遇到 <link>
标签或 <style>
标签时,会立即下载并解析 CSS 文件,但 HTML 的解析会继续进行。尽管如此,浏览器在完成 CSS 解析之前不会渲染任何内容。
- 原因:
- CSS 影响页面的呈现和布局,浏览器需要确保所有的样式信息都已经解析完毕后再进行渲染。
- 如果没有完全解析 CSS 就开始渲染,可能会导致页面在渲染过程中出现明显的样式变化,影响用户体验。
异步加载 JavaScript:async 和 defer 属性
为了避免 JavaScript 阻塞 HTML 的解析,可以使用 async
和 defer
属性,这两个属性允许 JavaScript 文件异步加载。
async
属性
- 用法:
<script src="script.js" async></script>
- 行为:
- 浏览器在解析 HTML 时,遇到带有
async
属性的<script>
标签时,会立即开始下载 JavaScript 文件,同时继续解析 HTML。 - JavaScript 文件下载完成后会立即执行,可能会打断 HTML 的解析。
- 浏览器在解析 HTML 时,遇到带有
- 适用场景:适用于不依赖于其他脚本或 DOM 内容的独立脚本。
defer
属性
- 用法:
<script src="script.js" defer></script>
- 行为:
- 浏览器在解析 HTML 时,遇到带有
defer
属性的<script>
标签时,会立即开始下载 JavaScript 文件,同时继续解析 HTML。 - 所有的
defer
脚本会按照它们在 HTML 中出现的顺序,在 HTML 解析完毕后,DOMContentLoaded
事件之前执行。
- 浏览器在解析 HTML 时,遇到带有
- 适用场景:适用于需要在 DOM 准备就绪后执行的脚本,尤其是依赖于 DOM 内容或其他脚本的脚本。
preload
和 lazy
属性的具体情况
preload
属性
preload
是一个 <link>
标签的属性,用于提示浏览器在需要之前下载资源,以提高页面加载性能。
- 用法:
<link rel="preload" href="script.js" as="script">
- 行为:
- 浏览器会在解析 HTML 时,预先下载
preload
指定的资源,但不会立即执行。 - 当需要时,这些资源会立即可用,减少加载时间。
- 浏览器会在解析 HTML 时,预先下载
- 适用场景:适用于希望提前下载但稍后执行的资源,如关键的 JavaScript 文件或字体文件。
lazy
属性
lazy
是用于懒加载资源的属性,主要用于图像和 iframe。
- 用法:
<img src="image.jpg" loading="lazy">
- 行为:
- 浏览器会延迟加载
lazy
属性指定的资源,直到用户即将滚动到它们。 - 这可以显著减少初始页面加载时间和流量消耗。
- 浏览器会延迟加载
- 适用场景:适用于延迟加载的图像、iframe 等,直到用户即将查看它们。
总结
- JavaScript 和 CSS 阻塞:JavaScript 的默认行为会阻塞 HTML 的解析,因为它可能修改 DOM 和 CSS。CSS 的解析会阻塞页面的渲染,但不会阻塞 HTML 的解析。
- 异步加载:使用
async
和defer
属性可以避免 JavaScript 阻塞 HTML 的解析,async
适用于独立脚本,defer
适用于需要 DOM 准备就绪后执行的脚本。 - 预加载和懒加载:使用
preload
可以预先下载关键资源,提高页面加载性能;使用lazy
可以延迟加载非关键资源,减少初始页面加载时间。
参考资料
MDN script标签script标签属性 MDN link标签link标签属性
5. React 通常不被认为是双向数据绑定的框架
主要是因为它的数据流和状态管理模式与传统的双向绑定框架(如 AngularJS)不同。以下是详细原因和解释:
1. 单向数据流
React 采用单向数据流(单向绑定),这意味着数据从父组件流向子组件,通过 props 传递。任何状态的变化都会触发重新渲染,但数据总是从顶层流向底层,而不是双向绑定。
- 单向数据流的好处:
- 易于调试和跟踪:数据流动方向单一,易于追踪状态变化和调试。
- 状态管理清晰:组件之间的数据依赖关系简单,易于管理和维护。
2. 状态管理
在 React 中,状态(state)通常保存在组件内部或通过状态管理库(如 Redux、MobX、Recoil 等)集中管理。组件的状态变化通常通过事件处理函数(如 onClick、onChange)来触发,而不是通过双向绑定的方式。
- 状态更新机制:
- 组件内部状态通过
this.setState
更新。 - 全局状态通过状态管理库中的动作(actions)和 reducers 更新。
- 组件内部状态通过
3. 双向绑定的定义
双向绑定指的是模型和视图之间的自动同步机制,即视图的变化会自动更新模型,模型的变化也会自动更新视图。AngularJS 是一个典型的双向绑定框架,在数据绑定上提供了更复杂的机制。
- 双向绑定的实现:
- 视图(View)和模型(Model)之间存在直接的绑定关系。
- 视图的变化会自动更新模型,反之亦然。
4. React 中的数据绑定
React 中的数据绑定是单向的,即使在表单输入处理中,也不是传统的双向绑定。
- 表单处理:
- React 中表单输入的处理通常通过受控组件(controlled components)实现。受控组件的值由 React 组件的状态管理,输入变化通过事件处理函数(如 onChange)来更新状态。
- 非受控组件(uncontrolled components)则通过 ref 直接访问 DOM 元素的值,但这也不是双向绑定。
示例:React 中的受控组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
handleChange = (event) => {
this.setState({ value: event.target.value });
};
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} />
);
}
}
在上述示例中,输入框的值(value)由组件状态(state)控制。用户在输入框中的操作触发 handleChange
事件处理函数,进而更新组件状态。
结论
React 不被认为是双向数据绑定框架,主要原因在于其采用了单向数据流和组件状态管理的模式。React 的设计理念强调数据自上而下的单向流动,这使得状态管理更加清晰、易于调试和维护。尽管可以通过受控组件实现类似双向绑定的效果,但这并不是 React 的核心设计模式。
6. Vue 和 React 都有 ref
属性,用于访问 DOM 元素或组件实例。尽管它们在某些方面具有相似性,但在用法和实现细节上也有显著的不同。下面是它们的相同点和不同点的详细比较:
相同点
直接访问 DOM 元素:
- 都允许开发者直接访问 DOM 元素,以便进行一些直接的 DOM 操作或与第三方库集成。
引用组件实例:
- 都可以用来获取组件实例的引用,允许访问组件实例的方法或属性。
不同点
Vue 中的 ref
定义和使用:
- 在 Vue 模板中,使用
ref
属性定义引用。 - 在 Vue 3 中,通过
this.$refs
访问模板中的引用,或者在组合 API 中通过ref
函数创建响应式引用。
- 在 Vue 模板中,使用
访问方式:
- 在 Vue 2 中,通过
this.$refs
访问。 - 在 Vue 3 中,结合组合 API,可以使用
ref
和onMounted
等钩子访问。
- 在 Vue 2 中,通过
响应式:
- Vue 3 中的
ref
可以创建响应式数据,并在组合 API 中使用。
- Vue 3 中的
示例:
vue<!-- Vue 2 组件 --> <template> <div> <input ref="myInput"> </div> </template> <script> export default { mounted() { this.$refs.myInput.focus(); } } </script> <!-- Vue 3 组件 --> <template> <div> <input ref="myInput"> </div> </template> <script> import { ref, onMounted } from 'vue'; export default { setup() { const myInput = ref(null); onMounted(() => { myInput.value.focus(); }); return { myInput }; } } </script>
React 中的 ref
定义和使用:
- 在 React 中,使用
React.createRef
或useRef
(在函数组件中)来创建引用。 - 将
ref
属性添加到 JSX 元素或组件中。
- 在 React 中,使用
访问方式:
- 在类组件中,通过
this.myRef.current
访问。 - 在函数组件中,通过
myRef.current
访问。
- 在类组件中,通过
非响应式:
- React 的
ref
本身并不是响应式的,不会触发重新渲染。
- React 的
示例:
jsx// 类组件 class MyComponent extends React.Component { constructor(props) { super(props); this.myInput = React.createRef(); } componentDidMount() { this.myInput.current.focus(); } render() { return <input ref={this.myInput} />; } } // 函数组件 import React, { useRef, useEffect } from 'react'; function MyComponent() { const myInput = useRef(null); useEffect(() => { myInput.current.focus(); }, []); return <input ref={myInput} />; }
总结
相同点:
- Vue 和 React 都使用
ref
来直接访问 DOM 元素或组件实例。 - 都可以在组件的生命周期钩子(Vue 的
mounted
,React 的componentDidMount
和useEffect
)中使用ref
。
- Vue 和 React 都使用
不同点:
- Vue 的
ref
在 Vue 3 中结合组合 API 可以创建响应式数据,而 React 的ref
不具备响应式特性。 - Vue 使用模板语法定义
ref
,通过this.$refs
或响应式引用访问;React 使用React.createRef
或useRef
创建引用,通过.current
访问。 - Vue 的
ref
属性更多地与模板绑定,React 的ref
属性则与 JSX 元素和组件直接绑定。
- Vue 的
通过理解这些相同点和不同点,可以更好地在项目中选择和使用 ref
,以实现需要的功能。
7. Promise原理
Promise 是一种用于处理异步操作的 JavaScript 对象,它提供了一种更优雅和强大的方式来处理异步代码。Promise 的原理涉及到其内部的状态管理和链式调用机制。
原理概述
状态:
- Promise 对象有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。
- 初始状态为 Pending,可以转变为 Fulfilled 或 Rejected。
状态转换:
- 当异步操作成功完成时,Promise 从 Pending 转变为 Fulfilled,表示操作成功,并携带返回的值。
- 当异步操作失败时,Promise 从 Pending 转变为 Rejected,表示操作失败,并携带错误信息。
链式调用:
- Promise 可以通过
.then()
方法进行链式调用,每个.then()
方法返回一个新的 Promise。 - 如果前一个 Promise 状态为 Fulfilled,则执行当前
.then()
的成功处理函数;如果状态为 Rejected,则执行当前.then()
的失败处理函数。 .then()
方法可以被多次调用,形成链式操作。
- Promise 可以通过
异步执行:
- Promise 内部的任务是异步执行的,即使是同步代码也会被放入事件循环队列中执行。
Promise 的实现(简化版)
以下是一个简化版的 Promise 实现,用于说明其基本原理:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof MyPromise) {
if (x.state === 'pending') {
x.then(y => {
resolvePromise(promise2, y, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, e => {
if (called) return;
called = true;
reject(e);
});
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
简要解释
- 构造函数:Promise 构造函数接收一个执行器函数(executor),它立即执行,并接收两个参数
resolve
和reject
。 - 状态管理:Promise 内部通过
this.state
和this.value
(成功值)或this.reason
(失败原因)来管理状态和结果。 - 链式调用:通过
then
方法实现链式调用,每个.then()
方法返回一个新的 Promise,可以通过resolve
和reject
控制状态转换。 - 异步执行:Promise 内部使用
setTimeout
模拟异步执行,确保每个.then()
方法都是异步执行的。
总结
Promise 的核心原理在于状态管理、链式调用和异步执行。它通过明确的状态转换和 .then()
方法的链式调用,提供了一种处理异步操作的优雅方式,避免了回调地狱(callback hell),使得异步代码更加清晰和可维护。
8. React.memo
和 useMemo
在 React 中都用于性能优化,但它们的作用和使用场景有所不同。
React.memo
React.memo
是一个高阶组件,用于函数组件的性能优化。它的作用是在组件 props 没有变化时,避免不必要的重新渲染,从而提升性能。
用法:
jsxconst MemoizedComponent = React.memo(MyComponent);
或者
jsxconst MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => { // 返回 true 表示 props 没有变化,组件不需要重新渲染 return prevProps.id === nextProps.id; });
作用:
- 当组件的 props 没有变化时,
React.memo
返回的组件会使用之前渲染的结果,避免重新执行组件的函数体和重新渲染。
- 当组件的 props 没有变化时,
适用场景:
- 当函数组件的渲染代价较高,但只有部分 props 变化时,可以使用
React.memo
进行优化。
- 当函数组件的渲染代价较高,但只有部分 props 变化时,可以使用
useMemo
useMemo
是一个 Hook,用于在渲染过程中缓存计算结果,避免重复计算。
用法:
jsxconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
作用:
useMemo
接收一个函数和依赖数组[a, b]
,只有当依赖数组中的某个值发生变化时,才会重新计算函数的返回值。- 返回值是计算结果的缓存,可以是任何类型的数据,不仅限于组件。
适用场景:
- 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用
useMemo
缓存计算结果,避免在每次渲染时都进行重复计算。
- 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用
区别总结
对象:
React.memo
是一个高阶组件,用于函数组件的性能优化,比较组件的 props 是否变化。useMemo
是一个 Hook,用于缓存计算结果,在依赖变化时重新计算。
作用:
React.memo
优化组件的渲染,避免不必要的重新渲染。useMemo
缓存计算结果,优化性能,避免重复计算。
适用场景:
- 使用
React.memo
当组件渲染开销大且只有部分 props 变化时。 - 使用
useMemo
当有复杂计算或数据转换逻辑时,需要缓存计算结果。
- 使用
通过合理使用 React.memo
和 useMemo
,可以有效地优化 React 应用的性能,提升用户体验和页面响应速度。
9. React 中常用的 Hooks 的作用和应用场景:
1. useContext
- 作用:用于在函数组件中访问 React 上下文(Context),获取全局数据。
- 应用场景:
- 在组件树较深的情况下,避免通过 props 层层传递数据,直接从 Context 中获取全局数据。
- 全局主题、用户信息等常用数据的传递和访问。
示例:
import React, { useContext } from 'react';
// 创建上下文
const ThemeContext = React.createContext('light');
// 使用 useContext 获取上下文中的值
function ThemeButton() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme }}>Button</button>;
}
2. useReducer
- 作用:类似于 Redux 中的 reducer,用于管理组件的复杂状态逻辑。
- 应用场景:
- 当组件状态具有复杂的逻辑关系或需要多个状态变量联动时,可以使用
useReducer
管理状态。 - 替代
useState
在多个状态之间切换和管理。
- 当组件状态具有复杂的逻辑关系或需要多个状态变量联动时,可以使用
示例:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
3. useRef
- 作用:用于在函数组件中创建持久性的引用,类似于在类组件中使用
this.refs
。 - 应用场景:
- 获取 DOM 元素的引用,进行 DOM 操作。
- 缓存任何可变的值,类似于实例变量的用法。
示例:
import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}
4. useLayoutEffect
- 作用:与
useEffect
类似,但是会在所有 DOM 变更之后同步调用 effect,可以立即读取 DOM 布局。 - 应用场景:
- 当需要在浏览器 layout(布局)之后立即执行某些操作时,特别是对 DOM 进行测量或窗口大小调整等操作。
示例:
import React, { useState, useLayoutEffect } from 'react';
function useWindowSize() {
const [size, setSize] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
return size;
}
5. useMemo
- 作用:用于缓存计算结果,避免在每次渲染时重新计算。
- 应用场景:
- 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用
useMemo
缓存计算结果,提高性能。
- 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用
示例:
import React, { useMemo } from 'react';
function computeExpensiveValue(a, b) {
// 模拟昂贵的计算
return a + b;
}
function MyComponent({ a, b }) {
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return <div>Result: {memoizedValue}</div>;
}
6. useCallback
- 作用:缓存回调函数,避免在每次渲染时重新创建回调函数。
- 应用场景:
- 当将回调函数传递给子组件时,可以使用
useCallback
缓存回调函数,避免不必要的子组件重新渲染。
- 当将回调函数传递给子组件时,可以使用
示例:
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<ChildComponent onClick={handleClick} />
<p>Clicked {count} times</p>
</div>
);
}
function ChildComponent({ onClick }) {
return <button onClick={onClick}>Click Me</button>;
}
7. useState
- 作用:用于在函数组件中添加状态。
- 应用场景:
- 组件内部需要存储和更新的状态数据。
示例:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
Count: {count}
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
8. useEffect
- 作用:在组件渲染完成后执行副作用操作。
- 应用场景:
- 数据获取、DOM 操作、订阅和取消订阅等副作用操作。
示例:
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return () => clearInterval(interval);
}, [seconds]);
return <div>Seconds: {seconds}</div>;
}
这些 React Hooks 在不同的场景下提供了灵活和强大的功能,能够有效地优化和管理 React 组件的状态、副作用和性能,是现代 React 开发中不可或缺的工具。