1. 在 React 中,如何检验 props?为什么要验证 props?
在 React 中,你可以使用 PropTypes 库来检查组件的 props。这可以确保组件收到的 props 类型正确,避免在应用运行过程中出现意外错误。具体的做法是导入 PropTypes 库,并为每个 prop 定义相应的类型和是否必需。
首先,你需要安装 prop-types
库:
npm install prop-types
接下来,在组件中进行使用:
import PropTypes from 'prop-types';
import React from 'react';
class MyComponent extends React.Component {
render() {
// 组件的渲染逻辑
return (
<div>{this.props.name}</div>
);
}
}
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isActive: PropTypes.bool,
};
验证 props 的目的是为了确保我们向组件传递的数据是预期中的,这样可以减少潜在的错误,尤其是在大型应用中互相调用的组件很多时,prop 类型检查就显得尤为重要。
扩展知识
为什么要验证 props?除了避免运行时错误,还有以下几个原因:
1)提高代码的可读性和可维护性:明确地定义了每个 prop 的类型和是否必需,可以让其他开发者一目了然,知道该组件需要哪些数据以及这些数据的类型,从而减少了沟通成本。
2)在开发阶段发现错误:PropTypes 允许你在开发阶段就发现并修正传递错误的 props 的问题,而不是在用户使用时才发现,从而提高了开发效率和代码质量。
3)增强组件的健壮性:当组件需要从父组件接收复杂的数据时,使用 PropTypes 可以确保接收到的数据是有效的,从而避免组件由于接收到无效数据而崩溃。
而且,PropTypes 检查的功能也非常强大,支持多种数据类型和自定义的验证函数: 1)PropTypes.array
:数组 2)PropTypes.bool
:布尔值 3)PropTypes.func
:函数 4)PropTypes.number
:数字 5)PropTypes.object
:对象 6)PropTypes.string
:字符串 7)PropTypes.node
:任何可以被渲染的内容 8)PropTypes.element
:React 元素
除此之外,PropTypes 还可以验证某些更复杂的条件,比如: 1)PropTypes.arrayOf(PropTypes.number)
:数组元素都是数字 2)PropTypes.oneOf(['Red', 'Green'])
:只能是给定值中的一个 3)PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number })
:指定结构的对象
2. React 的 JSX 和 HTML 有什么区别?
React 的 JSX 和 HTML 之间有几个明显的区别:
1)JSX 是 JavaScript 的语法扩展,用于在 React 中描述用户界面,而 HTML 是一种标记语言,用于构建网页内容。
2)JSX 中的属性命名使用驼峰命名法(camelCase),而 HTML 则使用全小写。例如,JSX 中的 class
要写成 className
。
3)在 JSX 中,你可以在大括号 {}
内嵌入任何 JavaScript 表达式,以实现动态内容,而 HTML 不支持这种功能。
4)JSX 元素必须闭合,无论是单标签元素还是双标签元素。而在 HTML 中,某些标签可以不必闭合。
扩展知识
看来这个题目涉及的知识点还是比较多的,所以我再延伸讲解一下。
1)JSX 是在 React 中替代 HTML 的一种语法糖
JSX 虽然看起来和 HTML 很像,但实际是 JavaScript 代码。在构建界面时,JSX 比纯 JavaScript 更直观,更易读。
2)属性命名的区别
我再具体讲几个例子:
onclick
在 HTML 中是小写的,但在 JSX 中是onClick
。for
是 HTML 中的属性,但在 JSX 中需要写作htmlFor
。 这背后的原因是,JSX 是一种 JavaScript 的语法扩展,而变量名和属性名在 JavaScript 中一般采用驼峰命名法。
3)动态内容
动态内容是 React 的强项之一。比如:
const name = 'React';
const element = <h1>Hello, {name}!</h1>;
在这个例子中,{name}
会被解析为 JavaScript 变量,并插入到最终的结果中。
4)元素闭合
在 React 中,所有的标签都需要闭合,即使是单标签元素。例如:
// 错误的写法
const element = <img src="image.jpg">;
// 正确的写法
const element = <img src="image.jpg" />;
3. 如何在 React JSX 中实现 for 循环?
在 React JSX 中,我们无法直接使用常规的 for
循环语句,因为 JSX 是一种语法糖,需要在 JavaScript 代码中返回用于渲染的元素。相反,我们通常通过数组的 map()
方法来实现循环渲染。
具体来说,当我们需要在 JSX 中进行循环渲染时,会利用 Array.prototype.map()
方法来创建一个包含 JSX 元素的新数组,最后返回这个数组,从而在 JSX 中渲染出我们想要的内容。
例如,有以下代码示例:
import React from 'react';
const items = ['Apple', 'Banana', 'Cherry'];
function ItemList() {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
export default ItemList;
在上面的例子中,我们通过 map()
方法将 items
数组中的每一个元素映射成一个 <li>
元素,并且用 key
属性标识每个元素。
扩展知识
我们可以进一步讨论一些相关的知识点以增强理解:
1)为什么使用 map()
而不是 for
循环:
- JSX 是一种语法糖,直接使用
for
循环不方便将结果返回。map()
方法能够更方便地生成并返回一个包含 JSX 元素的数组,这样可以直接嵌入到返回的 JSX 结构中。
2)React中的 key
属性:
- 当我们使用
map()
来创建列表的 JSX 元素时,给每个元素添加一个唯一的key
属性非常重要。React 使用key
来识别哪些元素发生了变化、添加或移除。这有助于优化渲染性能和确保正确地处理元素更新。
3)处理复杂逻辑:
-
如果你需要在循环过程中处理更复杂的逻辑,可以将渲染逻辑拆分为函数或者方法。例如:
function renderItems(items) { return items.map((item, index) => ( <li key={index}>{item}</li> )); } function ItemList() { const items = ['Apple', 'Banana', 'Cherry']; return ( <ul> {renderItems(items)} </ul> ); }
4)使用 for…of
循环(ES6 特性):
-
虽然传统的
for
循环不直接适用于 JSX,但你可以在方法内部使用for…of
循环来进行额外的处理,然后返回 JSX:function ItemList() { const items = ['Apple', 'Banana', 'Cherry']; const itemElements = []; for (const [index, item] of items.entries()) { itemElements.push(<li key={index}>{item}</li>); } return ( <ul> {itemElements} </ul> ); }
4. 如何在 React Router 中设置重定向?
在 React Router 中设置重定向,你可以使用 <Redirect>
组件或使用 useHistory
钩子函数来实现重定向。
1)使用 <Redirect>
组件: 在 React Router v5 中,你可以通过在 <Switch>
组件中使用 <Redirect>
组件来设置重定向。例如:
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Redirect from="/old-path" to="/new-path" />
<Redirect to="/" />
</Switch>
</Router>
);
2)使用 useHistory
钩子: 在函数组件中,你可以使用 useHistory
钩子进行编程式导航,如下所示:
import { useHistory } from 'react-router-dom';
const SomeComponent = () => {
const history = useHistory();
const handleRedirect = () => {
history.push('/new-path');
};
return (
<button onClick={handleRedirect}>Go to New Path</button>
);
};
扩展知识
- React Router 的版本差异: React Router v6 进行了重大改动,移除了
<Redirect>
组件,改为使用<Navigate>
组件,同时引入新的路由配置方案。使用示例如下:
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
const App = () => (
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="old-path" element={<Navigate to="/new-path" />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</Router>
);
- 编程式导航: 除了
useHistory
,React Router v6 引入了useNavigate
钩子来替代useHistory
,用法类似:
import { useNavigate } from 'react-router-dom';
const SomeComponent = () => {
const navigate = useNavigate();
const handleRedirect = () => {
navigate('/new-path');
};
return (
<button onClick={handleRedirect}>Go to New Path</button>
);
};
- 其他重定向方式: 除了
<Redirect>
和useHistory
/useNavigate
,有时还可能需要根据条件进行重定向,可以在组件生命周期中如componentDidMount
或useEffect
中进行,例如:
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
const ConditionalRedirect = ({ isLoggedIn }) => {
const navigate = useNavigate();
useEffect(() => {
if (!isLoggedIn) {
navigate('/login');
}
}, [isLoggedIn, navigate]);
return isLoggedIn ? <Dashboard /> : null;
};
5. 什么是 React 中的非受控组件?它的应用场景是什么?
React 中的非受控组件是指那些不通过 React 的 state 来控制其值的表单组件,而是直接利用 DOM 的内置机制(比如 ref
)来操作和获取组件的值。与受控组件不同,非受控组件的值的变化不是在 React 的状态中进行追踪,而是在组件的内部由 DOM 自身管理。
应用场景方面,非受控组件适用于一些不需要频繁与 React 状态同步的表单,或者那些需要快速移植并且不想重构为受控组件的代码场景。例如,简单的表单收集、历史遗留代码的整合、对性能要求较高的应用场景等。
扩展知识
1)受控组件和非受控组件的区别 - 受控组件:组件的状态和其值完全依赖于 React 的 state 来管理。所有的数据变化都必须通过 state 的 setState
方法来更新。 - 非受控组件:组件的值主要通过 DOM 自身来管理,React 通过 ref
来获取组件的当前值。你不需要用 setState
来更新组件的值。
2)如何使用非受控组件 - 使用 ref
获取 DOM 元素的引用。 - 举个简单的例子:
import React, { createRef } from 'react';
class MyForm extends React.Component {
constructor(props) {
super(props);
this.inputRef = createRef();
}
handleSubmit = (event) => {
event.preventDefault();
alert('A name was submitted: ' + this.inputRef.current.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.inputRef} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default MyForm;
以上代码中,我们使用 ref
获取了 input 元素,在提交表单时直接通过 this.inputRef.current.value
获取其值,这便是典型的非受控组件用法。
3)应用场景详解 - 简单数据收集:对于简单的数据收集表单,比如仅需获取少量的用户输入并立即进行操作,非受控组件使用起来更为直接和简便。 - 历史遗留项目整合:当你需要将一些遗留的表单代码快速移植到 React 中,不希望花费大量时间重构为受控组件,非受控组件是不二选择。 - 性能优化:如果表单中的某些数据不需要频繁与 React 的状态同步,使用非受控组件能减少不必要的重渲染,从而提高性能。
4)注意事项 - 非受控组件虽然更简单直接,但也缺乏受控组件提供的更细粒度的控制和即时反馈。在复杂的表单中使用时可能会面临挑战,应该谨慎选择。
6. React 中,如何防止 HTML 被转义?
在 React 中,如果你想防止 HTML 被转义,可以使用 dangerouslySetInnerHTML
属性。这个属性允许你直接将 HTML 字符串注入到 DOM 中。这样,React 不会对传入的 HTML 字符串进行任何转义操作。
具体用法如下:
const rawHTML = "<p>This is some <strong>raw</strong> HTML.</p>";
function MyComponent() {
return (
<div dangerouslySetInnerHTML={{ __html: rawHTML }} />
);
}
在这个例子里,我们创建了一个变量 rawHTML
,它包含未转义的 HTML 字符串。然后,我们将这个变量传递给一个包含 dangerouslySetInnerHTML
属性的 div
元素。
扩展知识
1)使用 dangerouslySetInnerHTML
的注意事项:
- 安全性:由于直接插入 HTML 内容会带来 XSS(跨站脚本攻击)的安全风险,因此对外部数据(尤其是用户输入的数据)进行严格的验证和消毒是至关重要的。你可以使用第三方库,比如 DOMPurify,来净化输入的 HTML。
- 代码可读性:在代码评审过程中,直接看到
dangerouslySetInnerHTML
这样的属性会提醒开发者注意潜在的安全隐患。正因为如此,React 使用了这种略显“危险”的命名方式。
2)防止 HTML 被转义的其他方法:
- 模板引擎:如果在服务器端生成 React 组件,可以使用模板引擎来插入未经转义的 HTML。
- 第三方库:例如,使用
react-html-parser
这样的库来解析和渲染 HTML 字符串。这个库的一大好处是内置了一些安全措施。
3)React 的转义机制:React 默认会对 JSX 中的内容进行转义以防止 XSS 攻击。这是一种保护机制,确保插入页面的内容是安全的。例如:
const maliciousString = "<script>alert('XSS');</script>";
// React will escape this string and it will render as plain text
<div>{maliciousString}</div>
4)社区实践和最佳实践:
- 尽量避免使用直接插入 HTML 的方式:除非有明确原因(如展示来自可信来源的富文本内容),否则尽量避免使用
dangerouslySetInnerHTML
属性。 - 验证和净化:任何从外部获取的 HTML 内容都应先经过验证和净化。例如,评论系统或富文本编辑器的内容需要经过严格的安全检查,以防止 XSS 攻击。
7. 为什么说:在 React 中,一切都是组件?
在 React 中,我们通常说“一切都是组件”,这是因为 React 的设计思想就是将用户界面抽象成一个个可复用的组件。这些组件既能描述结构,也能描述行为和状态。通过这种方式,开发者可以将 UI 拆分成独立的、分离的“构建块”,这些“构建块”可以单独编写、测试和维护,然后组合成完整的应用程序。React 组件可以是类组件或者函数组件,每个组件可以包含内部状态和生命周期方法(对于类组件而言),这使得组件能够自我管理其内部状态和行为。这种模块化的设计使得代码更加可维护和可扩展。
扩展知识
要理解“一切都是组件”,我们可以从以下几个方面进一步展开:
1)组件的类型:
-
函数组件:是一种使用 JavaScript 函数定义的组件,通常有更简洁的语法并依赖 React 的 Hooks 功能来处理状态和副作用。示例:
function MyComponent(props) { const [state, setState] = React.useState(initialState); React.useEffect(() => { // side effect code }, []); return <div>{props.someProp}</div>; }
-
类组件:就是用 ES6 类定义的组件,类组件可以有自己的状态(通过
this.state
)和生命周期方法。示例:class MyComponent extends React.Component { constructor(props) { super(props); this.state = { value: initialState }; } componentDidMount() { // side effect code } render() { return <div>{this.props.someProp}</div>; } }
2)组件的复用和组合:
- 通过将 UI 分成组件,开发者可以轻松将一个组件放入另一个组件中,从而实现复杂界面的构建。例如,一个导航栏组件可以由多个按钮组件组成。
- 组件可复用性让你可以在多个地方使用同一个组件,从而减少重复代码,提高开发效率和代码一致性。
3)状态与生命周期:
- 类组件通过
this.state
和生命周期方法(如componentDidMount
、componentDidUpdate
和componentWillUnmount
)管理组件的内部状态和副作用。 - 函数组件则使用 React Hooks,如
useState
、useEffect
等,来实现类似的功能。
4)各类组件的风格和最佳实践:
- 无状态组件(Stateless Component):即纯展示组件,没有自己的状态,只通过 props 接受数据。如果组件不需要管理任何状态或者副作用,可以考虑使用无状态组件。
- 有状态组件(Stateful Component):这些组件内部包含状态,用来保存某些动态信息并用于重新渲染组件。通常用于需要用户交互或者后台数据的场景。
- 容器组件和展示组件:一种常见的 React 组件分层思想,即将主要负责数据逻辑的容器组件(Container Component)和负责展示的展示组件(Presentational Component)分开,增加代码的可读性和可维护性。
8. React 的代码编写规范有哪些?
React 代码编写规范主要包括以下几个方面:
1)组件命名:组件名应该采用大驼峰命名法(PascalCase),如MyComponent
,方便区分普通的HTML元素与React 组件。
2)文件结构:遵循组织良好的文件结构,通常每个组件一个文件,并且文件名与组件名保持一致,例如:MyComponent.js
。
3)JSX 语法:JSX 中嵌入 JavaScript 代码时,使用小括号包裹代码。避免使用纯 JavaScript 拼接字串形成的方式。保持 JSX 清晰可读。
4)状态和属性:避免在组件中直接修改 state 和 props,应该使用setState
方法来更新 state,props 是只读的,不能修改。
5)无状态组件:对于仅依赖 props 渲染的组件,使用函数组件而不是类组件,因为函数组件更轻量且易于测试。
6)PropTypes:对组件的 props 进行类型检查,使用PropTypes
确保传递到组件的属性类型正确,有助于提高代码的健壮性和可维护性。
7)事件处理:事件处理函数应命名为动词短语,例如:handleClick
或onSubmit
,并且应使用箭头函数或者bind
绑定this上下文。
8)避免重复代码:提取公共逻辑和样式到单独的函数或组件中,遵循DRY(Don't Repeat Yourself)的原则。
9)Linting:使用 ESLint 标准规范化代码风格,通过 linting 工具确保代码的一致性和质量。
扩展知识
在以上提到的基础规范之外,还有一些扩展知识可以帮助你更好地编写 React 代码:
1)目录结构:可以参考以下目录层级,更容易管理:
src/
│-- components/
│ │-- Header/
│ │ │-- Header.js
│ │ │-- Header.css
│ │-- Footer/
│ │ │-- Footer.js
│ │ │-- Footer.css
│-- utils/
│ │-- api.js
│-- App.js
│-- index.js
2)Context API 和状态管理:在涉及到全局状态管理时,可以考虑使用 React Context API 或者 Redux 来更好地管理共享状态。
3)CSS Modules:当处理组件样式时,可以使用 CSS Modules 来避免样式污染并使样式更加模块化。
4)自定义Hooks:随着 React 16.8 引入 Hooks,可以编写自定义 Hooks 封装组件逻辑,提高代码的可读性和复用性。例如:useFetch
、useForm
等。
5)代码分割和懒加载:使用 React 的React.lazy
和Suspense
实现代码分割,提高应用的加载性能。
6)测试:编写单元测试和集成测试,使用 Jest 和 React Testing Library 确保组件行为符合预期。
7)TypeScript:考虑引入 TypeScript 为项目提供静态类型检查,进一步提高代码的健壮性和可维护性。
9. 什么是 React 受控组件和非受控组件?它们有什么区别?
在 React 中,受控组件和非受控组件主要用于处理表单数据。这两者的关键区别在于数据的管理方式。
1)受控组件(Controlled Components):受控组件是由 React 完全控制其值的组件,组件的状态和行为完全由 React 管理。这个意味着表单的值是通过组件的状态来管理,通常通过 state
对象和 setState
函数来实现。例如,当用户在输入框中输入数据时,onChange
事件会触发,并根据该事件来更新组件的状态,表单输入的值也会随之更新。
2)非受控组件(Uncontrolled Components):非受控组件则是由 DOM 自己管理其值的组件,React 不直接管理组件的值。我们主要使用 ref
属性来获取和操作 DOM 节点。在这种情况下,表单输入的值是由 DOM 维护的,React 只是读取它。表单的默认值通过 defaultValue
或 defaultChecked
设置。
扩展知识
不过,说到这里我觉得有必要再进一步解释一下这两种组件在实际开发中的应用场景和优缺点。
1)受控组件优缺点:
- 优点:
- 更简单管理表单数据:因为状态都在组件中管理,可以方便地进行验证和操作。
- 更强的可预测性:所有状态更新都通过
setState
方法,可以跟踪状态的变化。 - 更易于单元测试:因为状态变化是可控的。
- 缺点:
- 代码量较多:对于复杂的表单可能会产生大量的
setState
和处理函数。 - 性能开销较大:每次输入变动都要触发重新渲染。
- 代码量较多:对于复杂的表单可能会产生大量的
2)非受控组件优缺点:
- 优点:
- 更少的代码量:不需要设置状态和处理函数,反而更简洁。
- 较少的性能开销:不需要频繁地调用
setState
和重新渲染。
- 缺点:
- 难以管理数据:因为数据由 DOM 控制,处理复杂的表单验证和同步会比较麻烦。
- 不易于测试:因为状态不在 React 内部管理,难以进行状态的控制和预测。
3)实际应用场景:
- 当表单比较简单,且对性能要求比较高时,可以考虑非受控组件。
- 当需要对表单输入进行大量验证、格式化和关联操作时,建议使用受控组件。
- 如果需要在表单中嵌入复杂的逻辑(如多步骤表单),受控组件更为适合。
4)参考代码:
// 受控组件示例
class ControlledForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
// 非受控组件示例
class UncontrolledForm extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.inputRef.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.inputRef} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
10. React 中如何为非受控组件设置默认值?
在 React 中,为非受控组件设置默认值主要通过 defaultValue
或 defaultChecked
属性来实现。非受控组件是指那些不受 React 状态完全控制的表单元素,通常使用本地的 DOM 状态进行管理。
例如,对于一个输入框,可以通过 defaultValue
属性来设置默认值:
<input type="text" defaultValue="默认值" />
而对于一个复选框,可以使用 defaultChecked
属性:
<input type="checkbox" defaultChecked={true} />
扩展知识
进一步来聊一下非受控组件和受控组件的区别,以及为什么会用到非受控组件吧。
1)受控组件(Controlled Components): 受控组件指的是组件的值完全由 React 的状态管理,值的变更通过事件处理器来更新组件的 state,从而重新渲染组件。例如,一个受控的输入框:
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}
/>
);
}
}
2)非受控组件(Uncontrolled Components): 非受控组件则是使用 DOM 原生方式管理自己的状态,不受 React 状态完全控制。此时可以借助 defaultValue
和 defaultChecked
等属性来设置初始化值,同时通过 ref
来获取和操作 DOM 元素。例如:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
alert(this.inputRef.current.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
defaultValue="非受控默认值"
ref={this.inputRef}
/>
<button type="submit">提交</button>
</form>
);
}
}
3)为什么使用非受控组件: 受控组件在处理表单和用户输入时很灵活和强大,但在某些情况下,非受控组件会更简单方便:
- 简单的场景:当需要一个简单的、无需频繁操作表单元素的场景,非受控组件的代码更简洁。
- 库集成:当需要集成第三方库且该库与 React 状态管理耦合度较低时使用非受控组件更方便。
- 性能考虑:如果频繁更新 React 状态管理较为复杂且性能消耗较大,可以使用非受控组件减少不必要的渲染。
4)React Hooks 与非受控组件: 在函数组件中,如果仍然想使用非受控组件,你可以使用 useRef
Hook 来获取 DOM 元素,以下是一个示例:
import React, { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
alert(inputRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
defaultValue="非受控默认值"
ref={inputRef}
/>
<button type="submit">提交</button>
</form>
);
}
标签:受控,面试题,前端,React,PropTypes,props,组件,高频,JSX
From: https://www.cnblogs.com/Coisini-K/p/18371809