React
32736字约109分钟
2025-02-11
如果 React 组件的属性没有传值,它的默认值是什么?
详情
在 React 中,如果组件的属性(props)没有传值,它的默认值是 undefined。不过 React 提供了几种方式来设置默认值:
1)使用函数组件时的默认值设置
可以通过解构赋值时设置默认值:
function Button({ text = '点击', type = 'primary' }) {
return <button className={type}>{text}</button>;
}
2)使用 defaultProps(类组件)
可以在组件类上使用 defaultProps 属性来设置默认值:
class Button extends React.Component {
render() {
return <button>{this.props.text}</button>;
}
}
Button.defaultProps = {
text: '点击'
};
React 中除了在构造函数中绑定 this,还有其他绑定 this 的方式么?
详情
在 React 类组件中,除了在构造函数中绑定 this
,还有以下几种绑定 this
的方式:
1)使用箭头函数定义类方法
class Button extends React.Component {
handleClick = () => {
console.log('this is:', this);
}
render() {
return <button onClick={this.handleClick}>点击</button>;
}
}
2)在 render 方法中使用箭头函数
class Button extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
return <button onClick={() => this.handleClick()}>点击</button>;
}
}
3)使用 bind 方法在 render 中绑定
class Button extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
return <button onClick={this.handleClick.bind(this)}>点击</button>;
}
}
如何在 React 中引入其他 UI 库,比如 tailwind?
详情
在 React 中引入 UI 库(如 Tailwind)主要有以下几种方式:
- 1)使用 npm 安装并配置
- 2)使用 CDN 方式
- 3)CSS Modules 方式
为什么在 React 中遍历时不建议使用索引作为唯一的 key 值?
详情
在 React 中,当你使用 map
等方法遍历数组来渲染多个组件时,需要为每个组件提供一个唯一的 key
值。虽然使用数组的索引作为 key
是一种简单的做法,但通常不建议这样做,主要有以下几个原因:
1. 重新排序问题
当数组中的元素顺序发生变化时,使用索引作为 key
会导致 React 错误地识别组件。React 依赖 key
来识别哪些元素发生了变化、添加或删除。如果使用索引作为 key
,元素顺序的改变会使得 key
与元素的对应关系混乱,React 可能会复用错误的组件实例,从而导致性能问题和渲染错误。
2. 插入或删除元素问题
当在数组中插入或删除元素时,使用索引作为 key
会导致后续元素的 key
发生变化。这会使得 React 认为后续元素都发生了变化,从而重新渲染这些元素,即使它们实际上并没有改变。
3. 状态丢失问题
如果组件依赖于 key
来保持其内部状态,使用索引作为 key
可能会导致状态丢失。当元素的索引发生变化时,React 会认为这是一个新的组件实例,从而重置组件的状态。
React Router 中的 Router 组件有几种类型?
详情
在 React Router 中,Router 组件主要有以下几种类型,不同类型适用于不同的应用场景,以下分别介绍 React Router v5 和 React Router v6 版本中的 Router 组件类型:
1)React Router v5
在 React Router v5 中,常见的 Router 组件类型如下:
1. BrowserRouter:
- 基于 HTML5 的 history API(pushState、replaceState 和 popstate 事件)来实现路由功能。
- 适用于支持 HTML5 history API 的现代浏览器,提供美观的 URL 路径。
2. HashRouter:
- 使用 URL 的哈希部分(即 # 后面的内容)来实现路由功能。
- 适用于不支持 HTML5 history API 的旧浏览器,或者在某些环境中无法配置服务器来处理单页面应用的情况。
3. MemoryRouter:
- 将路由历史记录保存在内存中,而不是 URL 中。
- 适用于测试环境或者非浏览器环境,如 React Native 应用。
4. StaticRouter:
用于服务器端渲染(SSR),在服务器端根据请求的 URL 来渲染相应的组件。
React Router v6
在 React Router v6 中,BrowserRouter
、HashRouter
、MemoryRouter
依然存在,并且使用方式基本保持一致,不过在 API 上有一些变化,例如 Switch 被 Routes 所替代。
React 的 constructor 和 getInitialState 有什么区别?
详情
在 React 中,constructor
和 getInitialState
都与组件的初始化状态相关,但它们在不同的 React 版本中有不同的使用方式和特点,以下是详细的区别:
1. 版本适用性
constructor
:是 ES6 类组件的一部分,在 React 0.13.0 版本引入 ES6 类组件后开始使用。从 React 16.x 版本开始,ES6 类组件成为主流的组件定义方式,constructor
广泛应用于初始化组件的状态和绑定方法。getInitialState
:是React.createClass
创建组件时使用的方法,在早期的 React 版本(React 0.13.0 之前)中使用。随着 React 逐渐向 ES6 类组件过渡,getInitialState
已经不再推荐使用。
2. 语法和使用方式
constructor
:是 ES6 类的构造函数,需要在组件类中显式定义。在构造函数中,可以通过this.state
来初始化组件的状态,同时也可以进行方法的绑定。
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
count: 0
};
// 绑定方法
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
}
getInitialState
:是React.createClass
创建组件时的一个方法,用于返回组件的初始状态。在使用React.createClass
创建组件时,getInitialState
会自动调用并将返回值作为组件的初始状态。
const MyComponent = React.createClass({
getInitialState() {
return {
count: 0
};
},
handleClick() {
this.setState({ count: this.state.count + 1 });
},
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
});
3. 调用时机
constructor
:在组件实例化时立即调用,是组件生命周期中最早执行的方法。在constructor
中可以访问props
,并根据props
来初始化状态。getInitialState
:在组件实例化后,render
方法调用之前调用。它在组件创建时自动执行,用于设置组件的初始状态。
4. 注意事项
constructor
:在使用constructor
时,需要注意调用super(props)
,以确保this.props
在构造函数中可用。同时,避免在构造函数中使用setState
,因为构造函数只在组件实例化时执行一次,使用setState
会导致额外的渲染。getInitialState
:由于React.createClass
已经被弃用,getInitialState
也不再推荐使用。建议使用 ES6 类组件和constructor
来初始化组件的状态。
在 React 的 render 函数中,是否可以直接写 if else 判断?为什么?
详情
在 React 的 render
函数中是可以直接写 if else
判断的。以下为你详细分析原因和示例:
原因
render
函数本质上就是一个 JavaScript 函数,在 JavaScript 里,if else
语句属于基础的控制流结构,可依据条件来决定代码的执行路径。由于 render
函数是用 JavaScript 编写的,所以完全能够在其中使用 if else
判断语句,以此根据不同的条件渲染不同的 JSX 内容。
注意事项
- 返回值要求:
render
函数最终必须返回一个有效的React 元素
或者null
,不然会引发渲染错误。 - 性能考量:频繁在
render
函数中进行复杂的条件判断或许会影响性能,尤其是在需要频繁重新渲染的场景下。此时可以考虑使用更高效的条件渲染方式,例如三元运算符
或者&& 逻辑运算符
。
如何在 React 项目中引入图片?哪种方式更好?
详情
在 React 项目中,有多种方式可以引入图片,下面为你详细介绍这些方式及其优缺点:
1. 使用 import
语句引入图片
这种方式适用于使用 Webpack 等打包工具的 React 项目,Webpack 会处理图片资源,将其转换为一个 URL。
import React from 'react';
// 引入图片
import myImage from '../assets/myImage.jpg';
const ImageComponent = () => {
return (
<img src={myImage} alt="My Image" />
);
};
export default ImageComponent;
优点:
- 打包工具会处理图片的路径和文件名,避免手动管理路径的麻烦。
- 可以使用 Webpack 的 loader 对图片进行优化,如压缩、转换格式等。 缺点:
- 增加了打包的体积,尤其是图片较大时。
- 需要在代码中显式引入图片,不够灵活。
2. 使用 require 动态引入图片
require 可以在运行时动态引入图片,适合需要根据条件加载不同图片的场景。
import React from 'react';
const DynamicImageComponent = ({ imageName }) => {
// 动态引入图片
const image = require(`../assets/${imageName}.jpg`);
return (
<img src={image.default} alt="Dynamic Image" />
);
};
export default DynamicImageComponent;
优点:
- 可以根据条件动态加载不同的图片,提高代码的灵活性。
缺点:
- 同样会增加打包的体积。
- 需要确保图片路径和文件名的正确性,否则会导致加载失败。
3. 使用 public 文件夹
将图片放在 public
文件夹中,然后使用相对路径引用图片。
import React from 'react';
const PublicImageComponent = () => {
return (
<img src="/myImage.jpg" alt="Public Image" />
);
};
export default PublicImageComponent;
优点:
- 图片不会被打包,不会增加打包的体积。
- 可以直接使用图片的原始文件名和路径,方便管理。
缺点:
- 图片不会经过打包工具的处理,无法进行优化。
- 需要手动管理图片的路径和文件名,容易出错。
哪种方式更好?
这取决于具体的项目需求:
- 如果图片数量较少且不需要动态加载,使用 import 语句引入图片是一个不错的选择,它可以利用打包工具的优化功能,同时避免手动管理路径的麻烦。
- 如果需要根据条件动态加载不同的图片,可以使用 require 动态引入图片,提高代码的灵活性。
- 如果图片较大且不需要进行优化,或者项目对打包体积有严格要求,可以将图片放在 public 文件夹中,使用相对路径引用图片,避免增加打包的体积。
在 React 的 JSX 中,属性是否可以被覆盖?覆盖的原则是什么?
详情
在 React 的 JSX 里,属性是可以被覆盖的,下面为你详细介绍相关情况和覆盖原则。
属性可以被覆盖
在 React 中,当你把多个对象或者属性列表合并到一个组件上时,后面的属性会覆盖前面相同名称的属性。
覆盖原则
1. 基本属性覆盖
对于基本数据类型(如字符串、数字、布尔值等)的属性,后面定义的属性值会直接覆盖前面的属性值。
import React from 'react';
const App = () => {
const commonProps = {
color: 'blue',
size: 'medium'
};
return (
<div {...commonProps} color="red">
这是一个 div
</div>
);
};
export default App;
在这个例子里,color
属性一开始被设置成 'blue'
,但后面又被覆盖成 'red'
。最终 div
元素的 color
属性值是 'red'
。
2. 事件处理函数覆盖
如果是事件处理函数属性,后面的函数会覆盖前面的函数。不过,你可以在后面的函数里调用前面的函数来实现扩展。
import React from 'react';
const App = () => {
const handleClick1 = () => {
console.log('第一次点击处理');
};
const handleClick2 = () => {
handleClick1();
console.log('第二次点击处理');
};
return (
<button onClick={handleClick1} onClick={handleClick2}>
点击我
</button>
);
};
export default App;
在这个例子中,onClick
属性被后面的 handleClick2
函数覆盖了。不过,handleClick2
函数里调用了 handleClick1
函数,这样就实现了事件处理的扩展。
3. 对象属性覆盖
对于对象类型的属性,后面的对象会合并到前面的对象上,相同的键会被后面的值覆盖。
import React from 'react';
const App = () => {
const style1 = {
color: 'blue',
fontSize: '16px'
};
const style2 = {
color: 'red',
fontWeight: 'bold'
};
return (
<div style={{ ...style1, ...style2 }}>
这是一个 div
</div>
);
};
export default App;
在这个例子中,style
属性是一个对象。style2
对象合并到 style1
对象上,color
属性被 style2
里的值覆盖成 'red'
,同时新增了 fontWeight: 'bold'
属性。
在 React 中,如何操作虚拟 DOM 的 class 属性?
详情
在 React 里,由于 class
是 JavaScript 的保留关键字,所以在 JSX 中要使用 className
来操作虚拟 DOM 的 class
属性。下面为你介绍几种常见的操作方式:
1. 静态设置 className
当你有固定的类名需要应用到组件上时,可以直接在 JSX 中设置 className
属性。
import React from 'react';
const MyComponent = () => {
return (
<div className="my-class">
这是一个带有类名的 div
</div>
);
};
export default MyComponent;
2. 动态设置 className
要是类名需要根据组件的状态或者属性动态变化,就可以使用 JavaScript 表达式来设置 className
。
- 使用三元运算符
import React, { useState } from 'react';
const MyComponent = () => {
const [isActive, setIsActive] = useState(false);
return (
<div className={isActive ? 'active-class' : 'inactive-class'}>
这是一个根据状态动态设置类名的 div
<button onClick={() => setIsActive(!isActive)}>切换状态</button>
</div>
);
};
export default MyComponent;
- 使用模板字符串
import React, { useState } from 'react';
const MyComponent = () => {
const [isHighlighted, setIsHighlighted] = useState(true);
const baseClass = 'my-base-class';
const highlightClass = isHighlighted ? 'highlight' : '';
return (
<div className={`${baseClass} ${highlightClass}`}>
这是一个使用模板字符串动态设置类名的 div
<button onClick={() => setIsHighlighted(!isHighlighted)}>切换高亮</button>
</div>
);
};
export default MyComponent;
- 使用 classnames 库 classnames 库可以让动态设置类名变得更加简洁和方便。首先要安装该库:
npm install classnames
然后在代码中使用:
import React, { useState } from 'react';
import classNames from 'classnames';
const MyComponent = () => {
const [isActive, setIsActive] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);
const classes = classNames({
'my-base-class': true,
'active': isActive,
'disabled': isDisabled
});
return (
<div className={classes}>
这是一个使用 classnames 库动态设置类名的 div
<button onClick={() => setIsActive(!isActive)}>切换激活状态</button>
<button onClick={() => setIsDisabled(!isDisabled)}>切换禁用状态</button>
</div>
);
};
export default MyComponent;
如何在 React 中创建一个事件?
详情
在 React 中创建事件主要有以下几种方式:
1. 使用内联事件处理函数
直接在 JSX 元素上通过 on + 事件名(驼峰式)的方式添加事件:
<button onClick={(e) => {
console.log('按钮被点击了');
}}>
点击我
</button>
2 使用组件方法作为事件处理器
在组件中定义方法,然后将其作为事件处理器:
function MyComponent() {
const handleClick = (e) => {
console.log('按钮被点击了');
}
return <button onClick={handleClick}>点击我</button>
}
使用 class 组件的方法
如果使用 class 组件,可以将事件处理器定义为类的方法:
class MyComponent extends React.Component {
handleClick = (e) => {
console.log('按钮被点击了');
}
render() {
return <button onClick={this.handleClick}>点击我</button>
}
}
什么是 React 中的受控组件?它的应用场景是什么?
详情
在 React 中,受控组件是指表单元素(如 <input>
、<textarea>
、<select>
等)的值由 React 组件的状态(state)来控制。这意味着表单元素的输入值不会直接存储在 DOM 中,而是由 React 组件的 state
管理。每次表单元素的值发生变化时,都会触发一个事件处理函数,该函数会更新组件的 state
,从而更新表单元素的值。
以下是一个简单的受控组件示例:
import React, { useState } from 'react';
const ControlledInput = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => {
setInputValue(e.target.value);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
<p>你输入的内容是: {inputValue}</p>
</div>
);
};
export default ControlledInput;
在这个示例中,<input>
元素的值由 inputValue
状态控制。当用户在输入框中输入内容时,onChange
事件会触发 handleChange
函数,该函数会更新 inputValue
状态,从而更新输入框的值。
受控组件的应用场景
- 表单验证:由于受控组件的值由 React 组件的状态管理,因此可以在状态更新时进行表单验证。例如,可以在 handleChange 函数中添加验证逻辑,确保用户输入的内容符合要求。
import React, { useState } from 'react';
const ValidatedInput = () => {
const [inputValue, setInputValue] = useState('');
const [error, setError] = useState('');
const handleChange = (e) => {
const value = e.target.value;
if (value.length < 3) {
setError('输入内容至少需要 3 个字符');
} else {
setError('');
}
setInputValue(value);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<p>你输入的内容是: {inputValue}</p>
</div>
);
};
export default ValidatedInput;
- 实时更新:受控组件可以实时更新表单元素的值,因此可以用于实时预览或计算。例如,在一个计算器应用中,可以使用受控组件来实时更新计算结果。
- 数据同步:当需要将表单数据与其他组件或服务器进行同步时,受控组件非常有用。由于表单数据存储在 React 组件的状态中,可以方便地将数据传递给其他组件或发送到服务器。
- 撤销和重做:由于受控组件的值由 React 组件的状态管理,可以方便地实现撤销和重做功能。例如,可以将每次状态更新的历史记录存储在一个数组中,然后根据用户的操作来恢复或撤销状态。
React 中的 mixins 有什么作用?适用于什么场景?
详情
在 React 早期版本中,mixins
是一种复用代码的方式,它允许你将多个组件的公共逻辑提取到一个 mixin
对象中,然后在多个组件中复用这些逻辑。下面详细介绍其作用和适用场景。
作用
- 代码复用:将多个组件中相同的逻辑提取到一个 mixin 中,避免代码重复。例如,多个组件都需要在组件挂载时发起一个网络请求,那么可以将这个逻辑放到一个 mixin 中。
- 简化组件:减少组件中的代码量,使组件的代码更加简洁,易于维护。通过使用 mixin,可以将一些辅助逻辑从组件中分离出来。
适用场景
- 生命周期方法复用:多个组件在相同的生命周期阶段需要执行相同的操作,比如在
componentDidMount
中添加事件监听器,在componentWillUnmount
中移除事件监听器。
- 生命周期方法复用:多个组件在相同的生命周期阶段需要执行相同的操作,比如在
// 创建一个 mixin
const EventListenerMixin = {
componentDidMount() {
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
},
handleResize() {
console.log('Window resized');
}
};
// 使用 mixin 的组件
const MyComponent = React.createClass({
mixins: [EventListenerMixin],
render() {
return <div>My Component</div>;
}
});
- 状态管理复用:多个组件需要共享相同的状态管理逻辑,比如表单验证、数据格式化等。
const FormValidationMixin = {
validateInput(value) {
return value.length > 0;
}
};
const InputComponent = React.createClass({
mixins: [FormValidationMixin],
handleSubmit() {
const inputValue = this.refs.input.value;
if (this.validateInput(inputValue)) {
console.log('Input is valid');
} else {
console.log('Input is invalid');
}
},
render() {
return (
<div>
<input ref="input" />
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
});
注意事项
在 React 16.x 及以后的版本中,mixins 已经被弃用,不再推荐使用。主要原因是 mixins 会导致命名冲突和代码复杂性增加,不利于代码的维护和理解。替代方案有高阶组件(Higher-Order Components)、渲染属性(Render Props)和 React Hooks (高级函数)。例如,使用 React Hooks 可以实现类似的功能:
import React, { useEffect } from 'react';
// 自定义 Hook
const useEventListener = () => {
useEffect(() => {
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
};
const MyComponent = () => {
useEventListener();
return <div>My Component</div>;
};
export default MyComponent;
这样可以更清晰地复用逻辑,避免 mixins
带来的问题。
在 React Router 的 history 模式中,push 和 replace 方法有什么区别?
详情
在 React Router 的 history
模式中,push
和 replace
方法是用于在单页面应用(SPA)中进行路由导航的两个重要方法,它们的主要区别体现在对浏览器历史记录的操作上。
1. push 方法
push
方法用于向浏览器的历史记录栈中添加一个新的记录,这意味着当你使用 push
方法导航到一个新的路由时,用户可以通过点击浏览器的“后退”按钮回到上一个路由。
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
// 使用 push 方法导航到新的路由
history.push('/new-route');
};
return (
<button onClick={handleClick}>
导航到新路由
</button>
);
}
export default MyComponent;
- 解释
当用户点击按钮时,会使用 push 方法导航到 /new-route 路由,此时浏览器的历史记录栈会新增一条记录。用户点击浏览器的“后退”按钮,就会回到之前的路由。
2. replace 方法
replace
方法用于替换浏览器历史记录栈中的当前记录,而不是添加新的记录。这意味着当你使用 replace
方法导航到一个新的路由时,用户点击浏览器的“后退”按钮将不会回到当前路由,而是直接回到上上个路由。
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
// 使用 replace 方法导航到新的路由
history.replace('/new-route');
};
return (
<button onClick={handleClick}>
替换当前路由
</button>
);
}
export default MyComponent;
- 解释 当用户点击按钮时,会使用 replace 方法导航到 /new-route 路由,此时浏览器的历史记录栈中的当前记录会被替换为新的路由。用户点击浏览器的“后退”按钮,将直接回到上上个路由,而不是当前路由。
总结
- push 方法:向浏览器历史记录栈中添加新的记录,允许用户通过“后退”按钮回到上一个路由,适用于大多数正常的导航场景。
- replace 方法:替换浏览器历史记录栈中的当前记录,不允许用户通过“后退”按钮回到当前路由,适用于需要跳过某些路由的场景,例如登录成功后替换登录页面,避免用户通过“后退”按钮回到登录页面。
React 的 render 函数在什么情况下会被触发?
详情
在 React 中,render
函数是组件的核心部分,它负责返回要渲染的 JSX 内容。以下是 render
函数会被触发的几种常见情况:
1. 组件挂载时
当组件被创建并插入到 DOM 中时,render
函数会被调用。这是组件生命周期的初始阶段,用于初始化组件的 UI。
2. 组件的 state 发生变化
当组件的 state
通过 setState
方法更新时,render
函数会被触发,以重新渲染组件的 UI。
3. 组件的 props 发生变化
当组件的 props
发生变化时,render
函数也会被触发。这通常发生在父组件传递给子组件的 props
发生改变时。
4. 调用 forceUpdate 方法
forceUpdate
方法会强制组件重新渲染,即使 state
或 props
没有发生变化。通常情况下,不建议使用 forceUpdate
,因为它会绕过 React 的优化机制。
React 15 和 React 16 对 IE 的支持版本分别是什么?
详情
React 15 对 IE 的支持版本
React 15 支持 Internet Explorer 9 及以上版本。不过,在使用时,可能需要一些 polyfill 来填补旧版浏览器中缺失的功能,例如 Object.assign、Promise 等。
React 16 对 IE 的支持版本
React 16 支持 Internet Explorer 11 及以上版本。React 16 移除了对 IE 9 和 IE 10 的支持,因为这些旧版本浏览器缺乏很多现代 JavaScript 特性,维护起来成本较高。同样,为了确保在 IE 11 中正常运行,也需要使用一些 polyfill,比如 Promise、fetch 等。
为什么浏览器不能直接解析 React 的 JSX?怎么解决?
详情
1)JSX 不是标准的 JavaScript 语法
JSX 是 React 特有的语法扩展,它允许在 JavaScript 中编写类似 HTML 的代码。但这种语法并不是 ECMAScript 标准的一部分,浏览器的 JavaScript 引擎无法识别和解析这种语法。
2)JSX 需要被转换为普通的 JavaScript 代码
JSX 代码最终需要被转换成 React.createElement()
调用,这样浏览器才能理解和执行。
React 事件绑定的方式有哪些?每种方式有什么区别?
详情
在 React 中,常见的事件绑定方式有以下几种,并且各有区别:
1. 内联事件处理函数
import React from 'react';
const InlineEventComponent = () => {
return (
<button onClick={(e) => {
console.log('按钮被点击了', e);
}}>
点击我
</button>
);
};
export default InlineEventComponent;
优点:
- 代码简洁,适合简单的事件处理逻辑。
- 可以直接访问组件的状态和属性。 缺点:
- 每次渲染时都会创建一个新的函数实例,可能会影响性能,尤其是在列表渲染时。
- 复杂的逻辑会让 JSX 变得冗长,降低代码的可读性。
2. 组件方法作为事件处理器
import React from 'react';
const MethodEventComponent = () => {
const handleClick = (e) => {
console.log('按钮被点击了', e);
};
return (
<button onClick={handleClick}>
点击我
</button>
);
};
export default MethodEventComponent;
优点:
- 避免了每次渲染时创建新的函数实例,性能较好。
- 逻辑清晰,代码可读性高。 缺点:
- 如果需要传递额外的参数,需要使用箭头函数包裹,可能会导致一定的性能开销。
3. class 组件的方法绑定
import React from 'react';
class ClassEventComponent extends React.Component {
constructor(props) {
super(props);
// 绑定方法
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
console.log('按钮被点击了', e);
}
render() {
return (
<button onClick={this.handleClick}>
点击我
</button>
);
}
}
export default ClassEventComponent;
优点:
- 方法只绑定一次,避免了每次渲染时创建新的函数实例。
- 可以在方法中使用 this 访问组件的状态和属性。 缺点:
- 需要在构造函数中手动绑定方法,增加了代码量。
4. 使用箭头函数作为类的属性
import React from 'react';
class ArrowFunctionEventComponent extends React.Component {
handleClick = (e) => {
console.log('按钮被点击了', e);
}
render() {
return (
<button onClick={this.handleClick}>
点击我
</button>
);
}
}
export default ArrowFunctionEventComponent;
优点:
- 不需要手动绑定 this,代码简洁。
- 可以在方法中使用 this 访问组件的状态和属性。 缺点:
- 每个实例都会创建一个新的函数,可能会占用更多的内存。
在 React 构造函数中调用 super(props) 的目的是什么?
详情
在 React 类组件的构造函数中调用 super(props) 主要有以下两个重要目的:
1. 继承父类的属性和方法
在 JavaScript 里,当使用 extends
关键字创建一个子类时,子类的构造函数需要调用 super()
来执行父类的构造函数。在 React 中,类组件继承自 React.Component
,所以在子类的构造函数里必须调用 super()
来初始化父类的状态和方法。
2. 确保 this.props 可用
如果在构造函数中调用 super(props)
,就可以在构造函数内部使用 this.props
。props
是 React 组件用来接收外部数据的一种方式,若不调用 super(props)
,在构造函数里访问 this.props
会得到 undefined
。
使用 create-react-app 创建新应用时,如果遇到卡顿的问题,如何解决?
详情
当使用 create-react-app 创建新应用遇到卡顿问题时,可以从以下几个方面进行优化:
1)使用国内镜像源
将 npm 默认的镜像源替换为国内镜像源,可以显著提升依赖包的下载速度:
- 临时使用:npm install --registry=https://registry.npmmirror.com
- 永久设置:npm config set registry https://registry.npmmirror.com
2)优化依赖安装
- 1)使用 npm cache verify 验证缓存
- 2)使用 npm ci 代替 npm install 进行安装
- 3)确保提交 package-lock.json 文件
3)选择更快的包管理器
可以考虑使用 pnpm 或 yarn 代替 npm:
- 1)pnpm:通过硬链接共享依赖,安装速度更快,节省磁盘空间
- 2)yarn:支持并行安装,有更好的缓存机制
在 React 的 JSX 中如何写注释?
详情
在 React 的 JSX 里,注释的写法与普通 JavaScript 注释有所不同。下面为你介绍几种常见的注释方式:
1. 单行注释
在 JSX 中,单行注释需要用花括号包裹,并且使用 //
开头。示例如下:
import React from 'react';
const MyComponent = () => {
return (
<div>
{/* 这是一个单行注释 */}
<p>Hello, World!</p>
</div>
);
};
export default MyComponent;
2. 多行注释
多行注释同样要用花括号包裹,使用 /* */
来包裹注释内容。示例如下:
import React from 'react';
const MyComponent = () => {
return (
<div>
{/*
这是一个多行注释
可以写很多内容
*/}
<p>Hello, World!</p>
</div>
);
};
export default MyComponent;
3. 在标签属性中注释
如果要在标签属性里添加注释,同样需要遵循上述规则。示例如下:
import React from 'react';
const MyComponent = () => {
return (
<input
type="text"
// 这是一个单行注释,用于描述 placeholder 属性
placeholder="请输入内容"
/>
);
};
export default MyComponent;
React 中 Component 和 PureComponent 有什么区别?
详情
在 React 里,Component
和 PureComponent
都是用于创建组件的基类,不过它们之间存在一些关键的差异:
1. 浅层比较机制
Component
:Component
不会自动进行浅层比较(shallow comparison)。当state
或者props
发生变化时,Component
组件的render
方法就会被调用,重新渲染组件。PureComponent
:PureComponent
会自动进行浅层比较。当state
或者props
发生变化时,PureComponent
会对新旧state
和props
进行浅层比较,只有当它们存在差异时,才会调用render
方法重新渲染组件。
2. 性能优化
- Component:由于 Component 没有自动的浅层比较机制,即便 state 或者 props 没有变化,render 方法也可能会被调用,这就可能会导致不必要的渲染,影响性能。
- PureComponent:PureComponent 的浅层比较机制能够避免不必要的渲染,从而提升性能。不过,这种优化也存在一定的局限性,因为它只是进行浅层比较,对于嵌套对象或者数组的变化可能无法准确检测。
3. 代码示例
以下是 Component
和 PureComponent
的使用示例:
import React, { Component, PureComponent } from 'react';
// 使用 Component 创建组件
class MyComponent extends Component {
render() {
return <div>My Component: {this.props.value}</div>;
}
}
// 使用 PureComponent 创建组件
class MyPureComponent extends PureComponent {
render() {
return <div>My Pure Component: {this.props.value}</div>;
}
}
export { MyComponent, MyPureComponent };
4. 使用建议
Component
:当组件的 state 或者 props 频繁变化,或者需要手动控制渲染逻辑时,建议使用 Component。PureComponent
:当组件的 state 或者 props 变化较少,且不需要手动控制渲染逻辑时,建议使用 PureComponent 来提升性能。
React 项目如何将多个组件嵌入到一个组件中?
详情
在 React 项目里,把多个组件嵌入到一个组件中有多种方式,下面为你详细介绍几种常见的方法:
1. 直接在 JSX 中引入组件
你可以直接在父组件的 JSX 代码里引入多个子组件。
import React from 'react';
// 定义子组件
const Component1 = () => {
return <div>这是组件 1</div>;
};
const Component2 = () => {
return <div>这是组件 2</div>;
};
// 定义父组件
const ParentComponent = () => {
return (
<div>
<h1>父组件</h1>
<Component1 />
<Component2 />
</div>
);
};
export default ParentComponent;
2. 使用 props.children
借助 props.children
,你能把多个组件作为父组件的子元素传递进去。
import React from 'react';
// 定义子组件
const Component1 = () => {
return <div>这是组件 1</div>;
};
const Component2 = () => {
return <div>这是组件 2</div>;
};
// 定义父组件
const ParentComponent = ({ children }) => {
return (
<div>
<h1>父组件</h1>
{children}
</div>
);
};
// 使用父组件
const App = () => {
return (
<ParentComponent>
<Component1 />
<Component2 />
</ParentComponent>
);
};
export default App;
3. 通过数组引入多个组件
你可以把多个组件存储在数组里,然后在父组件中遍历这个数组来引入这些组件。
import React from 'react';
// 定义子组件
const Component1 = () => {
return <div>这是组件 1</div>;
};
const Component2 = () => {
return <div>这是组件 2</div>;
};
// 定义父组件
const ParentComponent = () => {
const components = [
<Component1 key="component1" />,
<Component2 key="component2" />
];
return (
<div>
<h1>父组件</h1>
{components}
</div>
);
};
export default ParentComponent;
事件在 React 中是如何处理的?
详情
在 React 中,事件处理机制与传统的 HTML 事件处理有所不同,但基本原理是相似的。以下是 React 中事件处理的主要特点和方式:
1. 事件命名采用驼峰命名法
在 HTML 中,事件名通常是小写的,如 onclick
。而在 React 中,事件名使用驼峰命名法,如 onClick
。
2. 使用函数作为事件处理程序
在 React 中,事件处理程序是一个函数,而不是一个字符串。
3. 合成事件
React 中的事件是合成事件(SyntheticEvent),它是对原生 DOM
事件的跨浏览器封装。合成事件具有与原生事件相同的接口,但在不同浏览器中表现一致。
4. 事件绑定方式
- 内联事件处理函数
import React from 'react';
const InlineEventComponent = () => {
return (
<button onClick={(e) => {
console.log('按钮被点击了', e);
}}>
点击我
</button>
);
};
export default InlineEventComponent;
- 组件方法作为事件处理器
import React from 'react';
const MethodEventComponent = () => {
const handleClick = (e) => {
console.log('按钮被点击了', e);
};
return (
<button onClick={handleClick}>
点击我
</button>
);
};
export default MethodEventComponent;
- class 组件的方法绑定
import React from 'react';
class ArrowFunctionEventComponent extends React.Component {
handleClick = (e) => {
console.log('按钮被点击了', e);
}
render() {
return (
<button onClick={this.handleClick}>
点击我
</button>
);
}
}
export default ArrowFunctionEventComponent;
- 使用箭头函数作为类的属性
import React from 'react';
class ArrowFunctionEventComponent extends React.Component {
handleClick = (e) => {
console.log('按钮被点击了', e);
}
render() {
return (
<button onClick={this.handleClick}>
点击我
</button>
);
}
}
export default ArrowFunctionEventComponent;
5. 阻止默认行为
在 React 中,阻止默认行为需要调用 event.preventDefault()
方法,而不是返回 false
。
import React from 'react';
const PreventDefaultComponent = () => {
const handleClick = (e) => {
e.preventDefault();
console.log('链接的默认行为被阻止');
};
return (
<a href="https://www.example.com" onClick={handleClick}>
点击我
</a>
);
};
export default PreventDefaultComponent;
6. 事件委托
React 使用事件委托机制,将所有事件处理程序挂载到文档的根节点上。这样可以减少事件处理程序的数量,提高性能。
综上所述,React 中的事件处理通过合成事件和事件委托机制,提供了一种统一、高效的方式来处理用户交互。
如何在 React 项目中开启生产模式?
详情
在 React 项目里,开启生产模式能够优化应用的性能,减少打包文件的大小。下面为你介绍不同创建方式的 React 项目开启生产模式的方法。
使用 Create React App 创建的项目
如果你使用 Create React App 创建项目,可通过以下命令构建生产版本:
npm run build
上述命令会在项目根目录下生成一个 build 文件夹,里面包含了优化后的生产版本文件。这些文件已经经过压缩和优化,适合部署到生产环境。
使用 Vite 创建的项目
若使用 Vite 创建 React 项目,可通过以下命令构建生产版本:
npm run build
上述命令会在项目根目录下生成一个 dist 文件夹,里面包含了优化后的生产版本文件。这些文件已经经过压缩和优化,适合部署到生产环境。
自定义 Webpack 配置的项目
如果你自定义了 Webpack 配置,可通过设置 mode
为 production
来开启生产模式。在 webpack.config.js
文件中添加如下配置:
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
// 其他配置...
};
然后使用以下命令进行打包:
npx webpack
在 React 中,是否可以在 render 方法中访问 refs?为什么?
详情
在 React 中,不能在 render
方法中访问 refs
,主要原因如下:
1)时机问题
render
方法执行时,DOM
元素还未被创建或更新,因此 refs
还未被赋值。refs
只有在组件挂载完成后(componentDidMount
)或更新完成后(componentDidUpdate
)才能访问。
2)React 的渲染机制
- 1)
render
方法是一个纯函数,应该只根据props
和state
返回JSX
- 2)
render
方法可能会被多次调用,而不一定会导致实际的DOM
更新 - 3)在
render
中访问refs
违反了 React 的声明式渲染原则
在 React 中声明组件时,组件名的第一个字母是否必须是大写?为什么?
详情
在 React 中声明组件时,组件名的第一个字母必须是大写。以下是详细原因:
1. JSX 解析规则
在 JSX 中,React 会根据标签名的大小写来区分是 HTML 标签还是自定义组件。如果标签名是小写字母开头,React 会将其解析为原生 HTML 标签,例如 <div>
、<span>
等;而如果标签名是大写字母开头,React 则会将其解析为自定义组件。
2. 遵循 React 规范
使用大写字母开头的组件名是 React 的官方规范,遵循这个规范可以使代码更具可读性和可维护性,同时也能避免潜在的错误。
综上所述,在 React 中声明组件时,为了让 React 正确解析自定义组件,组件名的第一个字母必须是大写。
React 的类组件和函数式组件有什么区别?
详情
在 React 中,类组件(Class Components)和函数式组件(Functional Components)是两种创建组件的方式,它们有以下区别:
1. 语法结构
- 类组件:使用 ES6 的
class
关键字定义,继承自React.Component
类,必须包含render
方法返回 JSX。
import React from 'react';
class ClassComponent extends React.Component {
render() {
return <div>这是一个类组件</div>;
}
}
export default ClassComponent;
- 函数式组件:是一个普通的 JavaScript 函数,接收
props
作为参数并返回 JSX。
import React from 'react';
const FunctionalComponent = (props) => {
return <div>这是一个函数式组件</div>;
};
export default FunctionalComponent;
2. 状态管理
- 类组件:可以使用
this.state
来定义和管理组件的状态,通过this.setState
方法来更新状态。 - 函数式组件:在 React Hooks 出现之前,函数式组件是无状态的,不能管理自己的状态。但自从 React 16.8 引入了 Hooks,函数式组件可以使用
useState
Hook 来管理状态。
3. 生命周期方法
- 类组件:有完整的生命周期方法,如
componentDidMount
、componentDidUpdate
、componentWillUnmount
等,可以在这些方法中执行副作用操作,如数据获取、事件监听和清理等。 - 函数式组件:在 React Hooks 出现之前,函数式组件没有生命周期方法。但现在可以使用
useEffect
Hook 来模拟生命周期方法,实现副作用操作。
4. 性能优化
- 类组件:可以使用
shouldComponentUpdate
生命周期方法来控制组件是否需要重新渲染,避免不必要的渲染。 - 函数式组件:可以使用
React.memo
高阶组件来实现浅比较,避免不必要的渲染。
5. 代码复杂度和可读性
- 类组件:由于类组件包含更多的语法和生命周期方法,代码结构相对复杂,尤其是在处理复杂逻辑时,可能会导致代码难以理解和维护。
- 函数式组件:函数式组件的代码结构简单,只包含一个函数,逻辑清晰,易于理解和维护。尤其是在使用 Hooks 后,函数式组件可以处理复杂的逻辑,同时保持代码的简洁性。
React 的 render 函数返回的数据类型是什么?
详情
在 React 里,render
函数主要用来返回要渲染的内容,它可以返回以下几种数据类型:
1. React 元素
这是最常见的返回类型,它代表了一个 DOM 节点或者另一个 React 组件。可以是原生 HTML 标签,也可以是自定义组件。
import React from 'react';
class MyComponent extends React.Component {
render() {
return <div>Hello, World!</div>;
}
}
export default MyComponent;
2. 数组和 fragments
- 数组:可以返回一个包含多个 React 元素的数组,数组中的每个元素都需要有唯一的 key 属性。
import React from 'react';
class MyComponent extends React.Component {
render() {
return [
<div key="1">Element 1</div>,
<div key="2">Element 2</div>
];
}
}
export default MyComponent;
- Fragments:用于在不添加额外 DOM 节点的情况下返回多个元素。可以使用显式的
<React.Fragment>
标签或短语法<>...</>
。
import React from 'react';
class MyComponent extends React.Component {
render() {
return (
<React.Fragment>
<div>Element 1</div>
<div>Element 2</div>
</React.Fragment>
);
}
}
export default MyComponent;
3. 字符串和数字
可以直接返回字符串或数字,它们会被渲染为文本节点。
import React from 'react';
class MyComponent extends React.Component {
render() {
return 'Hello, World!';
}
}
export default MyComponent;
4. null 和 false
返回 null
或 false
表示不渲染任何内容。
import React from 'react';
class MyComponent extends React.Component {
render() {
return null;
}
}
export default MyComponent;
5. 布尔值和 undefined
虽然布尔值和 undefined
本身不会被渲染,但在条件渲染中很有用。
import React from 'react';
class MyComponent extends React.Component {
render() {
const isVisible = true;
return isVisible && <div>Visible</div>;
}
}
export default MyComponent;
总结来说,render
函数返回的数据类型可以是 React 元素、数组、fragments、字符串、数字、null
、false
、布尔值和 undefined
。这些返回值决定了组件在页面上的渲染结果。
Redux 状态管理器与将变量挂载到 window 对象中有什么区别?
详情
Redux 和将变量挂载到 window 对象有以下关键区别:
状态管理机制
1)Redux:
- 提供可预测的状态管理
- 使用单向数据流
- 通过 reducer 纯函数修改状态
- 状态变更可追踪和调试
2)Window 对象:
- 全局变量可以被任意修改
- 没有状态变更追踪机制
- 容易导致命名冲突
- 难以调试和维护
数据流向
1)Redux:
- 严格的单向数据流
- 通过 dispatch action 修改状态
- 组件通过 connect 或 hooks 订阅状态
2)Window 对象:
- 数据流向不可控
- 任何地方都可以直接修改
- 没有统一的更新机制
React 的 state 和 setState 有什么区别?
详情
1)state 的定义
state 是组件内部的数据存储,它允许 React 组件跟踪可能发生变化的信息。state 是组件的内存,它在组件的多次渲染之间保持不变。
2)setState 的定义
setState 是 React 提供的一个用于更新组件状态的方法。它会将组件的状态更新加入队列中,并通知 React 需要使用更新后的状态重新渲染组件。
3)主要区别
- 状态更新方式:state 是一个快照,它反映了某个时间点的数据状态。而 setState 是一个用来更新这个快照的函数。
- 异步更新:setState 的执行是异步的,多个 setState 调用会被合并处理。这意味着在调用 setState 后立即读取 state,拿到的还是旧值。
- 批量处理:React 会对 setState 的调用进行批处理,多个 setState 的调用会被合并到一次重新渲染中,这样可以提高性能。
React 的 JSX 和 HTML 有什么区别?
详情
1)语法差异
- JSX 是 JavaScript 的语法扩展,虽然看起来很像 HTML,但实际上更接近 JavaScript。主要区别包括:
- 属性命名:JSX 使用驼峰命名法(camelCase),而 HTML 使用短横线命名法(kebab-case)。例如,HTML 中的 class 在 JSX 中要写成 className,tabindex 要写成 tabIndex。
- 事件处理:JSX 中事件处理使用驼峰命名法,如 onClick,而 HTML 中使用全小写,如 onclick。
2)表达式支持
JSX 可以通过大括号 {}
来嵌入任何有效的 JavaScript 表达式,而 HTML 只能包含纯文本内容。
3)自闭合标签
在 JSX 中,所有标签都必须正确闭合,包括自闭合标签如 img、input 等,必须写成 <img />
或 <input />
,而在 HTML 中可以省略结束标签。
在 React 中,如何检验 props?为什么要验证 props?
详情
1)为什么要验证 props
- props 验证的主要目的是确保组件接收到的数据类型和格式是正确的,这样可以:
- 帮助开发者快速定位问题:当传入的 props 不符合预期时,React 会在控制台显示警告信息。
- 提高代码可维护性:通过类型检查,使代码更加健壮,减少运行时错误。
- 作为组件的文档:props 验证可以清晰地表明组件期望接收什么类型的数据。
2)如何验证 props
在 React 中,有几种主要的 props 验证方式:
1. 使用 TypeScript
TypeScript 是 JavaScript 的一个超集,它提供了静态类型检查的功能,可以在编译时检查 props 的类型。
interface Props {
name: string;
age: number;
email?: string; // 可选的 prop
}
function User(props: Props) {
return <div>{props.name}</div>;
}
2. PropTypes(React 旧版本):
PropTypes 是 React 提供的一个用于验证 props 的库,在 React 旧版本中广泛使用。
import PropTypes from 'prop-types';
function User(props) {
return <div>{props.name}</div>;
}
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string
};
如何在 React JSX 中实现 for 循环?
详情
在 React JSX 中实现循环渲染主要有以下几种方式:
1)使用 map 方法
最常用的方式是使用数组的 map
方法:
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
2)使用 Children.map
方法
如果需要对 JSX 的子元素进行循环,可以使用 React.Children.map
:
function RowList({ children }) {
return (
<div>
{Children.map(children, child => (
<div className="row">
{child}
</div>
))}
</div>
);
}
在 React 中,组件和元素有什么区别?
详情
1)React 元素
React 元素是描述用户界面一部分的轻量级对象。它是 React 应用中最小的构建块,用来告诉 React 你想在屏幕上看到什么。
一个 React 元素的结构大致如下:
{
type: 'div',
props: {
className: 'greeting',
children: '你好'
}
}
2)React 组件
React 组件是可复用的代码块,它接收任意的输入(称为 props)并返回描述屏幕上应该显示内容的 React 元素。组件可以是函数组件或类组件:
// 函数组件
function Welcome(props) {
return <h1>你好, {props.name}</h1>;
}
// 类组件
class Welcome extends React.Component {
render() {
return <h1>你好, {this.props.name}</h1>;
}
}
什么是 React 的 propTypes?它有什么作用?
详情
1)propTypes 的定义
PropTypes 是 React 提供的一种类型检查机制,用于验证组件接收的 props 是否符合要求的类型。它可以帮助开发者在开发阶段及时发现类型错误。
2)作用
PropTypes 主要有以下作用:
- 类型检查:确保组件接收到的 props 类型正确。
- 开发调试:在开发环境下,如果传入的 props 类型不正确,控制台会显示警告信息。
- 文档功能:通过查看 propTypes 定义,其他开发者可以快速了解组件需要哪些 props 及其类型。
什么是 React 的 childContextTypes?它的作用是什么?
详情
childContextTypes
是 React 旧版本(在 React 16 之前)中用于上下文(Context)API 的一个属性。上下文 API 允许你在组件树中共享数据,而不必在每个层级都手动传递 props
。
这个 API 在 React 16.3 版本后被废弃,并在 React 19 中被完全移除。现在推荐使用 Context API(createContext)来替代它。
定义
childContextTypes
是一个对象,用于定义组件提供的上下文数据的类型。它是一个静态属性,通常在类组件中定义。通过 childContextTypes
,你可以指定上下文数据的类型,这样 React 会在开发环境中进行类型检查,确保上下文数据的类型符合预期。
作用
- 类型检查:在开发环境中,React 会检查组件提供的上下文数据是否符合
childContextTypes
中定义的类型。如果不符合,会在控制台显示警告信息,帮助开发者快速定位问题。
- 类型检查:在开发环境中,React 会检查组件提供的上下文数据是否符合
- 文档功能:
childContextTypes
可以作为组件的文档,清晰地表明组件提供的上下文数据的类型和结构,方便其他开发者理解和使用。
- 文档功能:
import React from 'react';
import PropTypes from 'prop-types';
class ParentComponent extends React.Component {
// 定义 childContextTypes
static childContextTypes = {
theme: PropTypes.string
};
// 定义 getChildContext 方法返回上下文数据
getChildContext() {
return {
theme: 'dark'
};
}
render() {
return (
<div>
<ChildComponent />
</div>
);
}
}
class ChildComponent extends React.Component {
// 定义 contextTypes 来接收上下文数据
static contextTypes = {
theme: PropTypes.string
};
render() {
return (
<div>
当前主题: {this.context.theme}
</div>
);
}
}
export default ParentComponent;
注意事项
- React 16 及以后版本:自 React 16 起,旧的上下文 API 已被弃用,推荐使用新的上下文 API(React.createContext)。新的上下文 API 更加简洁和安全,不再需要 childContextTypes 和 contextTypes。
- 类型检查:childContextTypes 只在开发环境中进行类型检查,生产环境中不会进行检查,以提高性能。
综上所述,childContextTypes 是 React 旧版本中用于上下文 API 的类型检查工具,它可以帮助开发者确保上下文数据的类型正确,并提供清晰的文档说明。但在新版本的 React 中,建议使用新的上下文 API 来实现数据共享。
什么是 React 的 useState?为什么要使用 useState?
详情
useState 是 React 提供的一个 Hook,它允许函数组件添加状态管理的能力。通过 useState,你可以:
- 1)在函数组件中声明一个状态变量。
- 2)获得一个用于更新这个状态的函数。
- 3)当状态更新时,React 会重新渲染组件。
使用 useState 的主要原因是它能让你的组件保持和跟踪数据随时间的变化,比如用户输入、API 响应、表单状态等。
如何在 React Router 中设置重定向?
详情
在 React Router 中设置重定向主要有以下几种方式:
1)使用 Navigate 组件
这是最简单直接的方式,适用于组件内部的声明式重定向:
import { Navigate } from 'react-router-dom';
function LoginPage() {
if (isAuthenticated) {
return <Navigate to="/dashboard" replace={true} />;
}
return <LoginForm />;
}
2)使用 useNavigate Hook
这种方式适用于在事件处理或者其他逻辑中进行编程式导航:
import { useNavigate } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const handleLogin = async () => {
await login();
navigate('/dashboard');
};
return <button onClick={handleLogin}>登录</button>;
}
3)使用 Redirect 组件(用于路由配置)
在路由配置中使用,可以设置路径重定向:
import { Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/home" element={<HomePage />} />
<Route path="/" element={<Navigate to="/home" replace />} />
</Routes>
);
}
如何定时更新一个 React 组件?
详情
在 React 中实现组件的定时更新主要有以下几种方式:
1)使用 useEffect + setInterval
这是最常用的方式,适合需要定期更新的场景:
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <h1>计数: {count}</h1>;
}
2)使用 useEffect + setTimeout
适合需要延迟执行一次的场景:
function DelayedMessage() {
const [showMessage, setShowMessage] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowMessage(true);
}, 3000);
return () => clearTimeout(timer);
}, []);
return <div>{showMessage ? '消息显示了!' : '等待中...'}</div>;
}
React 组件推荐的命名规范是什么?为什么不推荐使用 displayName?
详情
React 组件推荐的命名规范
1. 首字母大写
在 React 中,组件名的第一个字母必须大写。这是因为 JSX 会根据标签名的大小写来区分是 HTML 标签还是自定义组件。如果标签名是小写字母开头,React 会将其解析为原生 HTML 标签;而如果标签名是大写字母开头,React 则会将其解析为自定义组件。
// 正确的组件命名
const MyComponent = () => {
return <div>这是一个组件</div>;
};
// 错误的组件命名,可能会导致解析错误
const myComponent = () => {
return <div>这是一个组件</div>;
};
2. 使用 PascalCase(大驼峰命名法)
大驼峰命名法是指每个单词的首字母大写,其余字母小写。这种命名方式可以提高代码的可读性,让开发者更容易识别组件名。
// 推荐的命名方式
const UserProfile = () => {
return <div>用户信息</div>;
};
// 不推荐的命名方式
const user_profile = () => {
return <div>用户信息</div>;
};
3. 描述性命名
组件名应该能够清晰地描述组件的功能或用途。避免使用过于通用或无意义的名称。
// 推荐的命名方式
const LoginForm = () => {
return <form>登录表单</form>;
};
// 不推荐的命名方式
const Form = () => {
return <form>登录表单</form>;
};
为什么不推荐使用 displayName
1. 现代调试工具的支持
现代的调试工具(如 React DevTools)已经可以很好地显示组件名,不再依赖 displayName 来进行调试。即使没有设置 displayName,调试工具也能根据组件的定义正确显示组件名。
2. 潜在的混淆
displayName 可以手动设置,这可能会导致组件名与实际定义的组件名不一致,从而造成混淆。在代码审查和维护过程中,开发者可能会被不一致的名称误导。
3. 代码简洁性
使用 displayName 会增加额外的代码,降低代码的简洁性。遵循推荐的命名规范可以让代码更加简洁易读。
在 React 项目中,如何使用字体图标?
详情
在 React 项目中使用字体图标,一般可以通过以下几种常见的方式:
1. 使用第三方字体图标库(如 Font Awesome)
步骤如下:
- 安装 Font Awesome:可以使用 npm 或 yarn 进行安装。
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome
- 在组件中使用图标:
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCoffee } from '@fortawesome/free-solid-svg-icons';
const App = () => {
return (
<div>
<FontAwesomeIcon icon={faCoffee} />
</div>
);
};
export default App;
2. 使用自定义字体图标
步骤如下:
- 准备字体图标文件:可以从 Iconfont 等网站下载自定义的字体图标文件,通常包含
.ttf
、.woff
、.woff2
等格式的文件。 - 引入字体文件:在项目的 CSS 文件中引入字体文件。
@font-face {
font-family: 'MyIconFont';
src: url('./fonts/iconfont.ttf') format('truetype'),
url('./fonts/iconfont.woff') format('woff'),
url('./fonts/iconfont.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
- 定义图标类:在 CSS 中定义图标类。
.icon {
font-family: 'MyIconFont';
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-coffee:before {
content: '\e600';
}
- 在组件中使用图标:
import React from 'react';
import './styles.css';
const App = () => {
return (
<div>
<span className="icon icon-coffee"></span>
</div>
);
};
export default App;
3. 使用 SVG 图标
步骤如下:
- 准备 SVG 图标文件:可以从图标库下载 SVG 图标文件,或者自己创建 SVG 图标。
- 在组件中引入 SVG 图标:
import React from 'react';
import coffeeIcon from './coffee.svg';
const App = () => {
return (
<div>
<img src={coffeeIcon} alt="Coffee Icon" />
</div>
);
};
export default App;
在 React 的 JSX 中,如何使用自定义属性?
详情
在 React 的 JSX 里,你可以像使用标准 HTML 属性一样使用自定义属性。不过,需要注意的是,React 会自动将属性名转换为驼峰命名法,并且在渲染时,自定义属性会直接添加到 DOM 元素上。
下面为你介绍几种使用自定义属性的方法:
1. 直接在 JSX 标签上添加自定义属性
import React from 'react';
const CustomComponent = () => {
return (
<div myCustomAttribute="value">
这是一个包含自定义属性的 div 元素
</div>
);
};
export default CustomComponent;
2. 2. 使用变量来动态设置自定义属性的值
import React from 'react';
const CustomComponent = () => {
const customValue = '动态值';
return (
<div myCustomAttribute={customValue}>
这是一个包含动态自定义属性的 div 元素
</div>
);
};
export default CustomComponent;
3. 注意事项
- 自定义属性名的大小写:在 JSX 里,属性名遵循驼峰命名法。不过在 HTML 中,属性名是小写的。所以,在 JSX 中定义的自定义属性,在渲染到 DOM 时,会被转换为小写。
- 数据类型:自定义属性的值可以是字符串、数字、布尔值等。如果是布尔值,只需要写上属性名,而不需要赋值。例如:
import React from 'react';
const CustomComponent = () => {
return (
<div myCustomBooleanAttribute>
这是一个包含布尔类型自定义属性的 div 元素
</div>
);
};
export default CustomComponent;
在 React Router 中如何获取 URL 参数?
详情
在 React Router 中,获取 URL 参数有不同的方式,具体取决于你使用的是 React Router 的哪个版本。下面分别介绍 React Router v5 和 v6 中获取 URL 参数的方法。
React Router v5
在 React Router v5 中,可以使用 useParams
钩子(用于函数式组件)或者 withRouter
高阶组件(用于类组件)来获取 URL
参数。
- 使用
useParams
钩子(函数式组件):
import React from 'react';
import { BrowserRouter as Router, Route, useParams } from 'react-router-dom';
const UserProfile = () => {
const { id } = useParams();
return (
<div>
用户 ID: {id}
</div>
);
};
const App = () => {
return (
<Router>
<Route path="/users/:id" component={UserProfile} />
</Router>
);
};
export default App;
- 使用
withRouter
高阶组件(类组件):
import React from 'react';
import { BrowserRouter as Router, Route, withRouter } from 'react-router-dom';
class UserProfile extends React.Component {
render() {
const { id } = this.props.match.params;
return (
<div>
用户 ID: {id}
</div>
);
}
}
const UserProfileWithRouter = withRouter(UserProfile);
const App = () => {
return (
<Router>
<Route path="/users/:id" component={UserProfileWithRouter} />
</Router>
);
};
export default App;
React Router v6
在 React Router v6 中,useParams
钩子仍然可用,并且用法更加简洁。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useParams } from 'react-router-dom';
const UserProfile = () => {
const { id } = useParams();
return (
<div>
用户 ID: {id}
</div>
);
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</Router>
);
};
export default App;
React 的 getInitialState 方法有什么作用?
详情
getInitialState
是 React 类组件旧版本(在 React 16.8 引入 Hooks 之前,尤其是使用 React.createClass
创建组件时)用于初始化组件状态的方法。在使用 ES6 类定义组件时,通常使用构造函数来初始化状态。
作用
getInitialState
方法的主要作用是为组件设置初始状态。状态(state
)是组件内部的数据存储,它允许 React 组件跟踪可能发生变化的信息。通过 getInitialState
方法,可以在组件创建时为状态赋予初始值。
示例
以下是使用 React.createClass
创建组件并使用 getInitialState
方法初始化状态的示例:
// 使用 React.createClass 创建组件
const MyComponent = React.createClass({
// 定义 getInitialState 方法
getInitialState: function() {
return {
count: 0
};
},
// 定义一个增加计数的方法
incrementCount: function() {
this.setState({ count: this.state.count + 1 });
},
// 渲染组件
render: function() {
return (
<div>
<p>计数: {this.state.count}</p>
<button onClick={this.incrementCount}>增加计数</button>
</div>
);
}
});
// 渲染组件到 DOM
ReactDOM.render(<MyComponent />, document.getElementById('root'));
在这个示例中,getInitialState
方法返回一个对象,该对象包含一个初始状态 count
,其值为 0。在组件渲染时,count
的值会显示在页面上,并且可以通过点击按钮来增加计数。
替代方案
在现代 React 开发中,推荐使用 ES6 类和 Hooks 来创建组件。对于 ES6 类组件,可以在构造函数中初始化状态:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
count: 0
};
}
// 定义一个增加计数的方法
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};
// 渲染组件
render() {
return (
<div>
<p>计数: {this.state.count}</p>
<button onClick={this.incrementCount}>增加计数</button>
</div>
);
}
}
export default MyComponent;
对于函数式组件,可以使用 useState
Hook 来管理状态:
import React, { useState } from 'react';
const MyComponent = () => {
// 初始化状态
const [count, setCount] = useState(0);
// 定义一个增加计数的方法
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={incrementCount}>增加计数</button>
</div>
);
};
export default MyComponent;
使用 React 进行开发的方式有哪些?
详情
在 React 中,主要有以下几种开发方式:
1)函数式组件开发
这是目前最主流的开发方式,使用 JavaScript 函数来声明组件。函数接收 props 作为参数,返回 React 元素。
2)类组件开发
使用 ES6 的 class 语法来定义组件,继承自 React.Component
。虽然仍然可用,但在新项目中推荐使用函数式组件。
3)服务端组件(Server Components)
这是 React 18 引入的新特性,允许组件在服务器端运行和渲染,可以直接访问服务器资源,减少客户端 bundle 大小。
4)客户端组件(Client Components)
传统的 React 组件运行方式,组件在浏览器中运行,可以使用浏览器 API 和添加交互性。
React 的 props.children.map 和 JS 的 map 有什么区别?为什么优先选择 React 的 map?
详情
React 的 Children.map 和 JavaScript 的 map 主要有以下区别:
1)处理对象类型不同
JavaScript 的 map
只能处理数组,而 React 的 Children.map
可以处理所有类型的 children
,包括 undefined
、null
、字符串、数字、React 元素等。
2)安全性处理
React 的 Children.map
会自动处理 null
和 undefined
的情况,而 JavaScript 的 map
则需要手动处理这些边界情况。
3)扁平化处理
React 的 Children.map
会自动处理嵌套的结构,将多层级的 children
扁平化处理,而 JavaScript 的 map
则需要手动处理嵌套结构。
React 的 createClass 是怎么实现的?
详情
createClass
是 React 旧版本中用于创建组件的方法,在现代 React 开发中,推荐使用 ES6 类和函数式组件。下面我们来分析 createClass
的实现原理。
基本实现思路
createClass
的主要作用是将一个配置对象转换为一个 React 组件。这个配置对象包含了组件的各种属性和方法,如 render
方法、getInitialState
方法等。createClass
会对这些属性和方法进行处理,并返回一个可以用于渲染的组件类。
实现步骤
- 定义 createClass 函数:接收一个配置对象作为参数。
- 处理配置对象:提取配置对象中的属性和方法,如
render
、getInitialState
等。
- 处理配置对象:提取配置对象中的属性和方法,如
- 创建组件类:使用 JavaScript 的构造函数创建一个新的组件类。
- 绑定方法:将配置对象中的方法绑定到组件类的实例上。
- 返回组件类:返回创建好的组件类。
示例代码
// 定义 createClass 函数
function createClass(spec) {
// 创建一个新的组件类
class Component {
constructor(props) {
this.props = props;
// 初始化状态
if (spec.getInitialState) {
this.state = spec.getInitialState.call(this);
} else {
this.state = {};
}
}
// 绑定配置对象中的方法
componentWillMount() {
if (spec.componentWillMount) {
spec.componentWillMount.call(this);
}
}
render() {
return spec.render.call(this);
}
// 其他生命周期方法...
}
// 绑定方法到组件类的原型上
for (let key in spec) {
if (spec.hasOwnProperty(key) && typeof spec[key] === 'function') {
Component.prototype[key] = spec[key].bind(Component.prototype);
}
}
return Component;
}
// 使用 createClass 创建组件
const MyComponent = createClass({
getInitialState: function() {
return {
count: 0
};
},
incrementCount: function() {
this.setState({ count: this.state.count + 1 });
},
render: function() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
});
注意事项
createClass
已经被弃用:在现代 React 开发中,推荐使用 ES6 类和函数式组件。- 自动绑定:
createClass
会自动将所有方法绑定到组件实例上,而 ES6 类需要手动绑定。 getInitialState
方法:createClass
使用getInitialState
方法来初始化组件状态,而 ES6 类使用构造函数。
React 的 createElement 和 cloneElement 有什么区别?
详情
createElement 和 cloneElement 是 React 中两个用于创建元素的 API,它们有以下主要区别:
1)创建方式不同
createElement 是从零开始创建一个新的 React 元素:
- 需要指定元素类型(type)
- 可以设置新的 props
- 可以添加子元素(children)
cloneElement 是基于现有元素克隆并创建一个新的 React 元素:
- 使用原始元素的 type
- 可以添加或覆盖 props
- 可以替换或保留原有的子元素
2)使用场景不同
createElement:
- 是 JSX 的底层实现方式
- 用于创建全新的 React 元素
- 通常不会直接使用,而是使用 JSX 语法
cloneElement:
- 用于基于现有元素创建新元素
- 常用于需要为现有元素添加或修改 props 的场景
- 不推荐过度使用,因为会使数据流向难以追踪
React 项目中,什么时候使用箭头函数会更方便?
详情
在 React 项目中,箭头函数主要在以下几个场景会更方便:
1)事件处理函数
当需要传递额外参数给事件处理函数时:
<button onClick={(e) => handleClick(id, e)}>
点击
</button>
2)回调函数中
在 map 等数组方法的回调函数中:
{items.map((item) => (
<Item key={item.id} {...item} />
))}
3)组件内联渲染
当需要根据条件渲染不同内容时:
{isLoggedIn ? () => <UserGreeting /> : () => <GuestGreeting />}
扩展知识
1)性能考虑
箭头函数在每次渲染时都会创建新的函数实例,这在某些情况下可能影响性能:
- 1)当作为 props 传递给子组件时:
// 🚫 不推荐,每次渲染都会创建新函数
<Button onClick={() => handleClick(id)} />
// ✅ 推荐使用 useCallback 优化
const memoizedHandler = useCallback(() => {
handleClick(id)
}, [id])
<Button onClick={memoizedHandler} />
- 2)在 useEffect 依赖项中:
// 🚫 不推荐,会导致 useEffect 频繁触发
useEffect(() => {
const handler = () => {
// 处理逻辑
}
window.addEventListener('scroll', handler)
return () => window.removeEventListener('scroll', handler)
}, [])
// ✅ 推荐将函数提到组件外部或使用 useCallback
const handler = useCallback(() => {
// 处理逻辑
}, [])
2)使用场景建议
1)适合使用箭头函数的场景:
- 需要访问当前作用域的
this
- 简短的内联回调函数
- 条件渲染的函数组件
- 事件处理需要传递额外参数
2)不适合使用箭头函数的场景:
- 作为
props
传递给纯组件(React.memo
包装的组件) useEffect
的依赖项中- 复杂的类方法
- 需要使用
this.arguments
的场景
3)最佳实践
- 1)在类组件中:
class MyComponent extends React.Component {
// ✅ 推荐:使用箭头函数绑定方法
handleClick = () => {
console.log(this.props.name)
}
// 🚫 不推荐:在 render 中定义
render() {
return <button onClick={() => this.handleClick()}>点击</button>
}
}
- 2)在函数组件中:
function MyComponent() {
// ✅ 推荐:事件处理函数定义在组件内部
const handleClick = () => {
console.log('clicked')
}
// ✅ 推荐:需要依赖 props 或 state 时使用 useCallback
const handleSubmit = useCallback(() => {
// 处理逻辑
}, [/* 依赖项 */])
return <button onClick={handleClick}>点击</button>
}
什么是 React 中的非受控组件?它的应用场景是什么?
详情
非受控组件是指表单元素的值由 DOM 本身管理,而不是由 React 组件的 state 来控制。在非受控组件中,我们通常使用 ref 来获取表单值,而不是通过 onChange 事件来同步数据。
非受控组件的主要特点:
1)使用 defaultValue 或 defaultChecked
对于文本框使用 defaultValue
,对于复选框使用 defaultChecked
来设置初始值。
2)通过 ref 获取值
使用 React 的 ref
来获取表单元素的当前值,而不是通过 state
。
3)适用场景
- 表单验证:当只需要在提交时验证表单数据。
- 文件上传:处理
<input type="file">
这样的表单元素。 - 集成第三方 DOM 库:需要直接操作
DOM
的场景。 - 遗留系统集成:需要集成非 React 的代码库。
在 React 中,如何将参数传递给事件处理函数?
详情
在 React 中,有三种主要的方式向事件处理函数传递参数:
1)箭头函数方式
最常用的方式是通过箭头函数包装事件处理函数:
<button onClick={(e) => handleClick(param1, param2, e)}>
点击
</button>
2)bind 方法
使用 Function.prototype.bind()
预先绑定参数:
<button onClick={handleClick.bind(this, param1, param2)}>
点击
</button>
3)柯里化方式
通过函数返回函数的方式:
const handleClick = (param) => (e) => {
console.log(param, e);
};
<button onClick={handleClick(param)}>
点击
</button>
React 组件的构造函数是否必须存在?
详情
React 组件的构造函数不是必须的。在现代 React 开发中,我们有更简洁的方式来初始化 state
和绑定方法。
1)构造函数的主要用途
在类组件中,构造函数主要用于两个目的:
- 初始化组件的 state
- 绑定事件处理方法到实例
2)现代替代方案
使用类字段语法可以避免编写构造函数:
class Counter extends React.Component {
// 直接初始化 state
state = {
count: 0
};
// 使用箭头函数自动绑定
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
}
扩展知识
1)构造函数的使用注意事项
如果你确实需要使用构造函数,需要注意以下几点:
1)必须调用 super(props)
- 在构造函数中,必须先调用
super(props)
才能使用this
- 如果不调用,
this.props
在构造函数中会是undefined
2)避免副作用
- 构造函数中不应该有副作用或订阅相关的操作
- 这类操作应该放在
componentDidMount
中
3)state
初始化
- 构造函数是唯一可以直接给
this.state
赋值的地方 - 其他地方都需要使用
setState()
方法
2)类组件的发展趋势
1)函数组件的推荐
- React 团队推荐使用函数组件和 Hooks
- 类组件仍然被支持,但不建议在新代码中使用
2)Hooks 的优势
- 更简洁的代码结构
- 更好的逻辑复用
- 避免 this 绑定问题
3)示例对比
- 不使用构造函数的现代写法:
class Counter extends React.Component {
state = { count: 0 };
handleIncrement = () => {
this.setState(state => ({
count: state.count + 1
}));
};
render() {
return (
<button onClick={this.handleIncrement}>
Count: {this.state.count}
</button>
);
}
}
- 使用构造函数的传统写法:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleIncrement = this.handleIncrement.bind(this);
}
handleIncrement() {
this.setState(state => ({
count: state.count + 1
}));
}
render() {
return (
<button onClick={this.handleIncrement}>
Count: {this.state.count}
</button>
);
}
}
4)最佳实践建议
1)优先使用函数组件和 Hooks
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
2)如果必须使用类组件:
- 优先使用类字段语法
- 使用箭头函数避免手动绑定
- 只在确实需要构造函数特性时才使用它
为什么 React 中的 props 被认为是只读的?
详情
React 中的 props 被认为是只读的,主要有以下几个原因:
1)单向数据流
React 采用单向数据流的设计理念,数据只能从父组件流向子组件,子组件不能直接修改接收到的 props。这种设计让数据流向清晰可预测,方便追踪和调试。
2)保持组件的纯粹性
React 组件应该是纯函数,对于相同的输入(props)总是产生相同的输出(UI)。如果允许修改 props,就会破坏组件的纯粹性,导致组件的行为变得不可预测。
3)性能优化
props 的不可变性让 React 可以更高效地进行性能优化。React 可以通过对比新旧 props 的引用来判断组件是否需要重新渲染,如果 props 可以被修改,这种优化就失效了。
扩展知识
1)props 的特点
- 不可变性:
props
是组件的快照,每次父组件重新渲染时,子组件都会收到新的props
对象。 - 浅比较:React 使用
Object.is
来比较新旧props
的值,这是一个浅比较过程。
2)正确的数据更新方式
如果需要更新数据,应该:
- 在父组件中定义修改数据的方法,通过
props
传递给子组件。 - 子组件通过调用这些方法来请求数据更新。
- 父组件接收到更新请求后,使用
setState
更新状态,触发重新渲染。
3)实际应用示例
// 父组件
function Parent() {
const [name, setName] = useState('张三');
const handleNameChange = (newName) => {
setName(newName);
};
return <Child name={name} onNameChange={handleNameChange} />;
}
// 子组件
function Child({ name, onNameChange }) {
return (
<button onClick={() => onNameChange('李四')}>
当前名字: {name}
</button>
);
}
props 不可变带来的好处
- 调试更容易:由于数据流动方向单一,当出现问题时,更容易定位源头。
- 代码可维护性更好:组件之间的依赖关系更清晰,更容易理解和维护。
- 有利于性能优化:配合
React.memo
等 API,可以避免不必要的重新渲染。
使用 create-react-app 创建 React 项目有什么好处?
详情
使用 create-react-app
创建 React 项目有以下主要好处:
1)开发环境配置
create-react-app
提供了一个完整的开发环境,具体包括以下内容:
功能 | 说明 |
---|---|
内置的开发服务器 | 支持热更新,提升开发效率 |
现代 JavaScript 特性 | 支持 ES6+,使用最新的 JS 语法 |
自动化的构建流程 | 简化构建过程,提升开发体验 |
2)生产环境优化
create-react-app
自动集成了生产环境的优化功能:
功能 | 说明 |
---|---|
代码压缩和混淆 | 减少文件大小,提升加载速度 |
生成 source maps | 方便调试生产环境中的代码 |
资源文件添加 hash 值 | 支持长期缓存,提升用户体验 |
自动处理浏览器兼容性 | 确保应用在不同浏览器中正常运行 |
3)项目结构规范
create-react-app
提供了一个标准的项目结构:
功能 | 说明 |
---|---|
清晰的目录组织 | 方便项目管理和维护 |
合理的配置文件布局 | 提高配置的可读性和可维护性 |
统一的编码规范 | 保持代码风格一致,提升代码质量 |
扩展知识
1)内置功能
开箱即用的特性包括:
功能类别 | 具体功能说明 |
---|---|
CSS 支持 | CSS Modules、Sass/SCSS、PostCSS 处理、自动添加浏览器前缀 |
开发体验 | ESLint 代码检查、Jest 测试框架、友好的错误提示、开发环境的代理配置 |
2)构建优化
针对生产环境的优化:
优化类别 | 具体优化说明 |
---|---|
资源处理 | 静态资源的自动优化、图片和字体文件的优化处理、自动生成 HTML 文件 |
打包优化 | 代码分割、路由级别的代码分割、公共依赖的提取 |
3)可扩展性
虽然提供了默认配置,但也支持自定义:
扩展类别 | 具体扩展说明 |
---|---|
配置调整 | 通过 package.json 配置项目依赖、支持 eject 完全控制配置、可以通过 react-app-rewired 等工具修改配置 |
环境支持 | 支持不同环境的配置文件、环境变量的灵活配置、支持 PWA 功能的添加 |
4)注意事项
使用 create-react-app
也有一些需要注意的地方:
限制:
- 默认配置可能不满足复杂项目需求
- eject 后配置文件的维护成本增加
- 部分优化需要自行配置
建议:
- 小型项目可以直接使用默认配置
- 中大型项目可能需要考虑自定义配置
- 特殊需求可以考虑使用其他脚手架工具
React 中,如何防止 HTML 被转义?
详情
在 React 中,如果要防止 HTML 被转义,可以使用 dangerouslySetInnerHTML 属性。这是 React 提供的替代浏览器 DOM 中的 innerHTML 属性的方案。
使用方式如下:
const content = {
__html: '<p>这是一段 HTML</p>'
};
<div dangerouslySetInnerHTML={content} />;
扩展知识
1)使用注意事项
安全风险:使用 dangerouslySetInnerHTML 存在潜在的 XSS(跨站脚本攻击)风险,所以这个属性的命名以 dangerous 开头就是在提醒开发者要谨慎使用。
使用场景:只有在确实需要设置 HTML 内容,并且完全信任内容来源时才使用。比如:
- 渲染来自可信任的 CMS 系统的内容
- 渲染经过安全处理的 Markdown 内容
- 渲染富文本编辑器的内容
2)最佳实践
- 对象格式:传入的对象必须包含
__html
键,这是 React 的规定,不能改变。 - 创建时机:建议在靠近生成 HTML 的地方创建
dangerouslySetInnerHTML
对象,这样可以更清晰地标识哪些内容包含原始 HTML。
3)示例:渲染 Markdown 内容
function MarkdownPreview({ markdown }) {
const markdownToHtml = {
__html: convertMarkdownToHTML(markdown) // 使用 Markdown 解析库处理内容
};
return <div dangerouslySetInnerHTML={markdownToHtml} />;
}
4)替代方案
如果只是想展示文本内容,应该直接使用 JSX 的文本节点或者大括号插值,React 会自动处理 HTML 转义,保证安全性:
// 直接使用文本
<div>这是普通文本</div>
// 使用变量
const text = "这是普通文本";
<div>{text}</div>
如何有条件地渲染 React 组件?
详情
在 React 中实现条件渲染主要有以下几种方式:
1)使用 if 语句
在组件的 return
语句之前使用 if
语句进行条件判断,根据不同条件返回不同的 JSX。
function Component({ isLoggedIn }) {
if (isLoggedIn) {
return <UserDashboard />;
}
return <LoginForm />;
}
2)三元运算符
适用于简单的条件渲染场景,可以直接在 JSX 中使用。
function Component({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <UserDashboard /> : <LoginForm />}
</div>
);
}
3)逻辑与运算符 (&&)
当你只需要在条件为真时渲染内容时使用。
function Component({ notifications }) {
return (
<div>
{notifications.length > 0 && <NotificationList notifications={notifications} />}
</div>
);
}
扩展知识
1)条件渲染的注意事项
避免在条件渲染中使用以下值,因为它们会被 React 渲染出来:
false
会被忽略null
会被忽略undefined
会被忽略true
会被忽略0
会被渲染- 空字符串会被渲染
2)使用变量存储元素
对于复杂的条件渲染逻辑,可以使用变量来存储元素:
function Component({ isLoggedIn, userType }) {
let content;
if (isLoggedIn) {
if (userType === 'admin') {
content = <AdminDashboard />;
} else {
content = <UserDashboard />;
}
} else {
content = <LoginForm />;
}
return <div>{content}</div>;
}
3)使用对象映射
当有多个条件分支时,可以使用对象映射来优化代码结构:
const CONTENT_TYPES = {
draft: <DraftContent />,
published: <PublishedContent />,
archived: <ArchivedContent />
};
function Component({ status }) {
return (
<div>
{CONTENT_TYPES[status] || <DefaultContent />}
</div>
);
}
4)性能优化
如果条件渲染的组件计算成本较高,可以使用 React.memo
来避免不必要的重渲染:
const ExpensiveComponent = React.memo(function ExpensiveComponent() {
// 复杂的计算或渲染逻辑
});
function Component({ shouldShowExpensive }) {
return (
<div>
{shouldShowExpensive && <ExpensiveComponent />}
</div>
);
}
在 React 的 componentWillUpdate 中是否可以直接修改 state 的值?为什么?
详情
不可以在 componentWillUpdate
中直接修改 state
的值,原因如下:
1)违反 React 的单向数据流
React 组件的 state
是不可变的,必须通过 setState
方法来更新状态,以保证组件的可预测性。
2)可能导致无限循环
在 componentWillUpdate
中修改 state
会触发新的更新周期,从而导致组件陷入无限渲染的循环。
3)生命周期方法的定位
componentWillUpdate
的设计初衷是让开发者在组件更新前做一些准备工作,而不是用来修改组件的状态。
扩展知识
1)componentWillUpdate 的替代方案
componentWillUpdate
已被标记为 UNSAFE_componentWillUpdate
,React 官方建议使用以下方案替代:
- 1)如果需要根据
props
的变化来更新state
,使用getDerivedStateFromProps
:
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.value !== prevState.value) {
return {
value: nextProps.value
};
}
return null;
}
- 2)如果需要在更新后执行副作用,使用
componentDidUpdate
:
componentDidUpdate(prevProps, prevState) {
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
2)正确的状态更新方式
在 React 中,应该使用以下方式更新状态:
- 1)使用
setState
方法:
this.setState({ count: this.state.count + 1 });
- 2)如果新状态依赖于之前的状态,使用函数式更新:
this.setState(prevState => ({
count: prevState.count + 1
}));
3)React 状态更新的特点
1)状态更新可能是异步的:
setState
的调用会被合并- 多个
setState
可能会被批量处理 - 状态更新不会立即生效
2)状态更新会触发重新渲染:
- React 会将新的状态与旧的状态进行比较
- 只有在状态确实发生变化时才会触发重新渲染
- 可以使用
shouldComponentUpdate
或PureComponent
来优化性能
React 中如何监听 state 的变化?
详情
在 React 中监听 state
变化主要有以下几种方式:
1)使用 useEffect
最常用的方式是使用 useEffect
Hook,它可以在 state
变化时执行相应的副作用:
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// 当 count 变化时执行这里的代码
console.log('count changed:', count);
}, [count]); // 依赖数组中包含要监听的 state
}
2)在类组件中使用 componentDidUpdate
如果是类组件,可以使用 componentDidUpdate
生命周期方法:
class MyComponent extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (this.state.count !== prevState.count) {
// count 发生变化时执行这里的代码
console.log('count changed:', this.state.count);
}
}
}
扩展知识
1)监听多个 state 变化
1)可以在 useEffect
的依赖数组中添加多个 state
:
useEffect(() => {
// 当 name 或 age 变化时执行
console.log('name or age changed');
}, [name, age]);
2)也可以使用多个 useEffect
分别监听:
useEffect(() => {
// 只在 name 变化时执行
}, [name]);
useEffect(() => {
// 只在 age 变化时执行
}, [age]);
2)获取变化前后的值
1)在函数组件中,可以使用 ref
保存前一个值:
function MyComponent() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
2)在类组件中可以直接通过 componentDidUpdate
的参数获取:
componentDidUpdate(prevProps, prevState) {
// prevState.count 是变化前的值
// this.state.count 是变化后的值
}
3)注意事项
1)避免在监听函数中直接修改正在监听的 state
,这可能会导致无限循环:
// 🚫 错误示例
useEffect(() => {
setCount(count + 1); // 这会导致无限循环
}, [count]);
2)如果需要在 state
变化时执行异步操作,确保正确清理:
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await fetchSomething();
if (!ignore) {
// 确保组件还在挂载状态时才更新
setData(result);
}
}
fetchData();
return () => {
ignore = true; // 清理函数
};
}, [dependency]);
React 中 Element 和 Component 有什么区别?
详情
1)Element(元素)
React Element 是一个普通的 JavaScript 对象,用来描述你想要在屏幕上看到的内容。它是 React 应用中最小的构建块,包含以下属性:
- 1)type:可以是字符串(如 'div')或 React 组件(如 MyComponent)
- 2)props:传入的属性对象
- 3)key:用于标识元素的唯一值
- 4)ref:用于访问元素的引用
2)Component(组件)
Component 是一个可复用的代码块,它可以是一个函数或类,接收一些输入(称为 props)并返回一个 React Element。组件的特点:
- 1)可以包含状态(state)和生命周期方法
- 2)可以处理用户交互和业务逻辑
- 3)可以组合其他组件来构建复杂的 UI
什么是无状态组件?无状态组件的应用场景有哪些?
详情
1)无状态组件的定义
无状态组件是一种最简单的 React 组件形式,它是一个纯函数,接收 props
作为参数并返回 React 元素,不包含内部状态(state
)、生命周期方法和副作用。
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
2)无状态组件的特点
- 1)不使用
state
,只依赖传入的props
- 2)不包含生命周期方法
- 3)不使用
this
关键字 - 4)通常写成函数式组件的形式
- 5)代码简洁,易于测试和维护
React 中引入 CSS 的方式有哪些?
详情
1)外部样式表
- 1)通过 link 标签引入:
<link rel="stylesheet" href="styles.css" />
- 2)在 JavaScript 文件中导入:
import './styles.css';
2)内联样式
使用 style
属性,需要使用驼峰命名法:
<div style={{ backgroundColor: 'red', fontSize: '14px' }} />
3)CSS Modules
- 1)创建
[name].module.css
文件 - 2)在组件中导入并使用:
import styles from './Button.module.css';
function Button() {
return <button className={styles.button}>Click me</button>;
}
什么是 React 的事件机制?
详情
React 的事件机制主要包含以下几个核心特点:
1)合成事件系统
React 实现了一个合成事件系统(SyntheticEvent),它是对浏览器原生事件的跨浏览器包装。这个系统具有以下特点:
- 抹平了浏览器之间的差异,提供了一致的事件接口
- 所有事件都会被统一注册到
document
节点 - 通过事件委托机制来提高性能
2)事件委托
React 通过事件委托(Event Delegation)来处理事件:
- 不会把事件处理函数直接绑定到真实的节点上
- 而是把所有事件都绑定到结构的最外层(document),使用一个统一的事件监听器
- 当事件发生并冒泡到
document
时,React 将事件内容封装并交由真正的处理函数运行
3)事件池
React 实现了自己的事件池,在事件池中获取或释放事件对象,以避免频繁创建和销毁事件对象。
在 React 中,super() 和 super(props) 有什么区别?
详情
1)基本区别
super()
只会调用父类的构造函数super(props)
会调用父类的构造函数,并将props
传递给父类构造函数
2)使用影响
- 使用
super()
时,在构造函数中无法直接使用this.props
,因为props
没有被传递给父类构造函数 - 使用
super(props)
时,可以在构造函数中直接使用this.props
,因为props
已经被正确初始化
3)最佳实践
如果在构造函数中需要访问 this.props
,就必须使用 super(props)
。如果不需要访问,使用 super()
和 super(props)
都可以。但为了保持一致性和避免潜在问题,推荐始终使用 super(props)
。
React 中 setState 什么时候是同步的,什么时候是异步的?
详情
setState
的同步异步特性主要取决于执行上下文:
1)异步更新场景
在 React 18 中,setState
在以下场景中是异步批量执行的:
- React 事件处理函数内(比如
onClick
、onChange
等) - React 生命周期函数内
- 自定义 Hook 内
- React 可以接管的上下文中
2)同步更新场景
在以下场景中,setState
是同步执行的:
setTimeout/setInterval
等宏任务中- 原生
DOM
事件中 Promise
等异步操作的回调函数中- 其他
React
无法接管的上下文中
什么是 React 的实例?函数式组件是否有实例?
详情
1)React 实例的定义
React 实例是指通过类组件创建的对象实例。当使用类组件时,每个组件都会创建一个包含 state、生命周期方法和其他实例属性的对象。
2)函数组件与实例
函数组件本质上是一个函数,它不会创建实例。每次渲染时,函数组件都会被重新调用,而不是基于某个实例进行更新。
3)区别对比
类组件:
- 继承自 React.Component 或 React.PureComponent
- 拥有实例,可以访问 this
- 有完整的生命周期方法
- 可以使用 ref 直接引用组件实例
函数组件:
- 是一个纯函数
- 没有实例,不能使用 this
- 使用 Hooks 来管理状态和副作用
- 需要使用 forwardRef 来转发 ref
扩展知识
1)类组件实例的特点
生命周期:类组件实例具有完整的生命周期方法,如 componentDidMount
、componentDidUpdate
等。
状态管理:
- 通过
this.state
存储状态 - 使用
this.setState()
更新状态 - 可以访问实例方法和属性
2)函数组件的工作方式
状态管理:
- 使用
useState
Hook 管理状态 - 使用
useEffect
Hook 处理副作用 - 每次渲染都是独立的函数调用
3)React 团队的建议
React 团队推荐使用函数组件而不是类组件,原因如下:
代码简洁:函数组件的代码通常更简洁、更易于理解。
更好的性能:
- 没有实例化的开销
- 打包体积更小
- 更容易实现代码分
Hooks 的优势:
- 更容易复用状态逻辑
- 避免 this 绑定问题
- 减少代码重复
4)类组件到函数组件的迁移
如果需要将类组件迁移到函数组件,主要有以下转换:
- 1)state 转换:
// 类组件
class Example extends Component {
state = { count: 0 };
}
// 函数组件
function Example() {
const [count, setCount] = useState(0);
}
- 2)生命周期方法转换:
// 类组件
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
// 函数组件
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
说说真实 DOM 和虚拟 DOM 的区别?它们的优缺点是什么?
详情
1)真实 DOM
真实 DOM 是浏览器对 HTML 文档的对象表示,它是一个树形结构,每个节点代表文档中的一个元素、属性或文本。
特点:
- 直接操作页面元素
- 具有完整的 DOM API
- 与浏览器渲染引擎直接交互
2)虚拟 DOM
虚拟 DOM 是 React 在内存中维护的一个轻量级 JavaScript 对象,它是对真实 DOM 的抽象表示。
特点:
- 是普通的 JavaScript 对象
- 不直接操作浏览器 DOM
- 通过 diff 算法计算最小更新
3)工作流程
1)当数据发生变化时:
- React 首先在内存中生成新的虚拟 DOM 树
- 将新的虚拟 DOM 与旧的虚拟 DOM 进行对比
- 计算出需要更新的部分
- 只对变化的部分进行实际的 DOM 操作
扩展知识
1)虚拟 DOM 的优点
优点类别 | 具体优点 |
---|---|
性能优化 | 批量处理 DOM 更新,减少实际 DOM 操作次数,跨平台能力,可以在不同环境运行 |
开发体验 | 声明式编程,组件化开发,状态统一管理 |
2)虚拟 DOM 的缺点
缺点类别 | 具体缺点 |
---|---|
额外开销 | 需要额外的内存来存储虚拟 DOM,首次渲染可能比直接操作 DOM 慢,框架代码体积增加 |
使用限制 | 不适合频繁的小量数据更新,对于简单应用可能过度设计 |
3)适用场景
场景类型 | 适用场景 |
---|---|
虚拟 DOM 适合 | 复杂的单页应用,频繁的数据更新,需要跨平台的应用 |
直接操作 DOM 适合 | 简单的静态页面,极少量的 DOM 更新,特定的动画效果 |
4)React 中的优化
优化类型 | 具体优化措施 |
---|---|
批量更新 | React 会将多个 setState 合并成一次更新,减少不必要的渲染,提高应用性能 |
协调过程 | 采用 diff 算法对比虚拟 DOM,只更新必要的节点,维护组件的稳定性 |
渲染优化 | 使用 PureComponent 或 memo 减少重渲染,使用 key 属性帮助识别节点,合理拆分组件结构 |
React、React-dom 和 babel 的作用分别是什么?
详情
1)React 核心库
React 是整个 React 应用的核心库,它提供了组件定义、虚拟 DOM 处理、状态管理等基础功能。主要包含:
- 组件系统:类组件和函数组件的定义方式
- 状态管理:useState、useEffect 等 Hooks API
- 虚拟 DOM:实现高效的 DOM diff 和更新
- 生命周期:管理组件不同阶段的行为
2)React-dom 渲染库
React-dom 是 React 的渲染器之一,专门用于 Web 平台,负责将 React 组件渲染到浏览器 DOM 中。主要功能:
- 提供 ReactDOM.render() 方法渲染组件
- 处理事件系统
- 管理 DOM 更新
- 提供客户端水合(hydration)功能
3)Babel 编译工具
Babel 是 JavaScript 编译器,在 React 开发中主要用于:
- 将 JSX 语法转换为普通的 JavaScript 代码
- 转换现代 JavaScript 语法(ES6+)为浏览器兼容的代码
- 处理装饰器等实验性语法特性
扩展知识
1)React 生态系统的分离
React 核心库和渲染器的分离设计使得 React 具有更好的平台适应性:
React Native
:用于移动端渲染React-dom
:用于 Web 端渲染React-art
:用于绘制矢量图形
2)Babel 在 React 开发中的具体应用
Babel 配置示例:
presets 配置:
@babel/preset-react
:处理 JSX 语法@babel/preset-env
:处理 ES6+ 语法
plugins 配置:
@babel/plugin-proposal-class-properties
:支持类属性@babel/plugin-transform-runtime
:避免重复注入辅助代码
3)开发环境集成
在实际项目中,这三者通常通过构建工具(如 webpack)集成:
webpack.config.js
中配置babel-loader
处理.jsx
文件babel.config.js
配置编译规则package.json
中声明相关依赖
React 的 keys 是否需要设置为全局唯一?为什么?
详情
React 中的 keys 不需要设置为全局唯一,只需要在兄弟节点之间保持唯一即可,因为:
- 1)React 使用
key
的主要目的是在同一个父节点下的子元素之间进行区分和比较。 - 2)React 在进行
DOM diff
时,只会比较同一层级的节点,不会跨层级比较。因此key
只需要在当前列表范围内唯一就足够了。 - 3)不同父节点下的子元素可以使用相同的
key
,因为它们永远不会进行比较。
扩展知识
1)key 的使用场景
在实际开发中,key 主要用在以下场景:
- 列表渲染:在
map
循环中,每个列表项都需要一个唯一的key
。 - 动态组件:当使用相同的组件类型来展示不同的数据时。
- 表单重置:通过改变组件的
key
值可以强制组件重新渲染,重置其内部状态。
2)key 的最佳实践
选择 key 值的推荐方式:
- 使用后端返回的唯一标识:如数据库中的
id
。 - 使用稳定的业务属性:如用户的
email
、商品的SKU
等。 - 多字段组合:当单个字段无法保证唯一性时,可以组合多个字段。
3)key 的注意事项
- 避免使用不稳定的值:如
Math.random()
生成的随机数,这会导致每次渲染时都创建新的组件实例。 - 不要使用数组索引:在数据项顺序发生变化时会导致渲染错误。
- 保持一致性:一旦选定了某个属性作为
key
,就要保持使用该属性,不要在渲染过程中改变key
的来源。
4)性能考虑
- 合理的
key
设置可以提高 React 的渲染性能: - 帮助 React 准确识别发生变化的元素。
- 减少不必要的
DOM
操作。 - 确保组件状态的正确性。
为什么说:在 React 中,一切都是组件?
详情
在 React 中说"一切都是组件",主要基于以下几个方面:
- 1)组件是 React 应用的基本构建单元,整个应用都是由组件构成的组件树。
- 2)React 中的所有 UI 元素都可以被抽象为组件,从简单的按钮到复杂的页面布局。
- 3)组件可以像搭积木一样任意组合和嵌套,形成完整的应用界面。
扩展知识
1)组件的形式
React 中的组件主要有以下几种形式:
- 函数组件:使用函数声明的组件,是最简单和推荐的组件形式。
- 类组件:使用 ES6 class 声明的组件,提供了更多的特性。
- 高阶组件:用于复用组件逻辑的函数,它接收一个组件并返回一个新组件。
2)组件的特性
- 封装性:组件将 UI 和逻辑封装在一起,形成独立的功能单元。
- 可复用性:组件可以在不同的地方重复使用,提高代码复用率。
- 可组合性:可以将多个小组件组合成更大的组件。
- 单向数据流:父组件通过 props 向子组件传递数据。
3)组件的分类
- 展示型组件:专注于 UI 的呈现,不包含业务逻辑。
- 容器型组件:负责数据处理和状态管理。
- 通用型组件:如按钮、输入框等基础组件。
- 业务型组件:针对特定业务场景的组件。
4)组件化的优势
- 提高开发效率:组件的复用性降低了重复开发的工作量。
- 便于维护:组件的独立性使得代码更容易维护和测试。
- 团队协作:不同团队成员可以并行开发不同的组件。
- 性能优化:可以针对单个组件进行性能优化。
React 中 key 的作用是什么?
详情
React 中 key 的主要作用是帮助 React 识别哪些元素发生了变化,它在以下方面发挥着重要作用:
- 1)元素的唯一标识:在列表渲染中,key 帮助 React 准确追踪每个元素的身份。
- 2)性能优化:通过 key,React 可以最小化 DOM 操作,只更新真正发生变化的元素。
- 3)组件状态维护:key 帮助 React 在组件更新时保持正确的状态关联。
扩展知识
1)key 的工作原理
在 React 的虚拟 DOM diff 算法中:
- React 使用 key 来比较新旧虚拟 DOM 树中的元素。
- 当 key 值相同时,React 认为是同一个元素,只更新内容。
- 当 key 值不同时,React 会销毁旧元素,创建新元素。
2)key 的使用场景
- 列表渲染:在使用 map 方法渲染列表时必须提供 key。
- 动态组件:当需要强制重新创建组件时,可以通过改变 key 值实现。
- 表单重置:通过修改表单组件的 key 可以重置其状态。
3)key 的最佳实践
- 使用稳定的标识:优先使用后端返回的唯一 ID。
- 避免使用索引:不要使用数组的 index 作为 key,可能导致渲染错误。
- 保持唯一性:同级元素之间的 key 必须唯一。
4)不当使用 key 的后果
- 性能问题:错误的 key 会导致不必要的重渲染。
- 状态混乱:不恰当的 key 可能导致组件状态错误关联。
- 渲染错误:使用不稳定的 key 可能引起界面显示问题。
如何在 React 中阻止事件的默认行为?
详情
在 React 中阻止事件的默认行为,需要使用事件对象的 preventDefault() 方法。最常见的使用方式是:
- 1)在事件处理函数中调用 e.preventDefault()。
- 2)不要使用返回 false 的方式(这是 jQuery 的方式),React 中不支持这种写法。
- 3)阻止默认行为后,浏览器就不会执行事件的默认操作。
扩展知识
1)常见的默认行为场景
- 表单提交:阻止表单自动提交和页面刷新。
- 链接跳转:阻止 a 标签的默认跳转行为。
- 右键菜单:阻止浏览器默认的右键菜单。
- 拖拽事件:阻止默认的拖拽行为。
2)实际应用示例
表单提交示例:
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
// 自定义表单提交逻辑
}
return (
<form onSubmit={handleSubmit}>
{/* 表单内容 */}
</form>
);
}
链接点击示例:
function CustomLink() {
const handleClick = (e) => {
e.preventDefault();
// 自定义点击处理逻辑
}
return <a href="#" onClick={handleClick}>点击我</a>;
}
3)注意事项
- 尽早调用:在事件处理函数的开始就调用
preventDefault()
。 - 确保事件对象可用:事件处理函数必须接收事件对象参数。
- 不影响事件冒泡:
preventDefault()
只阻止默认行为,不会阻止事件冒泡。
4)相关事件方法
preventDefault()
:阻止默认行为。stopPropagation()
:阻止事件冒泡。defaultPrevented
:检查是否已调用过preventDefault()
。
React 生命周期有哪些阶段?每个阶段对应的函数是什么?
详情
React 生命周期主要分为三个阶段:
1)挂载阶段(Mounting):组件被创建并插入到 DOM 中
constructor -> render -> componentDidMount
2)更新阶段(Updating):组件的 props 或 state 发生变化时的重新渲染
shouldComponentUpdate -> render -> componentDidUpdate
3)卸载阶段(Unmounting):组件从 DOM 中被移除
componentWillUnmount
扩展知识
1)挂载阶段的函数
constructor
:初始化state
和绑定方法。render
:返回需要渲染的内容。componentDidMount
:组件挂载后执行,常用于:- 发起网络请求
- 添加订阅
- 操作
DOM
节点
2)更新阶段的函数
shouldComponentUpdate
:决定组件是否需要更新。render
:重新渲染组件。componentDidUpdate
:组件更新后执行,常用于:- 对比更新前后的
props
- 根据
props
变化发起网络请求 - 操作更新后的
DOM
- 对比更新前后的
3)卸载阶段的函数
componentWillUnmount
:组件卸载前执行,常用于:
- 清除定时器
- 取消网络请求
- 清除订阅
4)生命周期的最佳实践
- 避免副作用:
constructor
中不要执行副作用或订阅操作。 - 清理工作:在
componentWillUnmount
中清理所有订阅和监听。 - 条件判断:在
componentDidUpdate
中比较新旧props
避免无限循环。 setState
使用:避免在componentDidMount
中直接调用setState
。
React 的触摸事件有哪些?
详情
React 提供了四个主要的触摸事件:
- 1)onTouchStart:当手指放在屏幕上时触发。
- 2)onTouchMove:当手指在屏幕上滑动时触发。
- 3)onTouchEnd:当手指从屏幕上移开时触发。
- 4)onTouchCancel:当触摸被系统取消时触发(如接电话)。
扩展知识
1)触摸事件对象的属性
触摸事件对象(TouchEvent)包含以下重要属性:
- touches:当前屏幕上所有触摸点的列表。
- targetTouches:当前元素上的触摸点列表。
- changedTouches:触发当前事件的触摸点列表。
2)事件处理示例
function TouchDemo() {
const handleTouchStart = (e) => {
console.log('触摸开始', e.touches[0].clientX);
}
const handleTouchMove = (e) => {
console.log('触摸移动', e.touches[0].clientY);
}
return (
<div
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
style={{ height: '200px', background: '#f0f0f0' }}
>
触摸区域
</div>
);
}
3)触摸事件的特点
- 事件冒泡:触摸事件会向上冒泡,可以被父元素捕获。
- 多点触控:可以同时处理多个触摸点。
- 兼容性:在移动设备上广泛支持。
4)常见使用场景
- 滑动操作:实现轮播图、列表滑动等功能。
- 手势识别:实现缩放、旋转等手势操作。
- 拖拽功能:实现元素的拖拽移动。
在 React Router 中如何获取历史对象?
详情
在 React Router v6 中获取历史对象有以下几种方式:
- 1)使用
useNavigate
Hook(推荐方式):
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/home'); // 导航到指定路径
navigate(-1); // 返回上一页
navigate(1); // 前进一页
}
return <button onClick={handleClick}>点击导航</button>;
}
- 2)在类组件中使用
navigate
:
import { useNavigate } from 'react-router-dom';
class MyComponent extends React.Component {
handleClick = () => {
this.props.navigate('/home');
}
render() {
return <button onClick={this.handleClick}>点击导航</button>;
}
}
// 包装组件以注入 navigate
export default (props) => (
<MyComponent {...props} navigate={useNavigate()} />
);
扩展知识
1)navigate 的使用方式
- 基础导航:直接传入路径字符串。
- 带参数导航:传入对象配置导航选项。
// 基础导航
navigate('/home');
// 带参数导航
navigate('/user', {
state: { id: 123 },
replace: true // 替换当前历史记录
});
2)导航的特殊用法
- 返回上一页:
navigate(-1)
。 - 前进下一页:
navigate(1)
。 - 替换当前页:
navigate(path, { replace: true })
。
3)注意事项
- 只能在组件内使用:
useNavigate
是一个 Hook,只能在函数组件内使用。 - 组件外使用:需要通过特殊方式处理,如创建自定义 Router。
- 避免在异步回调中直接使用:可能会遇到闭包问题。
4)与旧版本的区别
- v5 使用
useHistory
,v6 改用useNavigate
。 - v6 移除了
history.push
,改用navigate
函数。 - v6 的 API 更简洁,使用更直观。
为什么在 React 项目标签中使用 htmlFor 而不是 for?
详情
在 React 项目中使用 htmlFor
而不是 for
的原因是:
- 1)
for
是 JavaScript 的保留字,用于循环语句。 - 2)React 使用
camelCase
命名约定来保持与DOM
属性的一致性。 - 3)这种命名方式可以避免与 JavaScript 关键字的冲突。
React 中如何获取组件对应的 DOM 元素?
详情
在 React 中获取组件对应的 DOM 元素主要有以下几种方式:
1)使用 useRef
Hook(函数组件):
function MyComponent() {
const myRef = useRef(null);
useEffect(() => {
// 可以访问 DOM 元素
console.log(myRef.current);
}, []);
return <div ref={myRef}>这是一个 DOM 元素</div>;
}
2)使用 createRef
(类组件):
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
// 可以访问 DOM 元素
console.log(this.myRef.current);
}
render() {
return <div ref={this.myRef}>这是一个 DOM 元素</div>;
}
}
扩展知识
1)ref 的使用场景
- 获取 DOM 元素尺寸和位置。
- 操作 DOM 元素(如 focus、播放视频等)。
- 集成第三方 DOM 库。
2)ref 转发
当需要在父组件中访问子组件的 DOM
元素时,需要使用 forwardRef
:
const ChildComponent = forwardRef((props, ref) => {
return <input ref={ref} />;
});
function ParentComponent() {
const inputRef = useRef(null);
useEffect(() => {
// 可以访问子组件中的 input 元素
inputRef.current.focus();
}, []);
return <ChildComponent ref={inputRef} />;
}
3)使用注意事项
- 避免过度使用:只在必要时才使用
ref
。 - 不要在渲染期间访问
ref
:应在effects
或事件处理中使用。 - 优先使用声明式解决方案:如受控组件。
4)ref 的替代方案
回调 ref
:可以更灵活地处理 ref
。
function MyComponent() {
const setRef = element => {
// element 是 DOM 元素或 null
if (element) {
element.focus();
}
};
return <input ref={setRef} />;
}
如何将事件传递给 React 子组件?
详情
在 React 中,将事件传递给子组件主要有三种方式:
1)通过 props 传递回调函数
最常用的方式是将父组件中定义的事件处理函数作为 props 传递给子组件。子组件通过调用这个函数来触发事件。
2)使用 children prop
可以通过 children prop 将包含事件处理的子元素传递给子组件。这种方式适合需要包装或增强子元素功能的场景。
3)使用 render props 模式
通过 render prop 传递一个函数,这个函数返回需要渲染的组件,并且可以包含事件处理逻辑。
扩展知识
1)基本事件传递示例
- 父组件:
function Parent() {
const handleClick = (e) => {
console.log('按钮被点击了');
}
return <Child onClick={handleClick} />;
}
- 子组件:
function Child({ onClick }) {
return <button onClick={onClick}>点击我</button>;
}
2)事件参数传递
如果需要在子组件中传递额外参数:
function Parent() {
const handleData = (data, e) => {
console.log('接收到数据:', data);
}
return <Child onData={handleData} />;
}
function Child({ onData }) {
return (
<button onClick={(e) => onData('some data', e)}>
发送数据
</button>
);
}
3)性能优化
当事件处理函数作为 props
传递时,可以使用 useCallback
来优化性能:
function Parent() {
const handleClick = useCallback((e) => {
console.log('按钮被点击了');
}, []);
return <Child onClick={handleClick} />;
}
4)注意事项
- 1)事件处理函数命名建议使用
handle
前缀,props
命名使用on
前缀。 - 2)确保传递的事件处理函数已经绑定了正确的
this
上下文(在类组件中)。 - 3)避免在事件处理函数中直接修改
props
,因为props
是不可变的。 - 4)如果子组件是类组件,需要注意在组件卸载时清理事件监听器。
React 的事件与普通 HTML 事件有什么区别?
详情
1)事件命名方式
HTML 事件使用全小写,而 React 事件采用驼峰命名法。
例如:HTML 中的 onclick 在 React 中要写成 onClick。
2)事件处理器写法
HTML 中事件处理器可以是字符串,而 React 中必须传入函数。
例如:HTML 中可以写 onclick="handleClick()",但在 React 中必须写成 onClick={handleClick}。
3)事件对象
React 提供了合成事件对象(SyntheticEvent),它是对原生事件对象的跨浏览器包装,确保在不同浏览器中行为一致。
扩展知识
1)事件处理机制
React 采用事件委托机制:
- 1)所有事件都被统一注册到
document
节点上,而不是直接绑定到DOM
元素。 - 2)当事件触发时,React 将事件逐级向上冒泡并处理。
- 3)这种机制可以节省内存,提高性能。
2)合成事件的特点
- 1)跨浏览器兼容性:React 抹平了不同浏览器的差异。
- 2)事件池:React 通过对象池来管理事件对象,避免频繁创建和销毁。
- 3)自动清理:事件处理完成后,合成事件对象的所有属性都会被置为
null
。
3)阻止默认行为的方式
- 1)HTML 中可以通过返回
false
或调用event.preventDefault()
。 - 2)React 中只能使用
e.preventDefault()
,不支持返回false
阻止默认行为。
4)事件冒泡的处理
- 1)HTML 中使用
event.stopPropagation()
阻止冒泡。 - 2)React 中使用
e.stopPropagation()
,但由于事件委托机制,其实现方式和原生的不完全相同。
React 处理表单输入的方法有哪些?
详情
React 中处理表单输入主要有两种方式:
1)受控组件(Controlled Components)
表单数据由 React 组件来管理:
- 1)使用 state 存储表单数据。
- 2)通过 onChange 事件更新 state。
- 3)表单元素的值由 state 控制。
2)非受控组件(Uncontrolled Components)
表单数据由 DOM 节点自己管理:
- 1)使用 ref 获取表单元素的值。
- 2)不需要为每个状态更新编写事件处理函数。
- 3)适合简单的表单场景。
什么是 React 中类组件和函数组件?它们有什么区别?
详情
React 中的组件主要分为类组件和函数组件两种:
1)函数组件
使用函数声明的组件,是目前 React 推荐的编写组件的方式:
- 1)更简单的代码组织方式。
- 2)使用 Hooks 特性管理组件状态和副作用。
- 3)没有 this 的困扰。
2)类组件
使用 ES6 class 声明的组件:
- 1)需要继承 React.Component。
- 2)必须实现 render 方法。
- 3)可以使用生命周期方法。
React 的代码编写规范有哪些?
详情
React 代码编写规范主要包括以下几个方面:
1. 命名规范
- 组件名称:必须以大写字母开头,使用 PascalCase。 例如:
UserProfile
、ProductList
。 - 事件处理函数:使用
handle
前缀。 例如:handleClick
、handleSubmit
。 - props 属性:使用 camelCase。 例如:
backgroundColor
、onClick
。
2. 文件组织
- 单一组件原则:每个文件只包含一个 React 组件。
- 文件名与组件名一致:组件文件名与组件名保持一致。
- 相关组件归类:相关组件放在同一目录下。
扩展知识
3. 组件编写规范
- 单一职责原则:组件职责要单一,避免过于复杂。
- 逻辑复用:提取可复用的逻辑到独立的 Hook。
- 类型检查:使用
PropTypes
或 TypeScript 进行类型检查。 - 避免深嵌套:避免过深的组件嵌套。
4. JSX 规范
- 多行包裹:使用小括号包裹多行 JSX。
- 标签闭合:标签要正确闭合。
- 属性分行:props 较多时每个属性占一行。
- 条件渲染:条件渲染使用三元运算符或
&&
运算符。
5. 状态管理规范
- 状态分组:将相关的状态放在一起。
- 避免冗余:避免冗余的状态。
- 命名描述性:使用
useState
时给状态起描述性的名称。 - 复杂状态处理:复杂状态考虑使用
useReducer
。
6. 样式规范
- 样式方案:推荐使用 CSS Modules 或
styled-components
。 - 命名有意义:样式命名要有意义且具体。
- 避免内联样式:避免内联样式,除非是动态计算的样式。
- 主题变量:使用主题变量管理颜色和尺寸。
7. 性能优化规范
- 避免重渲染:合理使用
React.memo
避免不必要的重渲染。 - 缓存值和函数:使用
useMemo
和useCallback
缓存值和函数。 - 虚拟滚动:大列表考虑使用虚拟滚动。
- 按需加载:按需加载组件使用
React.lazy
。
相比于原生开发,React 框架的优缺点是什么?
详情
1)React 的优点
React 提供了一些显著的优点,使其在现代前端开发中备受欢迎。
组件化开发
组件化开发是 React 的核心理念之一。通过将 UI 分解为独立的、可复用的组件,开发者可以更高效地管理代码。
- 代码复用性强:开发者可以在不同项目中重用相同的组件,减少重复代码。
- 便于维护和测试:组件化使得代码结构清晰,便于定位和修复问题。
- 团队协作效率高:组件的独立性使得团队成员可以并行开发不同的组件。
虚拟 DOM
虚拟 DOM 是 React 提升性能的关键技术。它通过在内存中维护一个轻量级的 DOM 树来减少实际 DOM 操作。
- 减少实际 DOM 操作:通过批量更新和最小化 DOM 操作,提升渲染性能。
- 提升渲染性能:虚拟 DOM 的差异算法使得更新更高效。
- 跨平台能力强:React Native 通过虚拟 DOM 实现跨平台开发。
单向数据流
React 采用单向数据流,确保数据在应用中的流动方向一致。
- 数据流向清晰:数据从父组件流向子组件,便于理解。
- 便于调试和维护:单向数据流使得状态变化更可预测。
- 状态可预测:状态管理工具(如 Redux)进一步增强了状态的可预测性。
2)React 的缺点
尽管 React 有许多优点,但也存在一些缺点。
学习成本
React 的学习曲线相对较陡,尤其对于新手开发者。
- 需要学习 JSX 语法:JSX 是 JavaScript 的扩展语法,初学者需要时间适应。
- 需要理解组件生命周期:理解组件的生命周期方法对于优化性能至关重要。
- 需要掌握状态管理:复杂应用需要使用状态管理工具,这增加了学习难度。
构建复杂度
React 项目的构建和配置可能比较复杂。
- 需要配置构建工具:如 Webpack、Babel 等工具需要配置。
- 打包体积较大:React 应用的初始打包体积可能较大,需要优化。
- 初始化项目繁琐:创建新项目需要配置多个工具和依赖。
React 和原生开发对比
以下是 React 和原生开发在不同方面的对比:
方面 | React 优势 | 原生开发优势 |
---|---|---|
开发效率 | 组件复用提高开发效率,丰富的生态系统和工具,热重载提升开发体验 | 不需要编译构建,浏览器直接支持,学习成本低 |
性能 | 虚拟 DOM 批量更新,可以实现精细的性能优化,代码分割和懒加载 | 没有框架额外开销,内存占用更少,首次加载更快 |
维护 | 组件化便于维护,状态管理清晰,TypeScript 支持好 | 代码简单直观,不依赖框架升级,调试更简单 |
适用场景 | 适合大型单页应用、需要频繁 DOM 更新、团队协作开发 | 适合简单的展示页面、对性能要求极高、小型项目 |
扩展知识
在选择使用 React 还是原生开发时,开发者需要根据项目的具体需求和团队的技术栈来做出决策。React 的组件化和虚拟 DOM 技术使其在复杂应用中表现出色,而原生开发则在简单项目和性能要求极高的场景中更具优势。
React 的生态系统提供了丰富的工具和库,如 Redux、React Router 等,帮助开发者更高效地构建应用。同时,React 的社区活跃,开发者可以从中获取大量的学习资源和支持。
对于新手开发者,建议从小型项目入手,逐步掌握 React 的核心概念和工具链。在学习过程中,可以参考 React 官方文档和社区资源,以加深对 React 的理解和应用能力。
React 组件的 state 和 props 有什么区别?
详情
state 和 props 是 React 组件中两个核心概念,它们有以下主要区别:
1. 数据控制权
- props:是外部传入的数据,组件本身无法修改。
- state:是组件内部的状态,组件可以通过
setState
方法自由修改。
2. 使用场景
- props:用于组件间的数据传递,实现父子组件通信。
- state:用于组件内部状态管理,处理用户交互、数据更新等。
3. 更新机制
- props:其更新由父组件决定,当父组件重新渲染时,子组件的 props 会更新。
- state:其更新由组件自身控制,调用
setState
方法后会触发组件重新渲染。
扩展知识
1. props 的特性
- 只读性:在 React 中,props 是只读的,组件不能修改自己的 props。
- 数据类型:props 可以传递任何类型的数据,包括字符串、数字、对象、函数等。
- 默认值:可以通过
defaultProps
为组件的 props 设置默认值。
2. state 的特性
- 异步更新:
setState
是异步操作,多个setState
调用可能会被合并。 - 不可变性:不要直接修改 state,而是创建新的对象来更新状态。
- 局部更新:
setState
只更新传入的字段,其他字段保持不变。
3. 使用建议
- 状态提升:当多个组件需要共享状态时,将
state
提升到它们最近的共同父组件。 - 无状态组件:如果组件只依赖
props
渲染界面,推荐使用函数组件。 - 避免冗余:不要将可以通过
props
或state
计算得到的数据存储在state
中。
React 项目中,如何动态改变组件的 class 来切换样式?
详情
在 React 中动态改变组件的 class 有以下几种常用方法:
1)使用 state 控制
通过 state 来控制类名的切换,结合模板字符串或条件运算符实现。
2)使用第三方库
可以使用 classnames 或 clsx 这类工具库来管理复杂的类名组合。
3)CSS Modules
使用 CSS Modules 可以实现样式的模块化管理,避免类名冲突。
扩展知识
1)基础实现方式
- 1)使用模板字符串:
function Button({ isActive }) {
return (
<button className={`btn ${isActive ? 'active' : ''}`}>
点击切换
</button>
);
}
- 2)使用数组 join:
function Card({ type, isHighlight }) {
const classes = [
'card',
type === 'primary' ? 'card-primary' : 'card-default',
isHighlight && 'card-highlight'
].filter(Boolean).join(' ');
return <div className={classes}>内容</div>;
}
2)使用 classnames 库
- 1)安装:
npm install classnames
- 2)基本使用:
import classNames from 'classnames';
function Alert({ type, isVisible }) {
const alertClass = classNames({
'alert': true,
'alert-success': type === 'success',
'alert-error': type === 'error',
'alert-hidden': !isVisible
});
return <div className={alertClass}>提示信息</div>;
}
3)CSS Modules 方案
- 1)创建样式文件(Button.module.css):
.button {
padding: 10px 20px;
}
.primary {
background: blue;
}
.secondary {
background: gray;
}
- 2)组件中使用:
import styles from './Button.module.css';
function Button({ type }) {
const btnClass = classNames(
styles.button,
type === 'primary' ? styles.primary : styles.secondary
);
return <button className={btnClass}>按钮</button>;
}
4)最佳实践
1)避免内联样式: 不推荐直接在 JSX 中使用
style
属性,因为这会导致性能问题。2)样式组织: 将样式逻辑抽离成独立的函数或自定义 Hook,提高代码复用性。
3)性能优化: 对于频繁变化的类名,可以使用
useMemo
缓存计算结果。
如果 React 的 Consumer 组件在上下文树中找不到 Provider,如何处理?
详情
当 Consumer 组件在上下文树中找不到 Provider 时,会有以下处理方式:
1)使用默认值
Consumer 会使用 createContext 时指定的默认值。这个默认值作为后备方案。
2)错误处理
如果没有指定默认值(值为 null),需要在组件中进行相应的错误处理或降级显示。
3)类型检查
使用 TypeScript 时,可以通过类型检查来确保 Provider 的正确使用。
扩展知识
1)默认值的使用
1)创建 Context 时设置默认值:
const ThemeContext = createContext('light');
const UserContext = createContext({ name: 'Guest' });
2)使用默认值的场景:
function ThemedButton() {
const theme = useContext(ThemeContext);
// 如果找不到 Provider,theme 的值将是 'light'
return <button className={theme}>按钮</button>;
}
2)错误处理方式
1)条件渲染:
function UserProfile() {
const user = useContext(UserContext);
if (!user) {
return <div>请先登录</div>;
}
return <div>欢迎, {user.name}</div>;
}
2)使用可选链:
function UserAvatar() {
const user = useContext(UserContext);
return <img src={user?.avatarUrl ?? '/default-avatar.png'} />;
}
3)最佳实践
1)Provider 的正确使用:
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Layout />
</ThemeContext.Provider>
);
}
2)嵌套多个 Provider:
function App() {
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<SettingsContext.Provider value={settings}>
<Layout />
</SettingsContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
3)自定义 Hook 封装:
function useTheme() {
const theme = useContext(ThemeContext);
if (!theme) {
throw new Error('useTheme 必须在 ThemeProvider 内部使用');
}
return theme;
}
React Router 中的 Link 标签和 HTML 的 a 标签有什么区别?
详情
Link
标签和 a
标签的主要区别在于:
1)路由行为
Link
标签是 React Router 提供的组件,实现单页应用(SPA)的路由跳转,不会刷新页面。a
标签是 HTML 原生标签,点击后会触发页面的完全刷新。
2)状态保持
Link
标签跳转时会保持应用的状态,包括组件状态和 Redux 等数据。a
标签跳转会导致应用状态丢失,需要重新加载和初始化。
3)性能表现
Link
标签只更新变化的部分,加载速度更快,用户体验更好。a
标签会重新请求页面资源,加载速度相对较慢。
如何使用 React 的 useState?请举例说明
详情
useState 是 React 的一个基础 Hook,用于在函数组件中添加状态管理能力。
1)基本语法
使用数组解构来获取状态值和更新函数:
const [state, setState] = useState(initialState);
2)使用场景
主要用于管理组件内的状态数据,比如表单输入、切换状态、计数器等。
3)更新方式
可以直接传值更新,也可以传入函数根据之前的状态计算新状态。
React 框架的核心思想有哪些?
详情
React 框架的核心思想
React 框架的核心思想主要包括以下几个方面:
1. 组件化
将用户界面拆分为独立、可复用的组件,每个组件维护自己的状态和逻辑。
2. 声明式编程
使用声明式的方式描述 UI,让开发者专注于描述想要的结果,而不是具体的实现步骤。
3. 单向数据流
数据在组件树中自上而下单向流动,使应用的数据流动更加可预测和易于理解。
扩展知识
1. 虚拟 DOM
1.1 原理
在内存中维护一个虚拟的 DOM 树,当状态发生变化时,先在虚拟 DOM 上进行操作。
1.2 优势
通过 diff 算法比较虚拟 DOM 的差异,最小化实际 DOM 操作,提升性能。
2. 状态管理
2.1 组件状态
使用 useState
、useReducer
等 Hook 管理组件内部状态。
2.2 状态提升
将共享状态提升到最近的共同父组件,实现状态共享。
2.3 全局状态
使用 Context API 或第三方状态管理库处理全局状态。
3. 组件通信
3.1 Props 传递
父组件通过 props 向子组件传递数据和回调函数。
3.2 Context
跨层级组件间的数据传递,避免 props 逐层传递。
3.3 事件机制
组件间通过自定义事件进行通信。
4. 性能优化
4.1 代码分割
使用 React.lazy
和 Suspense
实现按需加载。
4.2 缓存优化
使用 useMemo
和 useCallback
缓存计算结果和回调函数。
4.3 渲染优化
使用 React.memo
避免不必要的重渲染。
React JSX 怎么进行内联条件渲染?请举例说明
详情
在 React JSX 中,有多种方式实现内联条件渲染:
1)三元运算符
用于简单的条件判断,可以直接在 JSX 中使用。
2)逻辑与运算符 &&
用于条件为真时渲染内容,为假时不渲染。
3)立即执行函数
用于复杂的条件判断逻辑。
React 是否必须使用 JSX?为什么?
详情
React 不强制要求使用 JSX,但推荐使用它,原因如下:
1)可选性
JSX 是一种语法糖,最终会被编译成 React.createElement() 调用,你可以选择直接使用 createElement。
2)开发体验
JSX 提供了更直观的模板语法,使代码结构更清晰,开发效率更高。
3)工具支持
主流编辑器对 JSX 都有很好的支持,包括语法高亮、自动补全等功能。
React 中,父子组件如何进行通信?
详情
React 中父子组件通信主要有以下几种方式:
1)父传子:Props
父组件通过 props
向子组件传递数据,这是最基本也是最常用的通信方式。
2)子传父:回调函数
父组件通过 props
传递回调函数给子组件,子组件通过调用这个函数与父组件通信。
3)Context
当需要跨多层组件传递数据时,可以使用 Context
避免 props
逐层传递。
使用 ES6 或 ES5 语法来编写 React 代码有什么区别?
详情
什么是 React 的 getDefaultProps?它有什么作用?
详情
getDefaultProps
是 React 在 ES5 语法中用于设置组件默认属性的方法,它主要有以下作用:
1)设置默认值
当父组件没有传递某个 prop
时,getDefaultProps
返回的对象中的值会作为默认值。
2)使用方式
在 ES5 的 createClass
方法中使用:
var MyComponent = React.createClass({
getDefaultProps: function() {
return {
name: 'Guest',
age: 18
};
}
});
React 的 displayName 属性有什么作用?
详情
displayName
是 React 组件的一个特殊属性,主要用于调试目的,它有以下作用:
1)调试信息展示
在 React Developer Tools 中显示组件的自定义名称,便于在组件树中快速定位和识别组件。
2)使用场景
主要在以下情况下使用:
- 1)高阶组件(HOC)包装的组件
- 2)使用 React.memo、React.forwardRef 等 API 创建的组件
- 3)需要自定义组件在开发工具中显示名称的场景
扩展知识
1)设置方式
1)直接设置:
class MyComponent extends React.Component {}
MyComponent.displayName = 'CustomComponentName';
2)在高阶组件中设置:
function withSubscription(WrappedComponent) {
const WithSubscription = (props) => {
return <WrappedComponent {...props} />;
};
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
2)特点说明
1)开发环境作用:
- 仅在开发环境中生效
- 不影响生产环境的代码运行
- 有助于提高开发调试效率
2)命名规则:
- 应该使用有描述性的名称
- 推荐使用驼峰命名法
- 对于高阶组件,通常使用 WithXxx 的形式
3)使用建议
1)开发阶段:
- 为复杂组件设置 displayName
- 在使用高阶组件时务必设置
- 保持命名的语义化和可读性
2)生产环境:
- 可以通过打包工具移除 displayName
- 不要依赖 displayName 实现业务逻辑
React 中如何为非受控组件设置默认值?
详情
在 React 中,为非受控组件设置默认值主要通过以下方式:
1)使用 defaultValue 属性
用于文本输入框、文本域等表单元素:
<input type="text" defaultValue="默认文本" />
<textarea defaultValue="默认文本" />
2)使用 defaultChecked 属性
用于复选框和单选按钮:
<input type="checkbox" defaultChecked={true} />
<input type="radio" defaultChecked={true} />
React 中有几种构建组件的方式?它们的区别是什么?
详情
回答重点 React 中主要有三种构建组件的方式:
1)函数组件
最简单也是现在最推荐的方式:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
2)类组件
使用 ES6 的 class 语法创建:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
3)React.createClass(已废弃)
早期 React 使用的方式:
var Welcome = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
React 组件的构造函数有什么作用?
详情
React 组件的构造函数(constructor)主要有两个作用:
1)初始化组件的 state
在 constructor 中可以直接通过 this.state 来初始化组件的状态。这是唯一可以直接给 this.state 赋值的地方,其他地方都需要通过 setState 方法来更新状态。
2)绑定事件处理函数
在 constructor 中可以使用 bind 方法将类的方法绑定到实例上,确保在事件处理函数中可以正确访问组件实例的 this。
扩展知识
1)constructor 使用注意事项
如果定义了 constructor,必须先调用 super(props),否则 this.props 在 constructor 中会是 undefined。
不要在 constructor 中执行副作用或订阅操作,这些应该放在 componentDidMount 中。
不要在 constructor 中调用 setState,直接使用 this.state 赋值。
2)现代 React 开发中的 constructor
随着 JavaScript 特性的发展,constructor 的使用频率在降低:
可以使用类字段语法直接声明 state,不需要在 constructor 中初始化。
可以使用箭头函数定义方法,自动绑定 this,不需要在 constructor 中手动绑定。
3)函数组件中的替代方案
在函数组件中,没有与 constructor 完全对应的概念:
使用 useState Hook 来声明和初始化状态。
如果初始 state 的计算比较昂贵,可以给 useState 传入一个函数来惰性初始化。
4)严格模式下的行为
在开发环境中,当开启了严格模式(StrictMode),React 会调用两次 constructor,这有助于发现意外的副作用。第二次调用的结果会被丢弃。
如果 React 的 render 函数中的 return 没有使用圆括号,会出现什么问题?
详情
在 React 的 render 函数中,return 语句不使用圆括号可能会导致两个主要问题:
1)自动分号插入导致的返回值错误
JavaScript 会在某些换行的地方自动插入分号,这可能导致 return
语句提前终止,返回 undefined
,而不是期望的 JSX。
2)多行 JSX 解析错误
当 JSX 内容跨多行时,如果不使用圆括号包裹,浏览器可能无法正确解析 JSX 的结构,导致语法错误。
在 React 自定义组件中,render 函数是可选的吗?为什么?
详情
render 函数是否可选取决于组件的类型:
1)类组件
在类组件中,render 函数是必需的。它是 React.Component 中唯一必须实现的方法。因为类组件需要通过 render 函数来定义组件的输出内容。
2)函数组件
在函数组件中,不存在独立的 render 函数的概念。函数组件本身就相当于 render 函数,它直接返回要渲染的内容。
扩展知识
1)类组件中的 render
render 函数必须返回一个 React 元素,可以是:
- JSX 元素
- 数组或
fragments
Portals
- 字符串或数值类型
- 布尔值或
null
2)render 函数的特点
- 纯函数:不应该修改组件的状态。
- 避免副作用:不应该直接与浏览器交互。
- 可以返回
null
:当不想渲染任何内容时。
3)render 的调用时机
- 组件初始化渲染时。
- 组件更新时(
state
或props
变化)。 - 父组件重新渲染时。
4)性能考虑
render
函数应该保持简单和高效。- 可以使用
React.memo
或shouldComponentUpdate
来避免不必要的渲染。 - 复杂的计算逻辑应该在
render
之外处理。
为什么 React 不推荐直接修改 state?如果需要修改 state,应该如何操作?
详情
React 不推荐直接修改 state
的原因:
1)状态更新可能是异步的
直接修改 state
不会触发组件重新渲染,因为 React 可能会批量处理状态更新以提高性能。
2)破坏状态的不可变性
直接修改 state
会破坏 React 的不可变性原则,这可能导致一些性能优化失效,并使代码难以调试和维护。
扩展知识
1)正确的 state 更新方式
- 使用
setState
方法: - 类组件中使用
this.setState()
。 - 函数组件中使用
useState
的更新函数。
2)处理对象类型的 state
- 使用扩展运算符创建新对象:
setState({
...state,
property: newValue
});
3)处理数组类型的 state
使用数组方法返回新数组:
- 添加元素:使用展开运算符或
concat
。 - 删除元素:使用
filter
。 - 更新元素:使用
map
。
4)复杂状态的更新
对于复杂的状态更新,可以:
- 使用 immer 等不可变状态库。
- 将复杂状态拆分为多个简单状态。
- 使用
useReducer
管理复杂状态逻辑。
在 React 中,如何判断点击的元素属于哪个组件?
详情
在 React 中,可以通过事件对象(event)的两个重要属性来判断点击元素与组件的关系:
1)event.target
指向触发事件的具体 DOM 元素(实际被点击的元素)。
2)event.currentTarget
指向事件绑定的组件或元素(事件处理函数所在的组件)。
扩展知识
1)实际应用示例
function MyComponent() {
const handleClick = (event) => {
console.log('点击的具体元素:', event.target);
console.log('事件绑定的组件:', event.currentTarget);
// 判断是否点击了当前组件内部
if (event.target === event.currentTarget) {
console.log('直接点击了组件本身');
}
};
return (
<div onClick={handleClick}>
<button>点击我</button>
</div>
);
}
2)事件冒泡机制
- React 事件遵循冒泡机制,从触发元素向上传播。
- 可以使用
event.stopPropagation()
阻止事件冒泡。 - 使用
event.preventDefault()
阻止默认行为。
3)判断方法
通过 contains
方法判断:
if (componentRef.current.contains(event.target)) {
console.log('点击发生在组件内部');
}
4)常见使用场景
- 点击外部关闭弹窗。
- 实现自定义下拉菜单。
- 处理复杂的嵌套组件交互。
5)注意事项
- React 事件是合成事件,封装了原生 DOM 事件。
- 在异步操作中,需要使用
event.persist()
保留事件对象。 - 不同浏览器的事件对象可能略有差异。
为什么在 React 中使用 className 而不是 class?
详情
React 使用 className
而不是 class
的主要原因:
1)避免与 JavaScript 关键字冲突
class
是 JavaScript 的关键字,用于声明类。为了避免混淆和潜在的问题,React 选择使用 className
。
2)与 DOM API 保持一致
在 JavaScript 中,DOM 元素的 class
属性名就是 className
,React 保持了与 DOM API 的一致性。