300+ 个 React 面试问题
今天我从这个很棒的repo中准备了一份很长的 React 面试问题清单。
欲了解更多信息,我开发了一个项目,其中包含 500 多个面向前端开发人员的问题
如果您喜欢这篇文章,请添加到书签以供将来使用并关注我:
目录
核心反应
什么是 React?
React 是一个开源前端 JavaScript 库,用于构建用户界面,尤其是单页应用程序。它用于处理 Web 和移动应用程序的视图层。React 由Facebook 软件工程师Jordan Walke创建。React 于 2011 年首次部署在 Facebook 的 News Feed 上,并于 2012 年部署在 Instagram 上。
React 的主要特性是什么?
React 的主要特性包括:
- 考虑到 RealDOM 操作成本高昂,它使用VirtualDOM而不是 RealDOM。
- 支持服务器端渲染。
- 遵循单向数据流或数据绑定。
- 使用可重复使用/可组合的UI 组件来开发视图。
什么是 JSX?
JSX是 ECMAScript( JavaScript XML的缩写)的一种类似 XML 的语法扩展。本质上,它只是为函数提供了语法糖React.createElement()
,让我们既能拥有 JavaScript 的表达能力,又能拥有类似 HTML 的模板语法。
在下面的示例中,<h1>
标签内的文本作为 JavaScript 函数返回给渲染函数。
class App extends React.Component {
render() {
return (
<div>
<h1>{'Welcome to React world!'}</h1>
</div>
);
}
}
元素和组件有什么区别?
元素是一个普通的对象,它描述了你希望在屏幕上显示的内容(以 DOM 节点或其他组件的形式)。元素可以在其 props 中包含其他元素。创建 React 元素的成本很低。元素一旦创建,就永远不会改变。
React Element 的对象表示如下:
const element = React.createElement('div', { id: 'login-btn' }, 'Login');
上述React.createElement()
函数返回一个对象:
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
最后使用以下命令渲染到 DOM ReactDOM.render()
:
<div id="login-btn">Login</div>
组件可以用多种不同的方式声明。它可以是一个带有方法的类。或者,在简单的情况下,它可以定义为一个函数。render()
无论哪种情况,它都接受 props 作为输入,并返回一个 JSX 树作为输出:
const Button = ({ onLogin }) => (
<div id={'login-btn'} onClick={onLogin}>
Login
</div>
);
然后 JSX 被转换为React.createElement()
函数树:
const Button = ({ onLogin }) =>
React.createElement('div', { id: 'login-btn', onClick: onLogin }, 'Login');
如何在 React 中创建组件?
有两种可能的方法可以创建组件。
- 函数组件:这是创建组件最简单的方法。这些是纯 JavaScript 函数,接受 props 对象作为第一个参数,并返回 React 元素:
function Greeting({ message }) {
return <h1>{`Hello, ${message}`}</h1>;
}
- 类组件:你也可以使用 ES6 类来定义组件。上面的函数组件可以写成:
class Greeting extends React.Component {
render() {
return <h1>{`Hello, ${this.props.message}`}</h1>;
}
}
何时使用类组件而不是函数组件?
如果组件需要状态或生命周期方法,则使用类组件,否则使用函数组件。但是,从 React 16.8 开始,随着 Hooks 的加入,你可以在函数组件中使用状态、生命周期方法和其他仅在类组件中可用的功能。
什么是纯组件?
React.PureComponent
与 完全相同,React.Component
只是它替shouldComponentUpdate()
你处理了该方法。当 props 或 state 发生变化时,PureComponent会对 props 和 state 进行浅比较。而ComponentshouldComponentUpdate
则不会将当前的 props 和 state 与下一个 props 和 state 进行比较。因此,每次调用时,组件都会默认重新渲染。
React 中的状态是什么?
组件的状态是一个对象,它保存着一些可能在组件生命周期内发生变化的信息。我们应该始终尝试使状态尽可能简单,并尽量减少有状态组件的数量。
让我们创建一个具有消息状态的用户组件,
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
message: 'Welcome to React world',
};
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
</div>
);
}
}
状态类似于道具,但它是私有的并且完全由组件控制。即,除了拥有和设置它的组件之外,任何组件都无法访问它。
React 中的 props 是什么?
Props是组件的输入。它们是单个值或包含一组值的对象,这些值在创建时使用类似于 HTML 标签属性的命名约定传递给组件。它们是从父组件向下传递到子组件的数据。
React 中 props 的主要目的是提供以下组件功能:
- 将自定义数据传递给您的组件。
- 触发状态改变。
this.props.reactProp
通过组件内部的render()
方法使用。
例如,让我们创建一个具有reactProp
以下属性的元素:
<Element reactProp={'1'} />
这个reactProp
(或者任何你想出来的)名称将成为附加到 React 原生 props 对象的属性,该属性最初已经存在于使用 React 库创建的所有组件上。
props.reactProp
状态和道具有什么区别?
props和state都是普通的 JavaScript 对象。虽然它们都包含影响渲染输出的信息,但它们在组件中的功能有所不同。props 传递给组件的方式类似于函数参数,而 state 则在组件内部进行管理,类似于函数内声明的变量。
为什么我们不应该直接更新状态?
如果您尝试直接更新状态,那么它将不会重新渲染组件。
//Wrong
this.state.message = 'Hello world';
请使用setState()
方法。它会安排组件状态对象的更新。当状态发生变化时,组件会通过重新渲染来响应。
//Correct
this.setState({ message: 'Hello World' });
注意:您可以在构造函数中或使用最新的 javascript 的类字段声明语法直接分配给状态对象。
回调函数作为的参数的目的是什么setState()
?
回调函数在 setState 完成且组件渲染完成后调用。由于setState()
是异步操作,因此回调函数可用于任何后续操作。
注意:建议使用生命周期方法而不是此回调函数。
setState({ name: 'John' }, () => console.log('The name has updated and component re-rendered'));
HTML 和 React 事件处理有什么区别?
以下是 HTML 和 React 事件处理之间的一些主要区别,
- 在 HTML 中,事件名称应小写:
<button onclick="activateLasers()"></button>
而在 React 中它遵循camelCase约定:
<button onClick={activateLasers}>
- 在 HTML 中,您可以返回来
false
阻止默认行为:
<a href="#" onclick='console.log("The link was clicked."); return false;' />
而在 React 中你必须preventDefault()
明确调用:
function handleClick(event) {
event.preventDefault();
console.log('The link was clicked.');
}
- 在 HTML 中,您需要通过附加来调用该函数,
()
而在 React 中,您不应该附加()
函数名称。(例如,参考第一点中的“activateLasers”函数)
如何在 JSX 回调中绑定方法或事件处理程序?
有 3 种可能的方法可以实现此目的:
- 构造函数中的绑定:在 JavaScript 类中,方法默认不绑定。同样适用于定义为类方法的 React 事件处理程序。通常我们在构造函数中绑定它们。
class Component extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// ...
}
}
- 公共类字段语法:如果您不喜欢使用绑定方法,那么可以使用公共类字段语法来正确绑定回调。
handleClick = () => {
console.log('this is:', this);
};
<button onClick={this.handleClick}>{'Click me'}</button>
- 回调中的箭头函数:您可以在回调中直接使用箭头函数。
<button onClick={(event) => this.handleClick(event)}>{'Click me'}</button>
注意:如果将回调作为 prop 传递给子组件,这些组件可能会进行额外的重新渲染。在这种情况下,考虑到性能.bind()
,建议使用public class fields 语法。
如何将参数传递给事件处理程序或回调?
您可以使用箭头函数来包装事件处理程序并传递参数:
<button onClick={() => this.handleClick(id)} />
这相当于调用.bind
:
<button onClick={this.handleClick.bind(this, id)} />
除了这两种方法之外,你还可以将参数传递给定义为箭头函数的函数
<button onClick={this.handleClick(id)} />;
handleClick = (id) => () => {
console.log('Hello, your ticket number is', id);
};
React 中的合成事件是什么?
SyntheticEvent
是浏览器原生事件的跨浏览器包装器。它的 API 与浏览器原生事件相同,包括 和stopPropagation()
,preventDefault()
但事件在所有浏览器中的工作方式相同。
什么是内联条件表达式?
您可以使用JS 提供的if 语句或三元表达式来有条件地渲染表达式。除了这些方法之外,您还可以将任何表达式嵌入到 JSX 中,方法是将它们括在花括号中,然后跟上 JS 逻辑运算符&&
。
<h1>Hello!</h1>;
{
messages.length > 0 && !isLogin ? (
<h2>You have {messages.length} unread messages.</h2>
) : (
<h2>You don't have unread messages.</h2>
);
}
什么是“key”属性以及在元素数组中使用它有什么好处?
Akey
是一个特殊的字符串属性,在创建元素数组时应该包含它。Key属性帮助 React 识别哪些元素发生了更改、被添加或被删除。
我们通常使用数据中的 ID 作为密钥:
const todoItems = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
当你没有渲染项目的稳定 ID 时,你可以使用项目索引作为键作为最后的手段:
const todoItems = todos.map((todo, index) => <li key={index}>{todo.text}</li>);
笔记:
- 如果项的顺序可能会发生变化,则不建议对键使用索引。这可能会对性能产生负面影响,并可能导致组件状态出现问题。
- 如果您将列表项提取为单独的组件,则在列表组件上应用键而不是
li
标签。 key
如果列表项中不存在该道具,控制台中将出现一条警告消息。
refs 有什么用?
ref用于返回元素的引用。大多数情况下应该避免使用它们,但是,当你需要直接访问 DOM 元素或组件实例时,它们会很有用。
如何创建 refs?
有两种方法
- 这是最近添加的方法。ref使用方法创建,并通过属性附加到 React 元素上。为了在整个组件中使用 ref,只需在构造函数中将 ref 赋值给实例属性即可。
React.createRef()
ref
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
- 无论 React 版本如何,你都可以使用 ref 回调方法。例如,搜索栏组件的 input 元素访问如下:
class SearchBar extends Component {
constructor(props) {
super(props);
this.txtSearch = null;
this.state = { term: '' };
this.setInputSearchRef = (e) => {
this.txtSearch = e;
};
}
onInputChange(event) {
this.setState({ term: this.txtSearch.value });
}
render() {
return (
<input
value={this.state.term}
onChange={this.onInputChange.bind(this)}
ref={this.setInputSearchRef}
/>
);
}
}
你也可以通过闭包在函数组件中使用ref。注意:你也可以使用内联 ref 回调,尽管这不是推荐的方法。
什么是前向引用?
Ref 转发是一项功能,它允许某些组件获取它们收到的ref,并将其进一步传递给子组件。
const ButtonElement = React.forwardRef((props, ref) => (
<button ref={ref} className="CustomButton">
{props.children}
</button>
));
// Create ref to the DOM button:
const ref = React.createRef();
<ButtonElement ref={ref}>{'Forward Ref'}</ButtonElement>;
回调 refs 和 findDOMNode() 中哪个是首选选项?
建议使用回调 refs而不是findDOMNode()
API。因为它findDOMNode()
会阻碍 React 未来某些功能的改进。
传统使用方法findDOMNode
:
class MyComponent extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView();
}
render() {
return <div />;
}
}
推荐的方法是:
class MyComponent extends Component {
constructor(props) {
super(props);
this.node = createRef();
}
componentDidMount() {
this.node.current.scrollIntoView();
}
render() {
return <div ref={this.node} />;
}
}
为什么 String Refs 是遗留的?
如果您之前使用过 React,您可能熟悉一个较旧的 API,其中ref
属性是字符串,例如ref={'textInput'}
,并且 DOM 节点以 的形式访问this.refs.textInput
。我们不建议这样做,因为字符串引用存在以下问题,并且被视为遗留问题。字符串引用已在 React v16 中被移除。
- 它们强制 React 跟踪当前正在执行的组件。这很成问题,因为它使 React 模块具有状态,因此当 React 模块在 bundle 中重复时会导致奇怪的错误。
- 它们不可组合——如果库在传递的子元素上放置了一个引用,用户就无法在其上放置另一个引用。回调引用完全可组合。
- 它们不像 Flow 那样支持静态分析。Flow 无法猜测框架是如何让字符串引用出现在 上的
this.refs
,也无法猜测它的类型(类型可能不同)。回调引用对静态分析更友好。 - 它不能像大多数人期望的那样使用“渲染回调”模式(例如)
class MyComponent extends Component {
renderRow = (index) => {
// This won't work. Ref will get attached to DataTable rather than MyComponent:
return <input ref={'input-' + index} />;
// This would work though! Callback refs are awesome.
return <input ref={(input) => (this['input-' + index] = input)} />;
};
render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />;
}
}
什么是虚拟 DOM?
虚拟DOM (VDOM) 是真实 DOM的内存表示。UI 的表示保存在内存中,并与“真实” DOM 同步。这是在调用渲染函数和元素显示在屏幕上之间发生的一个步骤。整个过程称为协调 (reconciliation)。
虚拟 DOM 如何工作?
虚拟DOM 的工作原理分为三个简单的步骤。
- 每当任何底层数据发生变化时,整个 UI 都会以虚拟 DOM 表示重新渲染。
- 然后计算先前的 DOM 表示和新的 DOM 表示之间的差异。
- 一旦计算完成,真实的 DOM 将仅更新实际发生变化的内容。
Shadow DOM 和 Virtual DOM 有什么区别?
Shadow DOM是一种浏览器技术,主要用于在Web 组件中限定变量和 CSS 的作用域。Virtual DOM是由 JavaScript 库在浏览器 API 之上实现的概念。
什么是 React Fiber?
Fiber 是 React v16 中新的协调引擎,或者说是核心算法的重新实现。React Fiber 的目标是提升其在动画、布局、手势、暂停、中止或重用工作以及为不同类型的更新分配优先级等领域的适用性,以及新的并发原语。
React Fiber 的主要目标是什么?
React Fiber的目标是提升其在动画、布局和手势等领域的适用性。其核心功能是增量渲染:能够将渲染工作拆分成多个块,并将其分散到多个帧上。
什么是受控组件?
在后续用户输入时控制表单内输入元素的组件称为受控组件,即每个状态突变都会有一个相关的处理程序函数。
例如,要将所有名称都写成大写字母,我们使用下面的 handleChange,
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()})
}
什么是不受控制的组件?
非受控组件是指在内部存储自身状态的组件,需要时可以使用 ref 查询 DOM 来获取其当前值。这更像传统的 HTML。
在下面的 UserProfile 组件中,name
使用 ref 访问输入。
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
{'Name:'}
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
大多数情况下,建议使用受控组件来实现表单。
createElement 和 cloneElement 有什么区别?
JSX 元素将被转换为React.createElement()
函数,以创建用于 UI 对象表示的 React 元素。而 则cloneElement
用于克隆元素并向其传递新的 props。
React 中的 Lifting State Up 是什么?
当多个组件需要共享相同的变化数据时,建议将共享状态提升到它们最近的共同祖先组件。这意味着,如果两个子组件共享来自其父组件的相同数据,则将状态移动到父组件,而不是在两个子组件中都维护本地状态。
组件生命周期有哪些不同阶段?
组件生命周期有三个不同的生命周期阶段:
-
挂载:组件已准备好挂载到浏览器 DOM 中。此阶段涵盖
constructor()
、getDerivedStateFromProps()
、render()
和componentDidMount()
生命周期方法的初始化。 -
更新:在此阶段,组件通过两种方式更新:发送新的 props 以及从
setState()
或更新状态forceUpdate()
。此阶段涵盖getDerivedStateFromProps()
、shouldComponentUpdate()
、render()
和生命周期方法getSnapshotBeforeUpdate()
。componentDidUpdate()
-
卸载:在最后一个阶段,组件不再需要,并从浏览器 DOM 中卸载。此阶段包含
componentWillUnmount()
生命周期方法。
值得一提的是,React 内部在 DOM 操作中有一个阶段的概念。它们被划分如下:
-
渲染:组件将渲染,且不会产生任何副作用。这适用于纯组件,并且在此阶段,React 可以暂停、中止或重新启动渲染。
-
预提交在组件实际将更改应用于 DOM 之前,有一个时刻允许 React 通过 从 DOM 读取
getSnapshotBeforeUpdate()
。 -
Commit React 与 DOM 协同工作,并分别执行
componentDidMount()
挂载、componentDidUpdate()
更新和componentWillUnmount()
卸载的最终生命周期。
React 16.3+ 阶段(或交互式版本)
React 16.3 之前
React 的生命周期方法有哪些?
React 16.3 之前
- componentWillMount:渲染之前执行,用于根组件中的应用程序级别配置。
- componentDidMount:首次渲染后执行,所有 AJAX 请求、DOM 或状态更新以及设置的事件监听器都应该在这里发生。
- componentWillReceiveProps:当特定 prop 更新以触发状态转换时执行。
- shouldComponentUpdate:确定组件是否更新。默认情况下返回
true
。如果您确定组件在状态或属性更新后无需渲染,则可以返回 false 值。这是一个很好的提升性能的地方,因为它允许您在组件收到新的属性时避免重新渲染。 - componentWillUpdate:当 props 和 state 发生变化时,在重新渲染组件之前执行,并
shouldComponentUpdate()
返回 true。 - componentDidUpdate:主要用于响应 prop 或 state 的变化来更新 DOM。
- componentWillUnmount:它将用于取消任何传出的网络请求,或删除与组件关联的所有事件监听器。
React 16.3+
- getDerivedStateFromProps:在调用之前调用,并且每次
render()
渲染时都会调用。这适用于需要派生状态的罕见情况。如果你需要派生状态,值得一读。 - componentDidMount:首次渲染后执行,所有 AJAX 请求、DOM 或状态更新以及设置的事件监听器都应该在这里发生。
- shouldComponentUpdate:确定组件是否更新。默认情况下返回
true
。如果您确定组件在状态或属性更新后无需渲染,则可以返回 false 值。这是一个很好的提升性能的地方,因为它允许您在组件收到新的属性时避免重新渲染。 - getSnapshotBeforeUpdate:在渲染输出提交到 DOM 之前执行。此方法返回的任何值都将传入
componentDidUpdate()
。这对于从 DOM 中捕获信息(例如滚动位置)非常有用。 - componentDidUpdate:主要用于响应 prop 或 state 的变化来更新 DOM。如果
shouldComponentUpdate()
返回 ,则不会触发false
。 - componentWillUnmount它将用于取消任何传出的网络请求,或删除与组件关联的所有事件监听器。
什么是高阶组件?
高阶组件(HOC )是一个函数,它接受一个组件作为参数并返回一个新组件。本质上,它是一种源自 React 组合特性的模式。
我们称它们为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC 可用于多种用例:
- 代码重用、逻辑和引导抽象。
- 渲染劫持。
- 状态抽象和操作。
- 道具操控。
如何为 HOC 组件创建 props 代理?
您可以使用props 代理模式添加/编辑传递给组件的 props,如下所示:
function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: 'New Header',
footer: false,
showFeatureX: false,
showFeatureY: true,
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
什么是上下文?
Context提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。
例如,应用程序中的许多组件都需要访问经过身份验证的用户、语言环境偏好、UI 主题。
const { Provider, Consumer } = React.createContext(defaultValue);
children 道具是什么?
Children是一个 prop ( this.props.children
),它允许你将组件作为数据传递给其他组件,就像你使用的任何其他 prop 一样。放置在组件开始和结束标签之间的组件树将作为children
prop 传递给该组件。
React API 中有许多方法可以处理此 prop。其中包括React.Children.map
、React.Children.forEach
、React.Children.count
、React.Children.only
、React.Children.toArray
。
children prop 的简单用法如下,
const MyDiv = React.createClass({
render: function () {
return <div>{this.props.children}</div>;
},
});
ReactDOM.render(
<MyDiv>
<span>{'Hello'}</span>
<span>{'World'}</span>
</MyDiv>,
node,
);
如何在 React 中写评论?
React/JSX 中的注释类似于 JavaScript 多行注释,但用花括号括起来。
单行注释:
<div>
{/* Single-line comments(In vanilla JavaScript, the single-line comments are represented by double slash(//)) */}
{`Welcome ${user}, let's play React`}
</div>
多行注释:
<div>
{/* Multi-line comments for more than
one line */}
{`Welcome ${user}, let's play React`}
</div>
使用带有 props 参数的超级构造函数的目的是什么?
子类构造函数在方法调用之前不能使用this
引用super()
。ES6 子类也是如此。将 props 参数传递给 call 的主要原因是为了在子类构造函数中super()
访问。this.props
传递道具:
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // prints { name: 'John', age: 42 }
}
}
不传递道具:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log(this.props); // prints undefined
// but props parameter is still available
console.log(props); // prints { name: 'John', age: 42 }
}
render() {
// no difference outside constructor
console.log(this.props); // prints { name: 'John', age: 42 }
}
}
上面的代码片段表明,this.props
仅在构造函数内部有所不同。在构造函数外部,情况应该相同。
什么是和解?
当组件的 props 或 state 发生变化时,React 会通过比较新返回的元素与之前渲染的元素来决定是否需要实际更新 DOM。如果它们不相等,React 将更新 DOM。这个过程称为协调 (reconciliation )。
如何使用动态键名设置状态?
如果您使用 ES6 或 Babel 转换器来转换您的 JSX 代码,那么您可以使用计算属性名称来实现这一点。
handleInputChange(event) {
this.setState({ [event.target.id]: event.target.value })
}
每次组件渲染时调用函数的常见错误是什么?
您需要确保在将函数作为参数传递时不会调用该函数。
render() {
// Wrong: handleClick is called instead of passed as a reference!
return <button onClick={this.handleClick()}>{'Click Me'}</button>
}
相反,传递函数本身而不使用括号:
render() {
// Correct: handleClick is passed as a reference!
return <button onClick={this.handleClick}>{'Click Me'}</button>
}
惰性函数是否支持命名导出?
不,目前React.lazy
函数仅支持默认导出。如果您想导入名为导出的模块,可以创建一个中间模块,将其重新导出为默认模块。这还能确保摇树优化持续有效,并且不会拉取未使用的组件。我们以一个导出多个名为组件的组件文件为例,
// MoreComponents.js
export const SomeComponent = /* ... */;
export const UnusedComponent = /* ... */;
并MoreComponents.js
在中间文件中重新导出组件IntermediateComponent.js
// IntermediateComponent.js
export { SomeComponent as default } from './MoreComponents.js';
现在您可以使用惰性函数导入模块,如下所示,
import React, { lazy } from 'react';
const SomeComponent = lazy(() => import('./IntermediateComponent.js'));
为什么 React 使用className
overclass
属性?
class
className
是 JavaScript 中的关键字,而 JSX 是 JavaScript 的扩展。这就是 React 使用而不是 的主要原因class
。传递一个字符串作为className
prop。
render() {
return <span className={'menu navigation-menu'}>{'Menu'}</span>
}
什么是碎片?
这是 React 中常见的模式,用于一个组件返回多个元素。Fragment允许你对子元素列表进行分组,而无需在 DOM 中添加额外的节点。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}
还有一种更短的语法,但许多工具不支持它:
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}
为什么 Fragment 比容器 div 更好?
以下是原因列表,
- 由于不创建额外的 DOM 节点,Fragment 的速度更快,内存占用更少。这只有在非常大且深度极深的树上才会真正发挥作用。
- 一些 CSS 机制(例如Flexbox和CSS Grid)具有特殊的父子关系,在中间添加 div 会使保持所需的布局变得困难。
- DOM 检查器不再那么混乱。
React 中的门户是什么?
Portal是一种推荐的方法,用于将子项渲染到父组件 DOM 层次结构之外的 DOM 节点中。
ReactDOM.createPortal(child, container);
第一个参数是任何可渲染的 React 子元素,例如元素、字符串或片段。第二个参数是 DOM 元素。
什么是无状态组件?
如果行为独立于其状态,那么它可以是无状态组件。您可以使用函数或类来创建无状态组件。但除非您需要在组件中使用生命周期钩子,否则您应该选择函数组件。如果您决定在这里使用函数组件,会有很多好处:它们易于编写、理解和测试,速度更快,而且您可以this
完全避免使用关键字。
什么是有状态组件?
如果组件的行为依赖于组件的状态,则可以将其称为有状态组件。这些有状态组件始终是类组件,并且具有在 中初始化的状态constructor
。
class App extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
// ...
}
}
React 16.8 更新:
Hooks 让您无需编写类即可使用状态和其他 React 功能。
等效函数组件
import React, {useState} from 'react';
const App = (props) => {
const [count, setCount] = useState(0);
return (
// JSX
)
}
如何在 React 中对 props 应用验证?
当应用程序在开发模式下运行时,React 会自动检查我们在组件上设置的所有 props,以确保它们具有正确的类型。如果类型不正确,React 会在控制台中生成警告消息。由于性能影响,此功能在生产模式下被禁用。必需的 props 用 定义isRequired
。
预定义的 prop 类型集:
PropTypes.number
PropTypes.string
PropTypes.array
PropTypes.object
PropTypes.func
PropTypes.node
PropTypes.element
PropTypes.bool
PropTypes.symbol
PropTypes.any
我们可以propTypes
对User
组件进行如下定义:
import React from 'react';
import PropTypes from 'prop-types';
class User extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};
render() {
return (
<>
<h1>{`Welcome, ${this.props.name}`}</h1>
<h2>{`Age, ${this.props.age}`}</h2>
</>
);
}
}
注意:在 React v15.5 中,PropTypesReact.PropTypes
已从库中移出prop-types
。
React 有哪些优点?
以下是 React 的主要优点:
- 使用虚拟 DOM提高应用程序的性能。
- JSX 使代码易于阅读和编写。
- 它在客户端和服务器端(SSR)上呈现。
- 由于它只是一个视图库,因此易于与框架(Angular、Backbone)集成。
- 使用 Jest 等工具轻松编写单元和集成测试。
React 有哪些局限性?
除了优点之外,React 也有一些限制,
- React 只是一个视图库,而不是一个完整的框架。
- 对于刚接触 Web 开发的初学者来说,有一个学习曲线。
- 将 React 集成到传统的 MVC 框架中需要一些额外的配置。
- 代码复杂性随着内联模板和 JSX 而增加。
- 太多较小的组件导致过度工程或样板化。
React v16 中的错误边界是什么?
错误边界是捕获其子组件树中任何位置的 JavaScript 错误、记录这些错误并显示后备 UI 而不是崩溃的组件树的组件。
componentDidCatch(error, info)
如果类组件定义了一个名为或 的新生命周期方法,它就会成为错误边界static getDerivedStateFromError()
:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>{'Something went wrong.'}</h1>;
}
return this.props.children;
}
}
之后将其用作常规组件:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
React v15 中如何处理错误边界?
React v15使用方法为错误边界提供了非常基本的支持。在 React v16 中,unstable_handleError
它已被重命名为。componentDidCatch
静态类型检查的推荐方法有哪些?
通常,我们使用PropTypes 库(自 React v15.5 起React.PropTypes
已移至包中)在 React 应用程序中进行类型检查。对于大型代码库,建议使用静态类型检查器(例如 Flow 或 TypeScript),它们在编译时执行类型检查并提供自动完成功能。prop-types
包有什么用react-dom
?
该react-dom
软件包提供了特定于 DOM 的方法,可在应用的顶层使用。大多数组件并非必须使用此模块。此软件包的一些方法如下:
render()
hydrate()
unmountComponentAtNode()
findDOMNode()
createPortal()
render 方法的目的是什么react-dom
?
此方法用于将 React 元素渲染到指定容器的 DOM 中,并返回该组件的引用。如果该 React 元素之前已渲染到容器中,则该方法将对其进行更新,并仅在必要时修改 DOM 以反映最新的更改。
ReactDOM.render(element, container[, callback])
如果提供了可选的回调,它将在组件渲染或更新后执行。
什么是 ReactDOMServer?
该ReactDOMServer
对象使你能够将组件渲染为静态标记(通常用于 Node 服务器上)。此对象主要用于服务器端渲染(SSR)。以下方法可在服务器和浏览器环境中使用:
renderToString()
renderToStaticMarkup()
例如,您通常运行基于 Node 的 Web 服务器(如 Express、Hapi 或 Koa),然后调用它renderToString
来将根组件渲染为字符串,然后将其作为响应发送。
// using Express
import { renderToString } from 'react-dom/server';
import MyPage from './MyPage';
app.get('/', (req, res) => {
res.write('<!DOCTYPE html><html><head><title>My Page</title></head><body>');
res.write('<div id="content">');
res.write(renderToString(<MyPage />));
res.write('</div></body></html>');
res.end();
});
如何在 React 中使用 innerHTML?
该dangerouslySetInnerHTML
属性是 React 中用于innerHTML
浏览器 DOM 的替代方案。与 一样innerHTML
,考虑到跨站点脚本 (XSS) 攻击,使用此属性存在风险。您只需将__html
对象作为键,将 HTML 文本作为值传递即可。
在此示例中,MyComponent 使用dangerouslySetInnerHTML
属性来设置 HTML 标记:
function createMarkup() {
return { __html: 'First · Second' };
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
如何在 React 中使用样式?
该style
属性接受带有驼峰式命名属性的 JavaScript 对象,而不是 CSS 字符串。这与 DOM 样式的 JavaScript 属性一致,效率更高,并能防止 XSS 安全漏洞。
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}
样式键采用驼峰命名法,以便与访问 JavaScript 中的 DOM 节点上的属性保持一致(例如node.style.backgroundImage
)。
React 中的事件有何不同?
在 React 元素中处理事件有一些语法差异:
- React 事件处理程序使用驼峰式命名,而不是小写。
- 使用 JSX,您可以传递一个函数作为事件处理程序,而不是字符串。
如果在构造函数中使用会发生什么setState()
?
当你使用 时setState()
,除了将状态赋值给对象之外,React 还会重新渲染组件及其所有子组件。你会收到类似这样的错误:只能更新已安装或正在安装的组件。因此,我们需要this.state
在构造函数中使用 来初始化变量。
索引作为键有何影响?
键应该是稳定、可预测和唯一的,以便 React 可以跟踪元素。
在下面的代码片段中,每个元素的键将基于排序,而不是与所表示的数据绑定。这限制了 React 的优化能力。
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}
如果您使用元素数据作为唯一键,假设 todo.id 对于此列表是唯一的并且稳定,则 React 将能够重新排序元素而无需重新评估它们。
{
todos.map((todo) => <Todo {...todo} key={todo.id} />);
}
在方法setState()
中使用好吗?componentWillMount()
setState()
是的,在方法内部使用是安全的componentWillMount()
。但同时建议避免在componentWillMount()
生命周期方法中进行异步初始化。componentWillMount()
在挂载之前立即调用。它在 之前调用render()
,因此在此方法中设置状态不会触发重新渲染。避免在此方法中引入任何副作用或订阅。我们需要确保组件初始化的异步调用发生在 中,componentDidMount()
而不是 中componentWillMount()
。
componentDidMount() {
axios.get(`api/todos`)
.then((result) => {
this.setState({
messages: [...result.data]
})
})
}
如果在初始状态下使用 props 会发生什么?
如果组件的 props 在组件未刷新的情况下发生变化,新的 props 值将永远不会显示,因为构造函数永远不会更新组件的当前状态。props 中的 state 初始化仅在组件首次创建时运行。
以下组件不会显示更新后的输入值:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: [],
inputValue: this.props.inputValue,
};
}
render() {
return <div>{this.state.inputValue}</div>;
}
}
在 render 方法中使用 props 将更新值:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
record: [],
};
}
render() {
return <div>{this.props.inputValue}</div>;
}
}
如何有条件地渲染组件?
在某些情况下,您希望根据某些状态渲染不同的组件。JSX 不会渲染false
或undefined
,因此您可以使用条件短路,仅在特定条件为真时渲染组件的特定部分。
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address && <p>{address}</p>}
</div>
);
如果需要if-else
条件,则使用三元运算符。
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address ? <p>{address}</p> : <p>{'Address is not available'}</p>}
</div>
);
为什么在 DOM 元素上传播 props 时需要小心?
当我们扩展 props时,我们面临着添加未知 HTML 属性的风险,这是一种不好的做法。相反,我们可以使用带有...rest
操作符的 prop 解构,这样它只会添加必需的 props。
例如,
const ComponentA = () => <ComponentB isDisplay={true} className={'componentStyle'} />;
const ComponentB = ({ isDisplay, ...domProps }) => <div {...domProps}>{'ComponentB'}</div>;
如何在 React 中使用装饰器?
你可以装饰你的类组件,就像将组件传递给函数一样。装饰器是一种灵活且易读的修改组件功能的方式。
@setTitle('Profile')
class Profile extends React.Component {
//....
}
/*
title is a string that will be set as a document title
WrappedComponent is what our decorator will receive when
put directly above a component class as seen in the example above
*/
const setTitle = (title) => (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
document.title = title;
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
注意:装饰器是未纳入 ES7 的功能,但目前属于第 2 阶段提案。
如何记忆组件?
有可用于函数组件的记忆库。
例如,moize
库可以在另一个组件中记忆该组件。
import moize from 'moize';
import Component from './components/Component'; // this module exports a non-memoized component
const MemoizedFoo = moize.react(Component);
const Consumer = () => {
<div>
{'I will memoize the following entry:'}
<MemoizedFoo />
</div>;
};
更新:从 React v16.6.0 开始,我们新增了一个React.memo
。它提供了一个高阶组件,除非 props 发生变化,否则会记忆组件。要使用它,只需在使用前用 React.memo 包裹组件即可。
const MemoComponent = React.memo(function MemoComponent(props) {
/* render using props */
});
OR;
export default React.memo(MyFunctionComponent);
如何实现服务器端渲染(SSR)?
React 已经具备处理 Node 服务器上渲染的能力。它有一个特殊版本的 DOM 渲染器可用,其遵循与客户端相同的模式。
import ReactDOMServer from 'react-dom/server';
import App from './App';
ReactDOMServer.renderToString(<App />);
此方法将常规 HTML 输出为字符串,然后可以将其作为服务器响应的一部分放置在页面主体中。在客户端,React 会检测预渲染的内容,并从中断处无缝衔接。
如何在 React 中启用生产模式?
你应该使用 Webpack 的DefinePlugin
方法将其设置NODE_ENV
为production
,这样它就可以去除诸如 propType 验证和额外警告之类的内容。除此之外,如果你压缩代码,例如使用 Uglify 的死代码消除功能来去除仅用于开发的代码和注释,这将大大减少你的打包文件的大小。
什么是 CRA 及其好处?
CLI工具create-react-app
允许您快速创建和运行 React 应用程序,无需任何配置步骤。
让我们使用CRA创建 Todo App :
# Installation
$ npm install -g create-react-app
# Create new project
$ create-react-app todo-app
$ cd todo-app
# Build, test and run
$ npm run build
$ npm run test
$ npm start
它包含构建 React 应用程序所需的一切:
- React、JSX、ES6 和 Flow 语法支持。
- 超越 ES6 的语言附加功能,例如对象扩展运算符。
- 自动添加前缀 CSS,因此您不需要 -webkit- 或其他前缀。
- 一个快速交互式单元测试运行器,内置对覆盖率报告的支持。
- 实时开发服务器,对常见错误发出警告。
- 一个构建脚本,用于捆绑 JS、CSS 和图像以用于生产,并带有哈希和源映射。
安装过程中的生命周期方法顺序是什么?
当创建组件实例并将其插入 DOM 时,生命周期方法按以下顺序调用。
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
React v16 中将弃用哪些生命周期方法?
以下生命周期方法将是不安全的编码实践,并且在异步渲染时会出现更多问题。
componentWillMount()
componentWillReceiveProps()
componentWillUpdate()
从 React v16.3 开始,这些方法以UNSAFE_
前缀作为别名,而无前缀的版本将在 React v17 中删除。
生命周期方法的目的是什么getDerivedStateFromProps()
?
新的静态getDerivedStateFromProps()
生命周期方法在组件实例化之后以及重新渲染之前调用。它可以返回一个对象来更新状态,或者null
指示新的 props 不需要任何状态更新。
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}
此生命周期方法涵盖了componentDidUpdate()
的所有用例componentWillReceiveProps()
。
生命周期方法的目的是什么getSnapshotBeforeUpdate()
?
新的getSnapshotBeforeUpdate()
生命周期方法在 DOM 更新之前被调用。该方法的返回值将作为第三个参数传递给componentDidUpdate()
。
class MyComponent extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}
此生命周期方法涵盖了componentDidUpdate()
的所有用例componentWillUpdate()
。
Hooks 是否会取代渲染道具和高阶组件?
渲染道具和高阶组件都仅渲染一个子项,但在大多数情况下,Hooks 是一种通过减少树中的嵌套来实现此目的的更简单方法。
推荐的组件命名方式是什么?
建议通过引用来命名组件,而不是使用displayName
。
用于displayName
命名组件:
export default React.createClass({
displayName: 'TodoApp',
// ...
});
推荐的方法:
export default class TodoApp extends React.Component {
// ...
}
组件类中方法的推荐顺序是什么?
从安装到渲染阶段的方法推荐顺序:
static
方法constructor()
getChildContext()
componentWillMount()
componentDidMount()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
componentDidUpdate()
componentWillUnmount()
- 点击处理程序或事件处理程序,例如
onClickSubmit()
或onChangeDescription()
- 用于渲染的 getter 方法,例如
getSelectReason()
或getFooterContent()
- 可选的渲染方法,例如
renderNavigation()
或renderProfilePicture()
render()
什么是开关组件?
切换组件是渲染多个组件之一的组件。我们需要使用对象将 prop 值映射到组件。
例如,一个根据page
prop 显示不同页面的切换组件:
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ServicesPage from './ServicesPage';
import ContactPage from './ContactPage';
const PAGES = {
home: HomePage,
about: AboutPage,
services: ServicesPage,
contact: ContactPage,
};
const Page = (props) => {
const Handler = PAGES[props.page] || ContactPage;
return <Handler {...props} />;
};
// The keys of the PAGES object can be used in the prop types to catch dev-time errors.
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired,
};
为什么我们需要向 setState() 传递一个函数?
背后的原因是setState()
是一个异步操作。出于性能原因,React 会批量更改状态,因此setState()
调用 后状态可能不会立即更改。这意味着您在调用时不应依赖当前状态,setState()
因为您无法确定该状态将是什么。解决方案是将一个函数传递给setState()
,并将先前的状态作为参数。这样做可以避免由于 的异步特性而导致用户在访问时获取旧状态值的问题setState()
。
假设初始计数值为零,连续三次递增操作后,计数值只会增加一。
// assuming this.state.count === 0
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// this.state.count === 1, not 3
如果我们将一个函数传递给setState()
,计数就会正确增加。
this.setState((prevState, props) => ({
count: prevState.count + props.increment,
}));
// this.state.count === 3 as expected
(或者)
为什么函数比对象更受欢迎setState()
?
为了提高性能, React 可能会将多个setState()
调用批量合并到单个更新中。由于this.props
和this.state
可能会异步更新,因此不应依赖它们的值来计算下一个状态。
此反例将无法按预期更新:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
首选方法是setState()
使用函数而不是对象来调用。该函数将接收先前的状态作为第一个参数,并将应用更新时的 props 作为第二个参数。
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment,
}));
React 中的严格模式是什么?
React.StrictMode
是一个用于突出显示应用程序中潜在问题的有用组件。与 类似<Fragment>
,<StrictMode>
它不会渲染任何额外的 DOM 元素。它会为其后代激活额外的检查和警告。这些检查仅适用于开发模式。
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
在上面的例子中,严格模式检查仅适用于<ComponentOne>
和<ComponentTwo>
组件。
React Mixins 是什么?
Mixins是一种将组件完全分离,使其具有通用功能的方法。不应使用Mixins ,可以使用高阶组件或装饰器来替代。
最常用的 mixin 之一是PureRenderMixin
。你可能会在某些组件中使用它,以防止当 props 和 state 与之前的 props 和 state 浅相等时进行不必要的重新渲染:
const PureRenderMixin = require('react-addons-pure-render-mixin');
const Button = React.createClass({
mixins: [PureRenderMixin],
// ...
});
为什么是isMounted()
反模式以及什么是正确的解决方案?
主要用例isMounted()
是避免setState()
在组件卸载后调用,因为它会发出警告。
if (this.isMounted()) {
this.setState({...})
}
isMounted()
调用前检查setState()
确实可以消除警告,但也违背了警告的目的。使用isMounted()
是一种代码异味,因为你检查的唯一原因是你认为在组件卸载后可能还持有引用。
最佳解决方案是找到setState()
组件卸载后可能被调用的位置,并修复它们。这种情况最常见的原因是回调,即组件在等待数据时,在数据到达之前就被卸载了。理想情况下,任何回调都应该在componentWillUnmount()
卸载之前取消。
React 支持哪些指针事件?
指针事件提供了一种统一的方式来处理所有输入事件。过去,我们用鼠标和相应的事件监听器来处理它们,但现在我们有很多设备不再需要鼠标,例如带有触控板的手机或触控笔。需要注意的是,这些事件只能在支持指针事件规范的浏览器中工作。
React DOM中现在提供以下事件类型:
onPointerDown
onPointerMove
onPointerUp
onPointerCancel
onGotPointerCapture
onLostPointerCapture
onPointerEnter
onPointerLeave
onPointerOver
onPointerOut
为什么组件名称要以大写字母开头?
如果你使用 JSX 渲染组件,组件名称必须以大写字母开头,否则 React 会抛出无法识别标签的错误。之所以采用这种约定,是因为只有 HTML 元素和 SVG 标签可以以小写字母开头。
class SomeComponent extends Component {
// Code goes here
}
你可以定义一个组件类,其名称以小写字母开头,但在导入时必须使用大写字母。以下情况小写字母是可以的:
class myComponent extends Component {
render() {
return <div />;
}
}
export default myComponent;
而当导入另一个文件时,它应该以大写字母开头:
import MyComponent from './MyComponent';
React 组件命名有哪些例外?
组件名称应以大写字母开头,但此约定有一些例外。带有点(属性访问器)的小写标签名称仍被视为有效的组件名称。
例如,下面的标签可以编译为有效的组件,
render(){
return (
<obj.component /> // `React.createElement(obj.component)`
)
}
React v16 是否支持自定义 DOM 属性?
是的。过去,React 会忽略未知的 DOM 属性。如果你编写的 JSX 代码中包含 React 无法识别的属性,React 就会直接跳过它。
例如,让我们看一下下面的属性:
<div mycustomattribute={'something'} />
将使用 React v15 将空的 div 渲染到 DOM:
<div />
在 React v16 中,任何未知属性最终都会出现在 DOM 中:
<div mycustomattribute="something" />
这对于提供特定于浏览器的非标准属性、尝试新的 DOM API 以及与有主见的第三方库集成很有用。
构造函数和 getInitialState 有什么区别?
当使用 ES6 类时,应在构造函数中初始化状态,getInitialState()
当使用时,应在方法中初始化状态React.createClass()
。
使用 ES6 类:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
/* initial state */
};
}
}
使用React.createClass()
:
const MyComponent = React.createClass({
getInitialState() {
return {
/* initial state */
};
},
});
注意: React.createClass()
在 React v16 中已弃用并删除。请改用普通的 JavaScript 类。
您可以在不调用 setState 的情况下强制组件重新渲染吗?
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果你的render()
方法依赖于其他数据,你可以通过调用 来告诉 React 组件需要重新渲染forceUpdate()
。
component.forceUpdate(callback);
建议避免使用forceUpdate()
,而仅从this.props
和this.state
中读取render()
。
React 中使用 ES6 类的super()
和有什么区别?super(props)
当您想要访问this.props
时constructor()
,您应该将 props 传递给super()
方法。
使用super(props)
:
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // { name: 'John', ... }
}
}
使用super()
:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log(this.props); // undefined
}
}
外面constructor()
两者将显示相同的值this.props
。
如何在 JSX 内部循环?
您可以简单地使用Array.prototype.map
ES6箭头函数语法。
例如,items
对象数组被映射到组件数组中:
<tbody>
{items.map((item) => (
<SomeComponent key={item.id} name={item.name} />
))}
</tbody>
但不能使用for
循环进行迭代:
<tbody>
for (let i = 0; i < items.length; i++) {
<SomeComponent key={items[i].id} name={items[i].name} />
}
</tbody>
这是因为 JSX 标签会被转译成函数调用,而表达式内部无法使用语句。由于do
表达式(目前处于第一阶段提案)的改进,这种情况可能会有所改变。
如何访问属性引号中的 props?
React(或 JSX)不支持在属性值内进行变量插值。以下代码无法正常工作:
<img className="image" src="images/{this.props.image}" />
但是你可以将任何 JS 表达式放在花括号内作为整个属性值。因此,下面的表达式有效:
<img className="image" src={'images/' + this.props.image} />
使用模板字符串也可以:
<img className="image" src={`images/${this.props.image}`} />
React proptype 数组的形状是什么?
如果您想将对象数组传递给具有特定形状的组件,则将其用作React.PropTypes.shape()
参数React.PropTypes.arrayOf()
。
ReactComponent.propTypes = {
arrayWithShape: React.PropTypes.arrayOf(
React.PropTypes.shape({
color: React.PropTypes.string.isRequired,
fontSize: React.PropTypes.number.isRequired,
}),
).isRequired,
};
如何有条件地应用类属性?
您不应该在引号内使用花括号,因为它将被评估为字符串。
<div className="btn-panel {this.props.visible ? 'show' : 'hidden'}">
相反,您需要将花括号移到外面(不要忘记在类名之间包含空格):
<div className={'btn-panel ' + (this.props.visible ? 'show' : 'hidden')}>
模板字符串也可以起作用:
<div className={`btn-panel ${this.props.visible ? 'show' : 'hidden'}`}>
React 和 ReactDOM 有什么区别?
该react
包包含React.createElement()
、React.Component
、React.Children
以及其他与元素和组件类相关的帮助器。您可以将它们视为构建组件所需的同构或通用帮助器。该react-dom
包包含ReactDOM.render()
,并且react-dom/server
我们通过和提供服务器端渲染支持。ReactDOMServer.renderToString()
ReactDOMServer.renderToStaticMarkup()
为什么要将 ReactDOM 与 React 分离?
React 团队致力于将所有与 DOM 相关的功能提取到一个名为ReactDOM的独立库中。React v0.14 是首次将库拆分的版本。通过查看一些软件包react-native
,、、和,我们可以清楚地发现,React 的魅力和本质与浏览器或 DOM 无关。react-art
react-canvas
react-three
为了构建更多 React 可渲染的环境,React 团队计划将主 React 包拆分为两个:react
和react-dom
。这为编写可在 Web 版 React 和 React Native 之间共享的组件铺平了道路。
如何使用 React 标签元素?
如果您尝试<label>
使用标准for
属性呈现绑定到文本输入的元素,那么它会生成缺少该属性的 HTML 并向控制台打印警告。
<label for={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
由于for
是 JavaScript 中的保留关键字,htmlFor
因此请使用。
<label htmlFor={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
如何组合多个内联样式对象?
您可以在常规 React 中使用扩展运算符:
<button style={{ ...styles.panel.button, ...styles.panel.submitButton }}>{'Submit'}</button>
如果您使用的是 React Native,那么您可以使用数组符号:
<button style={[styles.panel.button, styles.panel.submitButton]}>{'Submit'}</button>
当浏览器调整大小时如何重新渲染视图?
您可以监听resize
中的事件componentDidMount()
,然后更新尺寸(width
和height
)。您应该删除 方法中的监听器componentWillUnmount()
。
class WindowDimensions extends React.Component {
constructor(props) {
super(props);
this.updateDimensions = this.updateDimensions.bind(this);
}
componentWillMount() {
this.updateDimensions();
}
componentDidMount() {
window.addEventListener('resize', this.updateDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateDimensions);
}
updateDimensions() {
this.setState({ width: window.innerWidth, height: window.innerHeight });
}
render() {
return (
<span>
{this.state.width} x {this.state.height}
</span>
);
}
}
setState()
和方法之间有什么区别replaceState()
?
使用 时,setState()
当前状态和先前状态会被合并。replaceState()
会丢弃当前状态,并将其替换为您提供的内容。通常setState()
使用 ,除非您出于某些原因确实需要删除所有先前的键。您也可以将状态设置为false
/ null
,setState()
而不是使用replaceState()
。
如何监听状态变化?
当状态发生变化时,生命周期方法componentDidUpdate
将被调用。你可以将提供的 state 和 props 值与当前 state 和 props 值进行比较,以确定是否有任何有意义的变化。
componentDidUpdate(object prevProps, object prevState)
注意: ReactJS 的早期版本也使用componentWillUpdate(object nextProps, object nextState)
状态更改。它在最新版本中已被弃用。
在 React 状态下删除数组元素的推荐方法是什么?
更好的方法是使用Array.prototype.filter()
方法。
例如,让我们创建一个removeItem()
更新状态的方法。
removeItem(index) {
this.setState({
data: this.state.data.filter((item, i) => i !== index)
})
}
是否可以在不渲染 HTML 的情况下使用 React?
最新版本(>=16.2)可以实现。以下是可能的选项:
render() {
return false
}
render() {
return null
}
render() {
return []
}
render() {
return <React.Fragment></React.Fragment>
}
render() {
return <></>
}
回程undefined
是行不通的。
如何使用 React 漂亮地打印 JSON?
我们可以使用标签,以便保留<pre>
格式:JSON.stringify()
const data = { name: 'John', age: 42 };
class User extends React.Component {
render() {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
}
React.render(<User />, document.getElementById('container'));
为什么你不能在 React 中更新 props?
React 的理念是 props 应该是不可变的和自上而下的。这意味着父组件可以向子组件发送任何 props 值,但子组件不能修改接收到的 props。
如何在页面加载时聚焦输入元素?
您可以通过创建元素的refinput
并在以下位置使用它来实现componentDidMount()
:
class App extends React.Component {
componentDidMount() {
this.nameInput.focus();
}
render() {
return (
<div>
<input defaultValue={"Won't focus"} />
<input ref={(input) => (this.nameInput = input)} defaultValue={'Will focus'} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
有哪些可能的方法可以更新状态中的对象?
- 调用
setState()
一个对象来合并状态:
-
用于
Object.assign()
创建对象的副本:const user = Object.assign({}, this.state.user, { age: 42 }); this.setState({ user });
-
使用扩展运算符:
const user = { ...this.state.user, age: 42 }; this.setState({ user });
setState()
使用函数调用:
this.setState((prevState) => ({
user: {
...prevState.user,
age: 42,
},
}));
我们如何在浏览器中找到运行时 React 的版本?
您可以使用React.version
来获取版本。
const REACT_VERSION = React.version;
ReactDOM.render(<div>{`React version: ${REACT_VERSION}`}</div>, document.getElementById('app'));
有哪些方法可以将 polyfill 纳入您的create-react-app
?
有一些方法可以在 create-react-app 中包含 polyfill,
- 手动导入自
core-js
:
创建一个名为(类似)的文件polyfills.js
并将其导入到根index.js
文件中。运行npm install core-js
或yarn add core-js
并导入您所需的特定功能。
import 'core-js/fn/array/find';
import 'core-js/fn/array/includes';
import 'core-js/fn/number/is-nan';
- 使用 Polyfill 服务:
通过将这一行添加到以下位置,使用 polyfill.io CDN 检索自定义的、特定于浏览器的 polyfill index.html
:
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes"></script>
在上面的脚本中,我们必须明确请求该Array.prototype.includes
功能,因为它不包含在默认功能集中。
如何在 create-react-app 中使用 https 而不是 http?
您只需要使用HTTPS=true
配置。您可以编辑package.json
脚本部分:
"scripts": {
"start": "set HTTPS=true && react-scripts start"
}
或者直接运行set HTTPS=true && npm start
如何避免在 create-react-app 中使用相对路径导入?
.env
在项目根目录中创建一个名为的文件并写入导入路径:
NODE_PATH=src/app
之后重启开发服务器。现在你应该能够导入任何内容,src/app
无需相对路径。
如何为 React Router 添加 Google Analytics?
在对象上添加一个监听器history
来记录每次页面浏览:
history.listen(function (location) {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview', location.pathname + location.search);
});
如何每秒更新一个组件?
您需要使用setInterval()
来触发更改,但也需要在组件卸载时清除计时器,以防止错误和内存泄漏。
componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
如何将供应商前缀应用于 React 中的内联样式?
React不会自动应用供应商前缀。你需要手动添加供应商前缀。
<div
style={{
transform: 'rotate(90deg)',
WebkitTransform: 'rotate(90deg)', // note the capital 'W' here
msTransform: 'rotate(90deg)', // 'ms' is the only lowercase vendor prefix
}}
/>
如何使用 React 和 ES6 导入和导出组件?
您应该使用默认值来导出组件
import React from 'react';
import User from 'user';
export default class MyProfile extends React.Component {
render() {
return <User type="customer">//...</User>;
}
}
使用导出说明符,MyProfile 将成为成员并导出到此模块,并且可以在其他组件中导入而无需提及名称。
为什么组件构造函数只被调用一次?
React 的协调算法假设,在没有任何相反信息的情况下,如果自定义组件在后续渲染中出现在相同的位置,则它与之前的组件相同,因此会重用之前的实例,而不是创建新的实例。
如何在 React 中定义常量?
您可以使用 ES7static
字段来定义常量。
class MyComponent extends React.Component {
static DEFAULT_PAGINATION = 10;
}
静态字段是类字段第 3 阶段提案的一部分。
如何在 React 中以编程方式触发点击事件?
您可以使用 ref propHTMLInputElement
通过回调获取对底层对象的引用,将该引用存储为类属性,然后使用该引用稍后使用该方法从事件处理程序触发点击HTMLElement.click
。
这可以分两个步骤完成:
- 在渲染方法中创建 ref:
<input ref={(input) => (this.inputElement = input)} />
- 在您的事件处理程序中应用点击事件:
this.inputElement.click();
可以在普通 React 中使用 async/await 吗?
如果你想在 React 中使用async
/ await
,你需要Babel和transform-async-to-generator插件。React Native 附带了 Babel 和一系列转换工具。
React 的常见文件夹结构有哪些?
React项目文件结构有两种常见的做法。
- 按特征或路线分组:
构建项目的一种常见方法是将 CSS、JS 和测试放在一起,按功能或路线分组。
common/
├─ Avatar.js
├─ Avatar.css
├─ APIUtils.js
└─ APIUtils.test.js
feed/
├─ index.js
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
└─ FeedAPI.js
profile/
├─ index.js
├─ Profile.js
├─ ProfileHeader.js
├─ ProfileHeader.css
└─ ProfileAPI.js
- 按文件类型分组:
构建项目的另一种流行方法是将相似的文件分组在一起。
api/
├─ APIUtils.js
├─ APIUtils.test.js
├─ ProfileAPI.js
└─ UserAPI.js
components/
├─ Avatar.js
├─ Avatar.css
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
├─ Profile.js
├─ ProfileHeader.js
└─ ProfileHeader.css
有哪些流行的动画软件包?
React Transition Group和React Motion是 React 生态系统中流行的动画包。
样式模块有什么好处?
建议避免在组件中硬编码样式值。任何可能跨不同 UI 组件使用的值都应提取到各自的模块中。
例如,这些样式可以提取到单独的组件中:
export const colors = {
white,
black,
blue,
};
export const space = [0, 8, 16, 32, 64];
然后在其他组件中单独导入:
import { space, colors } from './styles';
有哪些流行的 React 专用 linters?
ESLint 是一款流行的 JavaScript 语法检查工具。它有一些插件可以分析特定的代码风格。React 最常用的插件之一是名为 的 npm 包eslint-plugin-react
。默认情况下,它会检查一系列最佳实践,规则涵盖从迭代器中的键到完整的 prop 类型集合。
另一个流行的插件是eslint-plugin-jsx-a11y
,它可以帮助解决常见的可访问性问题。由于 JSX 的语法与常规 HTML 略有不同,因此常规插件无法检测到alt
文本和等问题。tabindex
如何进行 AJAX 调用以及应该在哪些组件生命周期方法中进行 AJAX 调用?
您可以使用 AJAX 库,例如 Axios、jQuery AJAX 和浏览器内置的fetch
。您应该在生命周期方法中获取数据componentDidMount()
。这样,setState()
当数据被检索到时,您就可以用它来更新组件。
例如,从 API 获取员工列表并设置本地状态:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
employees: [],
error: null,
};
}
componentDidMount() {
fetch('https://api.example.com/items')
.then((res) => res.json())
.then(
(result) => {
this.setState({
employees: result.employees,
});
},
(error) => {
this.setState({ error });
},
);
}
render() {
const { error, employees } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else {
return (
<ul>
{employees.map((employee) => (
<li key={employee.name}>
{employee.name}-{employee.experience}
</li>
))}
</ul>
);
}
}
}
什么是渲染道具?
Render Props是一种简单的技术,它使用一个值为函数的 prop 在组件之间共享代码。下面的组件使用了 render prop,它返回一个 React 元素。
<DataProvider render={(data) => <h1>{`Hello ${data.target}`}</h1>} />
React Router 和 DownShift 等库正在使用这种模式。
React 路由器
什么是 React Router?
React Router 是一个基于 React 构建的强大的路由库,它可以帮助您以惊人的速度向应用程序添加新的屏幕和流程,同时保持 URL 与页面上显示的内容同步。
React Router 与历史库有何不同?
React Router 是该库的包装器,history
它通过浏览器和哈希历史记录处理与浏览器的交互window.history
。它还提供内存历史记录,这对于没有全局历史记录的环境非常有用,例如移动应用开发(React Native)和使用 Node 的单元测试。
<Router>
React Router v4 的组件有哪些?
React Router v4 提供以下 3 个<Router>
组件:
<BrowserRouter>
<HashRouter>
<MemoryRouter>
上述组件将创建browser、hash和memoryhistory
history 实例。React Router v4通过对象中的上下文,使与路由器关联的实例的属性和方法可用router
。
的目的push()
和replace()
方法是什么history
?
历史实例有两种用于导航的方法。
push()
replace()
如果您将历史记录视为访问过的位置的数组,push()
则会向数组中添加一个新位置,并replace()
用新位置替换数组中的当前位置。
如何以编程方式使用 React Router v4 进行导航?
有三种不同的方法可以在组件内实现编程路由/导航。
- 使用
withRouter()
高阶函数:
高阶函数withRouter()
会将 history 对象作为组件的 prop 注入。该对象提供了push()
和replace()
方法,以避免使用 context。
import { withRouter } from 'react-router-dom'; // this also works with 'react-router-native'
const Button = withRouter(({ history }) => (
<button
type="button"
onClick={() => {
history.push('/new-location');
}}
>
{'Click Me!'}
</button>
));
- 使用
<Route>
组件和渲染道具模式:
该<Route>
组件传递与相同的道具withRouter()
,因此您将能够通过历史记录道具访问历史记录方法。
import { Route } from 'react-router-dom';
const Button = () => (
<Route
render={({ history }) => (
<button
type="button"
onClick={() => {
history.push('/new-location');
}}
>
{'Click Me!'}
</button>
)}
/>
);
- 使用上下文:
不推荐使用此选项,并将其视为不稳定的 API。
const Button = (props, context) => (
<button
type="button"
onClick={() => {
context.history.push('/new-location');
}}
>
{'Click Me!'}
</button>
);
Button.contextTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}),
};
如何在 React Router v4 中获取查询参数?
React Router v4 取消了解析查询字符串的功能,因为多年来用户一直要求支持不同的实现。因此,最终决定权交给了用户,让他们自行选择喜欢的实现。推荐的方法是使用查询字符串库。
const queryString = require('query-string');
const parsed = queryString.parse(props.location.search);
URLSearchParams
如果您想要一些原生的东西,您也可以使用:
const params = new URLSearchParams(props.location.search);
const foo = params.get('name');
您应该使用IE11 的polyfill 。
为什么会收到“路由器可能只有一个子元素”警告?
您必须将您的路线包装在一个<Switch>
块中,因为<Switch>
它是唯一的,因为它专门呈现一条路线。
首先,您需要添加以下内容Switch
到您的导入中:
import { Switch, Router, Route } from 'react-router';
然后在块内定义路线<Switch>
:
<Router>
<Switch>
<Route {/* ... */} />
<Route {/* ... */} />
</Switch>
</Router>
如何将参数传递给history.push
React Router v4 中的方法?
导航时,您可以将道具传递给history
对象:
this.props.history.push({
pathname: '/template',
search: '?name=sudheer',
state: { detail: response.data },
});
该search
属性用于在push()
方法中传递查询参数。
如何实现默认或NotFound页面?
A<Switch>
渲染第一个<Route>
匹配的子元素。<Route>
没有路径的 A 始终匹配。因此,您只需删除路径属性即可,如下所示
<Switch>
<Route exact path="/" component={Home} />
<Route path="/user" component={User} />
<Route component={NotFound} />
</Switch>
如何获取 React Router v4 的历史记录?
以下是在 React Router v4 上获取历史对象的步骤列表,
- 创建一个导出
history
对象的模块并在整个项目中导入该模块。
例如创建history.js
文件:
import { createBrowserHistory } from 'history';
export default createBrowserHistory({
/* pass a configuration object here if needed */
});
- 您应该使用
<Router>
组件而不是内置路由器。导入上述history.js
文件index.js
:
import { Router } from 'react-router-dom';
import history from './history';
import App from './App';
ReactDOM.render(
<Router history={history}>
<App />
</Router>,
holder,
);
- 您还可以使用
history
类似于内置历史对象的对象的推送方法:
// some-other-file.js
import history from './history';
history.push('/go-here');
如何实现登录后自动重定向?
该react-router
包提供了<Redirect>
React Router 中的组件。渲染<Redirect>
将导航到新位置。与服务器端重定向类似,新位置将覆盖历史记录堆栈中的当前位置。
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LoginComponent extends Component {
render() {
if (this.state.isLoggedIn === true) {
return <Redirect to="/your/redirect/page" />;
} else {
return <div>{'Login Please'}</div>;
}
}
}
React 国际化
什么是 React Intl?
React Intl库让 React 的内部化变得简单易行,它提供现成的组件和 API,能够处理从格式化字符串、日期、数字到复数的所有操作。React Intl 是FormatJS的一部分,FormatJS 通过其组件和 API 提供与 React 的绑定。
React Intl 的主要功能是什么?
以下是 React Intl 的主要功能,
- 显示带有分隔符的数字。
- 正确显示日期和时间。
- 显示相对于“现在”的日期。
- 将字符串中的标签变为复数。
- 支持 150 多种语言。
- 在浏览器和 Node 中运行。
- 建立在标准之上。
React Intl 中有哪两种格式化方式?
该库提供了两种格式化字符串、数字和日期的方法:
- 使用反应组件:
<FormattedMessage id={'account'} defaultMessage={'The amount is less than minimum balance.'} />
- 使用 API:
const messages = defineMessages({
accountMessage: {
id: 'account',
defaultMessage: 'The amount is less than minimum balance.',
},
});
formatMessage(messages.accountMessage);
如何使用<FormattedMessage>
React Intl 用作占位符?
组件返回<Formatted... />
的react-intl
是元素,而不是纯文本,因此它们不能用作占位符、替代文本等。在这种情况下,您应该使用低级 API formatMessage()
。您可以intl
使用高阶组件将对象注入到组件中injectIntl()
,然后使用该对象上的可用方法格式化消息formatMessage()
。
import React from 'react';
import { injectIntl, intlShape } from 'react-intl';
const MyComponent = ({ intl }) => {
const placeholder = intl.formatMessage({ id: 'messageId' });
return <input placeholder={placeholder} />;
};
MyComponent.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(MyComponent);
如何使用 React Intl 访问当前语言环境?
您可以使用以下方法获取应用程序任何组件中的当前语言环境injectIntl()
:
import { injectIntl, intlShape } from 'react-intl';
const MyComponent = ({ intl }) => <div>{`The current locale is ${intl.locale}`}</div>;
MyComponent.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(MyComponent);
如何使用 React Intl 格式化日期?
高阶组件将允许您通过组件中的 propsinjectIntl()
访问该方法。该方法由 的实例内部使用,并返回格式化日期的字符串表示形式。formatDate()
FormattedDate
import { injectIntl, intlShape } from 'react-intl';
const stringDate = this.props.intl.formatDate(date, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
});
const MyComponent = ({ intl }) => <div>{`The formatted date is ${stringDate}`}</div>;
MyComponent.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(MyComponent);
反应测试
React 测试中的浅渲染器是什么?
浅渲染对于在 React 中编写单元测试用例非常有用。它允许你将组件渲染到一层深度,并断言其渲染方法的返回结果,而无需担心未实例化或渲染的子组件的行为。
例如,如果您有以下组件:
function MyComponent() {
return (
<div>
<span className={'heading'}>{'Title'}</span>
<span className={'description'}>{'Description'}</span>
</div>
);
}
然后你可以断言如下:
import ShallowRenderer from 'react-test-renderer/shallow';
// in your test
const renderer = new ShallowRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
<span className={'heading'}>{'Title'}</span>,
<span className={'description'}>{'Description'}</span>,
]);
React 中的包是什么TestRenderer
?
此软件包提供了一个渲染器,可用于将组件渲染为纯 JavaScript 对象,而无需依赖 DOM 或原生移动环境。此软件包可以轻松获取由 ReactDOM 或 React Native 渲染的平台视图层次结构(类似于 DOM 树)的快照,而无需使用浏览器或jsdom
。
import TestRenderer from 'react-test-renderer';
const Link = ({ page, children }) => <a href={page}>{children}</a>;
const testRenderer = TestRenderer.create(
<Link page={'https://www.facebook.com/'}>{'Facebook'}</Link>,
);
console.log(testRenderer.toJSON());
// {
// type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ]
// }
ReactTestUtils 包的用途是什么?
该包中提供了ReactTestUtilswith-addons
,允许您针对模拟 DOM 执行操作以进行单元测试。
什么是 Jest?
Jest是 Facebook 基于 Jasmine 创建的 JavaScript 单元测试框架,提供自动化模拟创建和jsdom
环境。它常用于测试组件。
Jest 相对于 Jasmine 有哪些优势?
与 Jasmine 相比,它有几个优点:
- 自动查找在源代码中执行的测试。
- 运行测试时自动模拟依赖关系。
- 允许您同步测试异步代码。
- 使用伪 DOM 实现(通过
jsdom
)运行您的测试,以便您的测试可以在命令行上运行。 - 并行运行测试以便更快地完成。
举一个简单的Jest测试用例的例子
让我们为文件中两个数字相加的函数编写一个测试sum.js
:
const sum = (a, b) => a + b;
export default sum;
创建一个名为的文件sum.test.js
,其中包含实际测试:
import sum from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
然后将以下部分添加到您的package.json
:
{
"scripts": {
"test": "jest"
}
}
最后,运行yarn test
或npm test
,Jest 将打印结果:
$ yarn test
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (2ms)
React Redux
什么是助焊剂?
Flux是一种应用程序设计范式,用于替代更传统的 MVC 模式。它不是一个框架或库,而是一种新的架构,是对 React 和单向数据流概念的补充。Facebook 在内部使用 React 时也使用了这种模式。
调度程序、存储和视图组件之间的工作流程具有不同的输入和输出,如下所示:
什么是 Redux?
Redux是一个基于Flux 设计模式的 JavaScript 应用可预测状态容器。Redux 可以与 React 或任何其他视图库一起使用。它非常小巧(约 2kB)并且没有任何依赖项。
Redux 的核心原则是什么?
Redux 遵循三个基本原则:
- 单一事实来源:整个应用程序的状态存储在单个存储区内的对象树中。单一状态树让您能够更轻松地跟踪随时间推移的变化,并调试或检查应用程序。
- 状态是只读的:更改状态的唯一方法是发出一个动作(一个描述发生了什么的对象)。这确保了视图和网络回调都不会直接写入状态。
- 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,您可以编写 Reducer。Reducer 只是纯函数,它以先前的状态和操作作为参数,并返回下一个状态。
与 Flux 相比,Redux 有哪些缺点?
与其说是缺点,不如说是使用 Redux 相比 Flux 有一些缺点。具体如下:
- 你需要学习如何避免状态突变: Flux 对数据突变没有意见,但 Redux 不喜欢数据突变,而且许多 Redux 的补充包都假设你永远不会改变状态。你可以使用仅限开发的包(例如
redux-immutable-state-invariant
Immutable.js)来强制执行,或者指导你的团队编写不改变状态的代码。 - 您必须仔细选择您的软件包:虽然 Flux 明确不尝试解决撤消/重做、持久性或表单等问题,但 Redux 具有中间件和存储增强器等扩展点,并且它催生了丰富的生态系统。
- 目前还没有很好的 Flow 集成: Flux 目前允许您进行非常令人印象深刻的静态类型检查,但 Redux 尚不支持。
mapStateToProps()
和有什么区别mapDispatchToProps()
?
mapStateToProps()
是一个实用程序,可帮助您的组件获取更新状态(由其他一些组件更新):
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter),
};
};
mapDispatchToProps()
是一个实用程序,它将帮助您的组件触发动作事件(调度可能导致应用程序状态改变的动作):
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id));
},
};
};
建议始终使用“对象简写”形式mapDispatchToProps
Redux 将其包装在另一个类似于 (...args) => dispatch(onTodoClick(...args)) 的函数中,并将该包装函数作为 prop 传递给您的组件。
const mapDispatchToProps = {
onTodoClick,
};
我可以在 Reducer 中发送一个动作吗?
在 Reducer 中调度 Action 是一种反模式。你的 Reducer 应该没有副作用,只需消化 Action 的有效负载并返回一个新的状态对象即可。在 Reducer 中添加监听器并调度 Action 可能会导致 Action 链式执行和其他副作用。
如何在组件外部访问 Redux 存储?
您只需要从创建它的模块中导出存储即可createStore()
。此外,它不应该污染全局窗口对象。
store = createStore(myReducer);
export default store;
MVW 模式有哪些缺点?
- DOM 操作非常昂贵,会导致应用程序运行缓慢且效率低下。
- 由于循环依赖,围绕模型和视图创建了一个复杂的模型。
- 协作应用程序(如 Google Docs)会发生大量数据变化。
- 如果不添加太多额外的代码,就无法轻松地撤消(回到过去)。
Redux 和 RxJS 之间有什么相似之处吗?
这些库因用途不同而有很大差异,但也存在一些模糊的相似之处。
Redux 是一个用于管理整个应用程序状态的工具。它通常用作 UI 的架构。可以将其视为 Angular 的替代方案(或一半)。RxJS 是一个响应式编程库。它通常用作在 JavaScript 中完成异步任务的工具。可以将其视为 Promises 的替代方案。Redux 使用响应式范式,因为 Store 是响应式的。Store 会远程观察操作并进行自我修改。RxJS 也使用响应式范式,但它并非一种架构,而是提供了基本的构建块——可观察对象 (Observables),来实现这种模式。
如何在加载时分派动作?
componentDidMount()
您可以在方法中分派动作,并且render()
可以在方法中验证数据。
class App extends Component {
componentDidMount() {
this.props.fetchData();
}
render() {
return this.props.isLoaded ? <div>{'Loaded'}</div> : <div>{'Not Loaded'}</div>;
}
}
const mapStateToProps = (state) => ({
isLoaded: state.isLoaded,
});
const mapDispatchToProps = { fetchData };
export default connect(mapStateToProps, mapDispatchToProps)(App);
如何connect()
从 React Redux 使用?
您需要遵循两个步骤才能在容器中使用商店:
- 使用
mapStateToProps()
:它将商店中的状态变量映射到您指定的道具。 - 将上述 props 连接到你的容器:函数返回的对象
mapStateToProps
已连接到容器。你可以connect()
从导入react-redux
。
import React from 'react';
import { connect } from 'react-redux';
class App extends React.Component {
render() {
return <div>{this.props.containerData}</div>;
}
}
function mapStateToProps(state) {
return { containerData: state.data };
}
export default connect(mapStateToProps)(App);
如何在 Redux 中重置状态?
您需要在应用程序中编写一个根减速器,将处理操作委托给由生成的减速器combineReducers()
。
例如,让我们rootReducer()
返回 action 之后的初始状态USER_LOGOUT
。众所周知,undefined
无论 action 是什么,reducer 在被调用时都应该返回初始状态作为第一个参数。
const appReducer = combineReducers({
/* your app's top-level reducers */
});
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state = undefined;
}
return appReducer(state, action);
};
如果使用redux-persist
,您可能还需要清理存储。redux-persist
在存储引擎中保存状态副本。首先,您需要导入适当的存储引擎,然后在将状态设置为未定义之前解析状态,并清理每个存储状态键。
const appReducer = combineReducers({
/* your app's top-level reducers */
});
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
Object.keys(state).forEach((key) => {
storage.removeItem(`persist:${key}`);
});
state = undefined;
}
return appReducer(state, action);
};
at
Redux 连接装饰器中的符号有什么用途?
@符号实际上是一个 JavaScript 表达式,用于表示装饰器。装饰器使得在设计时注释和修改类和属性成为可能。
让我们举一个不使用和使用装饰器的 Redux 设置示例。
- 没有装饰器:
import React from 'react';
import * as actionCreators from './actionCreators';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
function mapStateToProps(state) {
return { todos: state.todos };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) };
}
class MyApp extends React.Component {
// ...define your main app here
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
- 使用装饰器:
import React from 'react';
import * as actionCreators from './actionCreators';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
function mapStateToProps(state) {
return { todos: state.todos };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) };
}
@connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {
// ...define your main app here
}
除了装饰器的用法之外,上述示例几乎相似。装饰器语法尚未内置于任何 JavaScript 运行时中,并且仍处于实验阶段,可能会发生变化。您可以使用 babel 来获得装饰器支持。
React context 和 React Redux 有什么区别?
您可以直接在应用程序中使用Context,它非常适合将数据传递到其设计的深层嵌套组件。
而Redux功能更强大,提供了 Context API 所不具备的大量功能。此外,React Redux 内部使用了 context,但并未在公共 API 中公开。
为什么 Redux 状态函数被称为 Reducer?
Reducer 始终返回状态的累积(基于所有先前和当前操作)。因此,它们充当状态的 Reducer。每次调用 Redux Reducer 时,状态和操作都会作为参数传递。然后,根据操作减少(或累积)此状态,并返回下一个状态。您可以Reduce一组操作和一个初始状态(存储状态),并在该初始状态上执行这些操作,以获得最终状态。
如何在 Redux 中发出 AJAX 请求?
您可以使用redux-thunk
允许您定义异步操作的中间件。
让我们举一个使用fetch API通过 AJAX 调用获取特定帐户的示例:
export function fetchAccount(id) {
return (dispatch) => {
dispatch(setLoadingAccountState()); // Show a loading spinner
fetch(`/account/${id}`, (response) => {
dispatch(doneFetchingAccount()); // Hide loading spinner
if (response.status === 200) {
dispatch(setAccount(response.json)); // Use a normal function to set the received state
} else {
dispatch(someError);
}
});
};
}
function setAccount(data) {
return { type: 'SET_Account', data: data };
}
我是否应该将所有组件的状态保存在 Redux 存储中?
将数据保存在 Redux 存储中,并将 UI 相关状态保存在组件内部。
访问 Redux 存储的正确方法是什么?
在组件中访问 store 的最佳方式是使用connect()
函数,它会创建一个新组件来包装现有组件。这种模式称为高阶组件,通常是在 React 中扩展组件功能的首选方式。它允许你将 state 和 action 创建器映射到组件,并在 store 更新时自动传入它们。
我们来看一个<FilterLink>
使用 connect 的组件的例子:
import { connect } from 'react-redux';
import { setVisibilityFilter } from '../actions';
import Link from '../components/Link';
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});
const FilterLink = connect(mapStateToProps, mapDispatchToProps)(Link);
export default FilterLink;
由于它具有相当多的性能优化并且通常不太可能导致错误,Redux 开发人员几乎总是建议使用它connect()
而不是直接访问存储(使用上下文 API)。
class MyComponent {
someMethod() {
doSomethingWith(this.context.store);
}
}
React Redux 中的组件和容器有什么区别?
组件是描述应用程序展示部分的类或功能组件。
容器是一个非正式术语,指的是连接到 Redux Store 的组件。容器订阅Redux 状态更新并调度操作,并且通常不渲染 DOM 元素;而是将渲染委托给展示型子组件。
Redux 中常量的用途是什么?
常量允许您在使用 IDE 时轻松找到项目中该特定功能的所有用法。它还可以防止您引入由拼写错误引起的愚蠢错误——在这种情况下,您会ReferenceError
立即得到一个。
通常我们会将它们保存在一个文件中(constants.js
或actionTypes.js
)。
export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const EDIT_TODO = 'EDIT_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const COMPLETE_ALL = 'COMPLETE_ALL';
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
在 Redux 中,您可以在两个地方使用它们:
- 在创建动作期间:
让我们来看看actions.js
:
import { ADD_TODO } from './actionTypes';
export function addTodo(text) {
return { type: ADD_TODO, text };
}
- 在减速器中:
让我们创建reducer.js
:
import { ADD_TODO } from './actionTypes';
export default (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false,
},
];
default:
return state;
}
};
有哪些不同的书写方式mapDispatchToProps()
?
有几种方法可以将动作创建者绑定到dispatch()
in mapDispatchToProps()
。
以下是可能的选项:
const mapDispatchToProps = (dispatch) => ({
action: () => dispatch(action()),
});
const mapDispatchToProps = (dispatch) => ({
action: bindActionCreators(action, dispatch),
});
const mapDispatchToProps = { action };
第三个选项只是第一个选项的简写。
和中ownProps
的参数有什么用?mapStateToProps()
mapDispatchToProps()
如果ownProps
指定了参数,React Redux 会将传递给组件的 props 传递到你的connect函数中。因此,如果你使用已连接的组件:
import ConnectedComponent from './containers/ConnectedComponent';
<ConnectedComponent user={'john'} />;
ownProps
你的mapStateToProps()
和函数内部mapDispatchToProps()
将是一个对象:
{
user: 'john';
}
您可以使用此对象来决定从这些函数返回什么。
如何构建 Redux 顶级目录?
大多数应用程序都有如下几个顶级目录:
- 组件:用于不知道 Redux 的哑组件。
- 容器:用于连接到 Redux 的智能组件。
- 动作:用于所有动作创建者,其中文件名对应于应用程序的一部分。
- Reducers:用于所有reducer,其中文件名与状态键相对应。
- Store:用于存储初始化。
这种结构非常适合中小型应用程序。
什么是 redux-saga?
redux-saga
是一个旨在使 React/Redux 应用程序中的副作用(异步事物,如数据获取和不纯事物,如访问浏览器缓存)更容易、更好。
它在 NPM 中可用:
$ npm install --save redux-saga
redux-saga 的心理模型是什么?
Saga就像应用程序中的一个单独线程,它完全负责副作用。redux-saga
是一个 redux中间件,这意味着这个线程可以通过正常的 Redux 操作从主应用程序启动、暂停和取消,它可以访问完整的 Redux 应用程序状态,也可以分派 Redux 操作。
call()
redux-saga 中的和有什么区别put()
?
call()
和都是put()
效果创建函数。call()
函数用于创建效果描述,指示中间件调用承诺。put()
函数创建一个效果,指示中间件将动作分派到商店。
让我们举例说明这些效果如何用于获取特定用户数据。
function* fetchUserSaga(action) {
// `call` function accepts rest arguments, which will be passed to `api.fetchUser` function.
// Instructing middleware to call promise, it resolved value will be assigned to `userData` variable
const userData = yield call(api.fetchUser, action.userId);
// Instructing middleware to dispatch corresponding action.
yield put({
type: 'FETCH_USER_SUCCESS',
userData,
});
}
什么是 Redux Thunk?
Redux Thunk中间件允许您编写返回函数而非 Action 的 Action 创建器。Thunk 可用于延迟 Action 的调度,或仅在满足特定条件时调度。内部函数接收 store 方法dispatch()
和getState()
作为参数。
redux-saga
和之间有什么区别redux-thunk
?
Redux Thunk和Redux Saga都处理副作用。在大多数情况下,Thunk 使用Promises来处理副作用,而 Saga 使用Generators来处理。Thunk 简单易用,Promises 也为许多开发者所熟悉,而 Sagas/Generators 功能更强大,但您需要学习它们。不过,这两种中间件可以共存,因此您可以先使用 Thunk,然后在需要时再引入 Sagas。
什么是 Redux DevTools?
Redux DevTools是一个实时编辑的 Redux 时间旅行环境,具有热重载、动作回放和可自定义的 UI。如果您不想费力安装 Redux DevTools 并将其集成到项目中,可以考虑使用适用于 Chrome 和 Firefox 的 Redux DevTools 扩展程序。
Redux DevTools 有哪些功能?
Redux DevTools 的一些主要功能如下:
- 让您检查每个状态和动作有效载荷。
- 让您通过取消操作回到过去。
- 如果您更改了 Reducer 代码,则每个分阶段的操作都将被重新评估。
- 如果减速器抛出错误,您将看到在哪个操作中发生了该错误,以及错误是什么。
- 使用
persistState()
商店增强器,您可以在页面重新加载时持久保存调试会话。
什么是 Redux 选择器以及为什么要使用它们?
选择器是将 Redux 状态作为参数并返回一些数据传递给组件的函数。
例如,从州获取用户详细信息:
const getUserData = (state) => state.user.data;
这些选择器有两个主要优点,
- 选择器可以计算派生数据,允许 Redux 存储最小可能状态
- 除非其中一个参数发生变化,否则选择器不会重新计算
什么是 Redux Form?
Redux Form与 React 和 Redux 协同工作,使 React 中的表单能够使用 Redux 存储其所有状态。Redux Form 可以处理原始 HTML5 输入,但它也能与 Material UI、React Widgets 和 React Bootstrap 等常见的 UI 框架完美兼容。
Redux Form 的主要特性是什么?
Redux Form 的一些主要功能包括:
- 通过 Redux 存储持久化字段值。
- 验证(同步/异步)和提交。
- 字段值的格式化、解析和规范化。
如何向 Redux 添加多个中间件?
您可以使用applyMiddleware()
。
例如,您可以添加redux-thunk
并将logger
它们作为参数传递给applyMiddleware()
:
import { createStore, applyMiddleware } from 'redux';
const createStoreWithMiddleware = applyMiddleware(ReduxThunk, logger)(createStore);
如何在 Redux 中设置初始状态?
您需要将初始状态作为第二个参数传递给 createStore:
const rootReducer = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});
const initialState = {
todos: [{ id: 123, name: 'example', completed: false }],
};
const store = createStore(rootReducer, initialState);
Relay 与 Redux 有何不同?
Relay 与 Redux 类似,因为它们都使用单个存储。主要区别在于 Relay 仅管理源自服务器的状态,并且所有对状态的访问都通过GraphQL查询(用于读取数据)和突变(用于更改数据)进行。Relay 会为您缓存数据,并通过仅获取更改的数据(仅此而已)来优化数据获取。
Redux 中的动作是什么?
操作是纯 JavaScript 对象或信息负载,用于将数据从您的应用发送到您的商店。它们是商店的唯一信息来源。操作必须具有 type 属性,用于指示正在执行的操作的类型。
例如,让我们采取一个表示添加新待办事项的操作:
{
type: ADD_TODO,
text: 'Add todo item'
}
反应原生
React Native 和 React 有什么区别?
React是一个 JavaScript 库,支持前端 Web 和在服务器上运行,用于构建用户界面和 Web 应用程序。
React Native是一个可编译为本机应用程序组件的移动框架,允许您使用 JavaScript 构建本机移动应用程序(iOS、Android 和 Windows),允许您使用 React 构建组件,并在底层实现 React。
如何测试 React Native 应用程序?
React Native 只能在 iOS 和 Android 等移动模拟器上测试。您可以使用 expo app ( https://expo.io ) 在手机上运行该应用。它使用二维码同步,您的手机和电脑应该处于同一无线网络中。
如何在 React Native 中进行日志记录?
您可以使用console.log
、console.warn
等。从 React Native v0.29 开始,您只需运行以下命令即可在控制台中查看日志:
$ react-native log-ios
$ react-native log-android
如何调试你的 React Native?
按照以下步骤调试 React Native 应用程序:
- 在 iOS 模拟器中运行您的应用程序。
- 按下
Command + D
,网页就会打开http://localhost:8081/debugger-ui
。 - 启用“捕获异常时暂停”以获得更好的调试体验。
- 按打开 Chrome 开发者工具,或者通过-> ->
Command + Option + I
打开。View
Developer
Developer Tools
- 您现在应该能够像平常一样进行调试。
React 支持的库和集成
什么是重新选择以及它如何工作?
Reselect是一个使用memoization概念的 Redux选择器库。它最初是为了计算类似 Redux 应用程序状态中的派生数据而编写的,但它不能与任何架构或库绑定。
Reselect 会保留上次调用的最后输入/输出的副本,并且仅当其中一个输入发生变化时才重新计算结果。如果连续两次提供相同的输入,Reselect 将返回缓存的输出。它的记忆和缓存功能完全可自定义。
什么是 Flow?
Flow是一个静态类型检查器,旨在查找 JavaScript 中的类型错误。与传统类型系统相比,Flow 类型可以表达更细粒度的区分。例如,null
与大多数类型系统不同,Flow 可以帮助您捕获涉及 的错误。
Flow 和 PropTypes 有什么区别?
Flow 是一个静态分析工具(静态检查器),它使用语言的超集,允许您向所有代码添加类型注释并在编译时捕获一整类错误。
PropTypes 是一个基础类型检查器(运行时检查器),现已添加到 React 中。它只能检查传递给指定组件的 props 的类型。如果您希望为整个项目进行更灵活的类型检查,Flow/TypeScript 是合适的选择。
如何在 React 中使用 Font Awesome 图标?
按照以下步骤将 Font Awesome 纳入 React 中:
- 安装
font-awesome
:
$ npm install --save font-awesome
- 导入
font-awesome
您的index.js
文件:
import 'font-awesome/css/font-awesome.min.css';
- 在以下位置添加 Font Awesome 类
className
:
render() {
return <div><i className={'fa fa-spinner'} /></div>
}
什么是 React Dev Tools?
React 开发者工具可让您检查组件层次结构,包括组件 props 和状态。它既可以作为浏览器扩展(适用于 Chrome 和 Firefox),也可以作为独立应用(适用于其他环境,包括 Safari、IE 和 React Native)。
适用于不同浏览器或环境的官方扩展。
- Chrome 扩展程序
- Firefox 扩展
- 独立应用程序(Safari、React Native 等)
为什么 DevTools 无法在 Chrome 中加载本地文件?
如果您在浏览器中打开了本地 HTML 文件(file://...
),那么您必须首先打开Chrome 扩展程序并检查Allow access to file URLs
。
如何在 React 中使用 Polymer?
您需要按照以下步骤在 React 中使用 Polymer,
- 创建聚合物元素:
<link rel="import" href="../../bower_components/polymer/polymer.html" />;
Polymer({
is: 'calender-element',
ready: function () {
this.textContent = 'I am a calender';
},
});
- 通过将 Polymer 组件 HTML 标签导入 HTML 文档来创建它,例如将其导入到
index.html
React 应用程序中:
<link rel="import" href="./src/polymer-components/calender-element.html" />
- 在 JSX 文件中使用该元素:
import React from 'react';
class MyComponent extends React.Component {
render() {
return <calender-element />;
}
}
export default MyComponent;
React 相对于 Vue.js 有哪些优势?
React 相比 Vue.js 具有以下优势:
- 在大型应用程序开发中提供更大的灵活性。
- 更容易测试。
- 适用于创建移动应用程序。
- 提供更多信息和解决方案。
注:以上优势仅代表个人观点,具体情况会根据专业经验而有所不同。但它们作为基本参数很有用。
React 和 Angular 有什么区别?
让我们以表格形式看看 React 和 Angular 之间的区别。
反应 | 角度 |
---|---|
React 是一个库,只有 View 层 | Angular 是一个框架,具有完整的 MVC 功能 |
React 在服务器端处理渲染 | AngularJS 仅在客户端渲染,但 Angular 2 及更高版本在服务器端渲染 |
React 使用 JSX,它看起来像 JS 中的 HTML,这可能会造成混淆 | Angular 遵循 HTML 的模板方法,这使得代码更短且易于理解 |
React Native,是一种 React 类型,用于构建更快、更稳定的移动应用程序 | Ionic,Angular 的移动原生应用相对不太稳定,速度较慢 |
在 React 中,数据仅以单向流动,因此调试很容易 | 在 Angular 中,数据是双向流动的,即子组件和父组件之间有双向数据绑定,因此调试通常很困难 |
注意:以上差异列表仅代表个人观点,具体差异会根据专业经验而有所不同。但它们作为基本参数很有用。
为什么 React 选项卡没有显示在 DevTools 中?
页面加载时,React DevTools会设置一个名为 的全局变量__REACT_DEVTOOLS_GLOBAL_HOOK__
,然后 React 会在初始化期间与该钩子进行通信。如果网站未使用 React,或者 React 无法与 DevTools 通信,则不会显示该标签页。
什么是样式组件?
styled-components
是一个用于设置 React 应用程序样式的 JavaScript 库。它消除了样式和组件之间的映射,让你能够编写实际的 CSS 代码,并辅以 JavaScript 的支持。
给出一个 Styled Components 的例子?
让我们为每个组件创建具有特定样式的<Title>
组件<Wrapper>
。
import React from 'react';
import styled from 'styled-components';
// Create a <Title> component that renders an <h1> which is centered, red and sized at 1.5em
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a <Wrapper> component that renders a <section> with some padding and a papayawhip background
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
这两个变量Title
和Wrapper
现在是您可以像任何其他反应组件一样渲染的组件。
<Wrapper>
<Title>{'Lets start first styled component!'}</Title>
</Wrapper>
什么是 Relay?
Relay 是一个 JavaScript 框架,用于使用 React 视图层为 Web 应用程序提供数据层和客户端-服务器通信。
如何在create-react-app
应用程序中使用 TypeScript?
从react-scripts@2.1.0或更高版本开始,内置了对 TypeScript 的支持。也就是说,create-react-app
现在原生支持 TypeScript。您只需传递--typescript
以下选项即可:
npx create-react-app my-app --typescript
# or
yarn create react-app my-app --typescript
但是对于较低版本的反应脚本,只需在创建新项目时提供--scripts-version
选项即可。是一组调整,采用标准项目管道并将 TypeScript 融入其中。react-scripts-ts
react-scripts-ts
create-react-app
现在项目布局应该如下所示:
my-app/
├─ .gitignore
├─ images.d.ts
├─ node_modules/
├─ public/
├─ src/
│ └─ ...
├─ package.json
├─ tsconfig.json
├─ tsconfig.prod.json
├─ tsconfig.test.json
└─ tslint.json
各种各样的
Reselect 库的主要功能是什么?
让我们看看 Reselect 库的主要功能,
- 选择器可以计算派生数据,从而允许 Redux 存储尽可能小的状态。
- 选择器非常高效。除非参数发生变化,否则选择器不会重新计算。
- 选择器是可组合的。它们可以用作其他选择器的输入。
举一个Reselect用法的例子?
让我们使用 Reselect 的简化用法来计算装运订单的不同金额:
import { createSelector } from 'reselect';
const shopItemsSelector = (state) => state.shop.items;
const taxPercentSelector = (state) => state.shop.taxPercent;
const subtotalSelector = createSelector(shopItemsSelector, (items) =>
items.reduce((acc, item) => acc + item.value, 0),
);
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100),
);
export const totalSelector = createSelector(subtotalSelector, taxSelector, (subtotal, tax) => ({
total: subtotal + tax,
}));
let exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.2 },
{ name: 'orange', value: 0.95 },
],
},
};
console.log(subtotalSelector(exampleState)); // 2.15
console.log(taxSelector(exampleState)); // 0.172
console.log(totalSelector(exampleState)); // { total: 2.322 }
静态对象可以与 React 中的 ES6 类一起使用吗?
否,statics
仅适用于React.createClass()
:
someComponent = React.createClass({
statics: {
someMethod: function () {
// ..
},
},
});
但是你可以在 ES6+ 类中编写静态代码,如下所示,
class Component extends React.Component {
static propTypes = {
// ...
};
static someMethod() {
// ...
}
}
或者在课外写如下,
class Component extends React.Component {
....
}
Component.propTypes = {...}
Component.someMethod = function(){....}
Redux 只能与 React 一起使用吗?
Redux 可以用作任何 UI 层的数据存储。最常见的用法是与 React 和 React Native 配合使用,但也为 Angular、Angular 2、Vue、Mithril 等提供绑定。Redux 仅提供了一种订阅机制,可供任何其他代码使用。
您是否需要特定的构建工具才能使用 Redux?
Redux 最初是用 ES6 编写的,并使用 Webpack 和 Babel 将其转译为 ES5 以供生产环境使用。无论您的 JavaScript 构建过程如何,都应该能够使用它。Redux 还提供了 UMD 构建,无需任何构建过程即可直接使用。
Redux Form 如何initialValues
从状态获取更新?
您需要添加enableReinitialize : true
设置。
const InitializeFromStateForm = reduxForm({
form: 'initializeFromState',
enableReinitialize: true,
})(UserEdit);
如果您的initialValues
道具更新,您的表格也会更新。
React PropTypes 如何允许一个 prop 具有不同的类型?
您可以使用oneOfType()
的方法PropTypes
。
例如,高度属性可以用string
或number
类型定义,如下所示:
Component.PropTypes = {
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
我可以导入 SVG 文件作为反应组件吗?
您可以直接将 SVG 作为组件导入,而无需将其作为文件加载。此功能适用于 32 位react-scripts@2.0.0
及更高版本。
import { ReactComponent as Logo } from './logo.svg';
const App = () => (
<div>
{/* Logo is an actual react component */}
<Logo />
</div>
);
注意:不要忘记导入中的花括号。
为什么不推荐使用内联 ref 回调或函数?
如果将 ref 回调定义为内联函数,它将在更新过程中被调用两次,第一次使用 null,第二次使用 DOM 元素。这是因为每次渲染时都会创建一个新的函数实例,因此 React 需要清除旧的 ref 并设置新的 ref。
class UserForm extends Component {
handleSubmit = () => {
console.log('Input Value is: ', this.input.value);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={(input) => (this.input = input)} /> // Access DOM input in handle
submit
<button type="submit">Submit</button>
</form>
);
}
}
但我们的期望是,ref 回调在组件挂载时被调用一次。一个快速的解决方法是使用 ES7 的类属性语法来定义函数
class UserForm extends Component {
handleSubmit = () => {
console.log('Input Value is: ', this.input.value);
};
setSearchInput = (input) => {
this.input = input;
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.setSearchInput} /> // Access DOM input in handle submit
<button type="submit">Submit</button>
</form>
);
}
}
注意:在 React v16.3 中,⬆ 返回顶部
React 中的渲染劫持是什么?
渲染劫持的概念是指能够控制一个组件从另一个组件输出什么内容。它实际上意味着你通过将组件包装到高阶组件中来装饰它。通过包装,你可以注入额外的 props 或进行其他更改,从而改变渲染逻辑。它实际上并不启用渲染劫持,但通过使用 HOC,你可以让你的组件以不同的方式运行。
HOC 工厂实现有哪些?
在 React 中实现 HOC 主要有两种方式。
- 道具代理(PP)和
- 继承反转(二)。
但它们遵循不同的方法来操作WrappedComponent。
道具代理
在这个方法中,HOC 的 render 方法返回一个 WrappedComponent 类型的 React Element。我们还会传递 HOC 接收到的 props,因此得名Props Proxy。
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
继承反转
在这种方法中,返回的 HOC 类(Enhancer)会扩展 WrappedComponent。之所以称之为反向继承,是因为 WrappedComponent 不是扩展某个 Enhancer 类,而是被动地被 Enhancer 扩展。这样一来,它们之间的关系看起来似乎是反向的。
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render();
}
};
}
如何将数字传递给 React 组件?
您应该通过花括号({})传递数字,而将字符串放在引号中
React.render(<User age={30} department={'IT'} />, document.getElementById('container'));
我需要把所有状态都保存到 Redux 中吗?我应该使用 React 内部状态吗?
这取决于开发人员的决定。也就是说,开发人员需要确定应用程序由哪些类型的状态组成,以及每种状态应该存放在何处。有些用户倾向于将每条数据都保存在 Redux 中,以便始终保持应用程序完全可序列化且受控的版本。而另一些用户则倾向于将非关键状态或 UI 状态(例如“此下拉菜单当前是否打开”)保存在组件的内部状态中。
以下是确定应将哪种类型的数据放入 Redux 的经验法则
- 应用程序的其他部分是否关心这些数据?
- 您是否需要能够基于这些原始数据创建进一步的派生数据?
- 是否使用相同的数据来驱动多个组件?
- 能够将此状态恢复到给定的时间点(即时间旅行调试)对您来说有价值吗?
- 您是否要缓存数据(即,如果数据已经存在则使用当前状态,而不是重新请求它)?
React 中 registerServiceWorker 的用途是什么?
React 默认会创建一个 Service Worker,无需任何配置。Service Worker 是一个 Web API,它能帮助你缓存资源和其他文件,以便用户在离线或网络速度较慢时仍能在屏幕上看到结果,从而提升用户体验。以上就是你目前需要了解的 Service Worker 的全部内容。它主要讲的是为网站添加离线功能。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
React 备忘录功能是什么?
当类组件的输入 props 相同时,可以使用PureComponent 或 shouldComponentUpdate限制其渲染。现在,你可以将函数组件包装在React.memo中,从而实现同样的效果。
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
什么是 React 惰性函数?
该React.lazy
函数允许你将动态导入的组件渲染为常规组件。当组件渲染时,它将自动加载包含 OtherComponent 的 bundle。它必须返回一个 Promise,该 Promise 解析为一个默认导出的模块,该模块包含一个 React 组件。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
注意: React.lazy 和 Suspense 尚不支持服务端渲染。如果您想在服务端渲染的应用中进行代码拆分,我们仍然推荐使用 React Loadable。
如何防止使用 setState 进行不必要的更新?
您可以将状态的当前值与现有状态值进行比较,并决定是否重新渲染页面。如果值相同,则需要返回null以停止重新渲染,否则返回最新的状态值。
例如,用户个人资料信息有条件地呈现如下,
getUserProfile = (user) => {
const latestAddress = user.address;
this.setState((state) => {
if (state.address === latestAddress) {
return null;
} else {
return { title: latestAddress };
}
});
};
如何在 React 16 版本中渲染数组、字符串和数字?
数组:与旧版本不同,在 React16 中,你无需确保render方法返回单个元素。你可以通过返回数组来返回多个兄弟元素,而无需使用包装元素。
例如,让我们来看看下面的开发人员列表,
const ReactJSDevs = () => {
return [<li key="1">John</li>, <li key="2">Jackie</li>, <li key="3">Jordan</li>];
};
您还可以将此项目数组合并到另一个数组组件中。
const JSDevs = () => {
return (
<ul>
<li>Brad</li>
<li>Brodge</li>
<ReactJSDevs />
<li>Brandon</li>
</ul>
);
};
字符串和数字:您还可以从渲染方法返回字符串和数字类型。
render() {
return 'Welcome to ReactJS questions';
}
// Number
render() {
return 2018;
}
如何在 React 类中使用类字段声明语法?
使用类字段声明可以使 React 类组件更加简洁。您可以无需使用构造函数来初始化本地状态,并使用箭头函数声明类方法,而无需额外绑定它们。
让我们举一个反例来演示不使用构造函数和不绑定方法的状态的类字段声明,
class Counter extends Component {
state = { value: 0 };
handleIncrement = () => {
this.setState((prevState) => ({
value: prevState.value + 1,
}));
};
handleDecrement = () => {
this.setState((prevState) => ({
value: prevState.value - 1,
}));
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
</div>
);
}
}
什么是钩子?
Hooks 是一项新功能(React 16.8),它允许您无需编写类即可使用状态和其他 React 功能。
让我们看一个 useState hook 的例子,
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
钩子需要遵循什么规则?
使用钩子需要遵循两个规则,
- 仅在 React 函数的顶层调用 Hook。也就是说,不应在循环、条件或嵌套函数中调用 Hook。这将确保每次组件渲染时 Hook 都以相同的顺序被调用,并在多次 useState 和 useEffect 调用之间保留 Hook 的状态。
- 仅从 React 函数调用 Hooks。即,您不应该从常规 JavaScript 函数调用 Hooks。
如何确保钩子遵循项目中的规则?
React 团队发布了一个名为eslint-plugin-react-hooks的 ESLint 插件,它强制执行这两条规则。你可以使用以下命令将此插件添加到你的项目中:
npm install eslint-plugin-react-hooks@next
并在你的 ESLint 配置文件中应用以下配置,
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}
注意:此插件默认在 Create React App 中使用。
Flux 和 Redux 之间有什么区别?
以下是 Flux 和 Redux 之间的主要区别
通量 | Redux |
---|---|
状态是可变的 | 状态是不可变的 |
Store 包含状态和变更逻辑 | 商店和更改逻辑是分开的 |
存在多个商店 | 仅存在一家商店 |
所有商店均断开且平坦 | 具有分层 Reducer 的单一存储 |
它有一个单例调度程序 | 没有调度员的概念 |
React 组件订阅 store | 容器组件使用 connect 函数 |
React Router V4 有哪些好处?
以下是 React Router V4 模块的主要优点,
- 在 React Router v4(版本 4)中,API 完全与组件相关。路由器可以被视为一个单独的组件(
<BrowserRouter>
),它包装了特定的子路由器组件(<Route>
)。 - 您无需手动设置历史记录。路由器模块会通过使用
<BrowserRouter>
组件包装路由来处理历史记录。 - 通过仅添加特定的路由器模块(Web,核心或本机)来减小应用程序大小
您能描述一下 componentDidCatch 生命周期方法签名吗?
componentDidCatch生命周期方法在后代组件抛出错误后调用。该方法接收两个参数:
- 错误:- 抛出的错误对象
- 信息: - 带有 componentStack 键的对象包含有关哪个组件引发错误的信息。
方法结构如下
componentDidCatch(error, info);
在哪些情况下错误边界不会捕获错误?
以下是错误边界不起作用的情况,
- 事件处理程序内部
- 使用setTimeout 或 requestAnimationFrame回调的异步代码
- 在服务器端渲染期间
- 当错误边界代码本身抛出错误时
为什么事件处理程序不需要错误边界?
错误边界无法捕获事件处理程序中的错误。与渲染方法或生命周期方法不同,事件处理程序不会在渲染期间发生或调用。因此,React 知道如何在事件处理程序中恢复此类错误。如果您仍然需要在事件处理程序中捕获错误,请使用常规 JavaScript try / catch 语句,如下所示
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
handleClick = () => {
try {
// Do something that could throw
} catch (error) {
this.setState({ error });
}
};
render() {
if (this.state.error) {
return <h1>Caught an error.</h1>;
}
return <div onClick={this.handleClick}>Click Me</div>;
}
}
上述代码使用原始 javascript try/catch 块而不是错误边界来捕获错误。
try catch 块和错误边界之间有什么区别?
Try catch 块与命令式代码一起工作,而错误边界则用于在屏幕上呈现声明式代码。
例如,用于以下命令式代码的 try catch 块
try {
showButton();
} catch (error) {
// ...
}
错误边界包装声明性代码如下,
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
因此,如果在组件树深处的某个地方,由setState导致的componentDidUpdate方法中发生错误,它仍然会正确传播到最近的错误边界。
React 16 中未捕获错误的行为是怎样的?
在 React 16 中,任何未被错误边界捕获的错误都将导致整个 React 组件树被卸载。这样做的原因是,保留损坏的 UI 比彻底移除它更糟糕。例如,对于支付应用来说,显示错误的金额比什么都不渲染更糟糕。
错误边界的正确位置是什么?
错误边界使用的粒度取决于开发人员的项目需求。您可以采用以下任一方法:
- 您可以包装顶级路由组件来显示整个应用程序的通用错误消息。
- 您还可以将单个组件包装在错误边界中,以防止它们导致应用程序的其余部分崩溃。
从错误边界进行组件堆栈跟踪有什么好处?
除了错误消息和 JavaScript 堆栈之外,React16 还将使用错误边界概念显示带有文件名和行号的组件堆栈跟踪。
例如,BuggyCounter 组件显示组件堆栈跟踪如下,
类组件需要定义什么方法?
该render()
方法是类组件中唯一必需的方法。即,对于类组件来说,除了 render 方法之外的所有方法都是可选的。
render 方法可能的返回类型有哪些?
以下是使用并从渲染方法返回的类型的列表,
- React 元素:指示 React 渲染 DOM 节点的元素。它包括 HTML 元素,例如 JavaScript 元素
<div/>
和用户定义元素。 - 数组和片段:返回多个元素以呈现为数组和片段来包装多个元素
- 门户:将子项渲染到不同的 DOM 子树中。
- 字符串和数字:将字符串和数字渲染为 DOM 中的文本节点
- 布尔值或空值:不渲染任何内容,但这些类型用于有条件地渲染内容。
构造函数的主要用途是什么?
构造函数主要用于两个目的,
- 通过将对象赋值给 this.state 来初始化本地状态
- 将事件处理程序方法绑定到实例例如,下面的代码涵盖了上述两种情况,
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
是否必须为 React 组件定义构造函数?
不,这不是强制性的。即,如果您不初始化状态并且不绑定方法,则不需要为 React 组件实现构造函数。
什么是默认道具?
defaultProps 是组件类的一个属性,用于设置该类的默认 props。它用于未定义的 props,但不用于 null props。
例如,让我们为按钮组件创建颜色默认属性,
class MyButton extends React.Component {
// ...
}
MyButton.defaultProps = {
color: 'red',
};
如果未提供 props.color,则它将默认值设置为“红色”。即,每当您尝试访问颜色属性时,它都会使用默认值
render() {
return <MyButton /> ; // props.color will be set to red
}
注意:如果您提供空值,那么它仍保持空值。
为什么不应该在 componentWillUnmount 中调用 setState?
您不应该调用setState()
,componentWillUnmount()
因为一旦组件实例被卸载,它将永远不会再次被安装。
getDerivedStateFromError 的目的是什么?
此生命周期方法在后代组件抛出错误后调用。它接收抛出的错误作为参数,并返回一个值来更新状态。
生命周期方法的签名如下,
static getDerivedStateFromError(error)
让我们以上述生命周期方法的错误边界用例为例进行演示,
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
组件重新渲染时的方法顺序是什么?
更新可能由 props 或 state 的更改引起。当组件重新渲染时,将按以下顺序调用以下方法。
- 静态 getDerivedStateFromProps()
- 应该组件更新()
- 使成为()
- 获取更新前的快照()
- 组件更新()
错误处理期间调用哪些方法?
当渲染过程中、生命周期方法中或任何子组件的构造函数中出现错误时,将调用以下方法。
- 静态 getDerivedStateFromError()
- 组件DidCatch()
displayName 类属性的用途是什么?
displayName 字符串用于调试消息。通常情况下,您无需显式设置它,因为它是根据定义组件的函数或类的名称推断出来的。如果您希望为了调试目的或创建高阶组件时显示不同的名称,则可能需要显式设置它。
例如,为了便于调试,请选择一个显示名称来表明它是 withSubscription HOC 的结果。
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {
/* ... */
}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
哪些浏览器支持 React 应用程序?
React 支持所有流行的浏览器,包括 Internet Explorer 9 及更高版本,尽管旧版浏览器(如 IE 9 和 IE 10)需要一些 polyfill。如果使用es5-shim 和 es5-sham polyfill,它甚至支持不支持 ES5 方法的旧版浏览器。
unmountComponentAtNode 方法的目的是什么?
此方法来自 react-dom 包,它会从 DOM 中移除已挂载的 React 组件,并清理其事件处理程序和状态。如果容器中未挂载任何组件,则调用此函数不会执行任何操作。如果组件已卸载,则返回 true;如果没有可卸载的组件,则返回 false。
方法签名如下,
ReactDOM.unmountComponentAtNode(container);
什么是代码分割?
代码拆分是 Webpack 和 Browserify 等打包工具支持的功能,它可以创建多个可在运行时动态加载的包。React 项目通过动态 import() 功能支持代码拆分。
例如,在下面的代码片段中,它将 moduleA.js 及其所有唯一依赖项作为一个单独的块,仅在用户单击“加载”按钮后加载。moduleA.js
const moduleA = 'Hello';
export { moduleA };
App.js
import React, { Component } from 'react';
class App extends Component {
handleClick = () => {
import('./moduleA')
.then(({ moduleA }) => {
// Use moduleA
})
.catch((err) => {
// Handle failure
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>Load</button>
</div>
);
}
}
export default App;
严格模式有什么好处?
这在以下情况下会有所帮助
- 识别具有不安全生命周期方法的组件。
- 关于旧字符串引用API 使用的警告。
- 检测意外的副作用。
- 检测遗留上下文API。
- 关于弃用 findDOMNode 用法的警告
什么是 Keyed Fragments?
使用显式语法声明的 Fragment 可以包含键。一般用例是将集合映射到 Fragment 数组,如下所示:
function Glossary(props) {
return (
<dl>
{props.items.map((item) => (
// Without the `key`, React will fire a key warning
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
注意: key 是唯一可以传递给 Fragment 的属性。未来可能会支持其他属性,例如事件处理程序。
React 是否支持所有 HTML 属性?
从 React 16 开始,标准或自定义 DOM 属性均已完全支持。由于 React 组件通常同时使用自定义属性和 DOM 相关属性,因此 React 像 DOM API 一样使用了驼峰命名约定。
让我们针对标准 HTML 属性采取一些措施,
<div tabIndex="-1" /> // Just like node.tabIndex DOM API
<div className="Button" /> // Just like node.className DOM API
<input readOnly={true} /> // Just like node.readOnly DOM API
这些 props 的作用与相应的 HTML 属性类似,除了特殊情况外。它也支持所有 SVG 属性。
HOC 有哪些局限性?
高阶组件除了其优点之外,还有一些注意事项。以下按顺序列出:
- 不要在渲染方法中使用 HOC:不建议在组件的渲染方法中将 HOC 应用于组件。
render() {
// A new version of EnhancedComponent is created on every render
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// That causes the entire subtree to unmount/remount each time!
return <EnhancedComponent />;
}
上述代码会重新挂载组件,导致该组件及其所有子组件的状态丢失,从而影响性能。建议在组件定义之外使用 HOC,这样生成的组件只需创建一次。
- 静态方法必须复制:当你将 HOC 应用于组件时,新组件不具有原始组件的任何静态方法
// Define a static method
WrappedComponent.staticMethod = function () {
/*...*/
};
// Now apply a HOC
const EnhancedComponent = enhance(WrappedComponent);
// The enhanced component has no static method
typeof EnhancedComponent.staticMethod === 'undefined'; // true
您可以通过在返回容器之前将方法复制到容器上来解决这个问题,
function enhance(WrappedComponent) {
class Enhance extends React.Component {
/*...*/
}
// Must know exactly which method(s) to copy :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
- Refs 不会被传递:对于 HOC,你需要将所有 props 传递给被包装的组件,但这不适用于 ref。这是因为 ref 并非真正类似于 key 的 prop。在这种情况下,你需要使用 React.forwardRef API。
如何在 DevTools 中调试 forwardRefs?
React.forwardRef接受渲染函数作为参数,DevTools 使用此函数来确定为 ref 转发组件显示什么。
例如,如果您没有命名渲染函数或不使用 displayName 属性,那么它将在 DevTools 中显示为“ForwardRef”,
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
但是如果你命名渲染函数,那么它将显示为“ForwardRef(myFunction)”
const WrappedComponent = React.forwardRef(function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
});
另外,您还可以为 forwardRef 函数设置 displayName 属性,
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
// Give this component a more helpful display name in DevTools.
// e.g. "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}
组件 props 何时默认为 true?
如果未传递任何 prop 值,则默认为 true。此行为是为了与 HTML 的行为相匹配。
例如,以下表达式是等效的,
<MyInput autocomplete />
<MyInput autocomplete={true} />
注意:不建议使用此方法,因为它可能与 ES6 对象简写混淆(例如,{name}
它是 的缩写{name: name}
)
什么是 NextJS 以及它的主要功能?
Next.js 是一个流行的轻量级框架,用于使用 React 构建静态和服务器渲染应用程序。它还提供样式和路由解决方案。以下是 NextJS 提供的主要功能:
- 默认服务器渲染
- 自动代码分割,加快页面加载速度
- 简单的客户端路由(基于页面)
- 基于 Webpack 的开发环境,支持(HMR)
- 能够使用 Express 或任何其他 Node.js HTTP 服务器实现
- 可使用您自己的 Babel 和 Webpack 配置进行定制
如何将事件处理程序传递给组件?
你可以将事件处理程序和其他函数作为 props 传递给子组件。它可以在子组件中按如下方式使用:
<button onClick="{this.handleClick}"></button>
在渲染方法中使用箭头函数好吗?
是的,你可以使用。这通常是向回调函数传递参数最简单的方法。但是你需要在使用它时优化性能。
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={() => this.handleClick()}>Click Me</button>;
}
}
注意:在 render 方法中使用箭头函数会在每次组件渲染时创建一个新函数,这可能会对性能产生影响
如何防止一个函数被多次调用?
如果您使用onClick 或 onScroll等事件处理程序,并希望防止回调触发过快,则可以限制回调的执行速率。您可以通过以下方式实现:
- 节流:根据时间频率进行更改。例如,可以使用 lodash 函数 _.throttle 来使用它
- 去抖动:在一段时间不活动后发布更改。例如,可以使用 _.debounce lodash 函数
- RequestAnimationFrame 节流:基于 requestAnimationFrame 进行更改。例如,可以使用 raf-schd lodash 函数
JSX 如何防止注入攻击?
React DOM 在渲染 JSX 中嵌入的任何值之前都会进行转义。这样可以确保你永远不会注入任何未在应用程序中明确编写的内容。所有内容在渲染之前都会被转换为字符串。
例如,您可以嵌入如下所示的用户输入,
const name = response.potentiallyMaliciousInput;
const element = <h1>{name}</h1>;
这样您就可以防止应用程序中的 XSS(跨站点脚本)攻击。
如何更新渲染元素?
您可以通过将新创建的元素传递给 ReactDOM 的 render 方法来更新 UI(由渲染的元素表示)。
例如,让我们举一个滴答作响的时钟示例,它通过多次调用渲染方法来更新时间,
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
怎么说道具是只读的?
当你将组件声明为函数或类时,它绝不能修改其自身的 props。
让我们采取低于资本的函数,
function capital(amount, interest) {
return amount + interest;
}
上述函数被称为“纯函数”,因为它不会尝试改变输入,并且对于相同的输入始终返回相同的结果。因此,React 有一条规则:“所有 React 组件在处理其 props 时都必须表现得像纯函数。”
您如何说状态更新已合并?
当你在组件中调用 setState() 时,React 会将你提供的对象合并到当前状态中。
例如,让我们以 Facebook 用户的帖子和评论详细信息作为状态变量,
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
现在,您可以使用下面的单独调用来独立更新它们setState()
,
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
如上面的代码片段所述,this.setState({comments})
仅更新评论变量而不修改或替换帖子变量。
如何将参数传递给事件处理程序?
在迭代或循环期间,通常需要向事件处理程序传递一个额外的参数。这可以通过箭头函数或 bind 方法实现。
让我们以网格中更新的用户详细信息为例,
<button onClick={(e) => this.updateUser(userId, e)}>Update User details</button>
<button onClick={this.updateUser.bind(this, userId)}>Update User details</button>
在这两种方法中,合成参数 e 都作为第二个参数传递。对于箭头函数,你需要显式地传递它;对于 bind 方法,它会自动转发。
如何防止组件渲染?
你可以通过根据特定条件返回 null 来阻止组件渲染。这样就可以有条件地渲染组件。
function Greeting(props) {
if (!props.loggedIn) {
return null;
}
return <div className="greeting">welcome, {props.name}</div>;
}
class User extends React.Component {
constructor(props) {
super(props);
this.state = {loggedIn: false, name: 'John'};
}
render() {
return (
<div>
//Prevent component render if it is not loggedIn
<Greeting loggedIn={this.state.loggedIn} />
<UserDetails name={this.state.name}>
</div>
);
}
在上面的例子中,greeting 组件通过应用条件并返回空值来跳过其渲染部分。
安全地使用索引作为键的条件是什么?
有三个条件可以确保使用索引作为键是安全的。
- 列表和项目是静态的——它们不会被计算,也不会改变
- 列表中的项目没有 ID
- 该列表从未被重新排序或过滤。
密钥应该是全局唯一的吗?
数组中使用的键在其兄弟数组中应该是唯一的,但它们不需要是全局唯一的。即,您可以在两个不同的数组中使用相同的键。
例如,下面的书籍组件使用两个具有不同数组的数组,
function Book(props) {
const index = (
<ul>
{props.pages.map((page) => (
<li key={page.id}>{page.title}</li>
))}
</ul>
);
const content = props.pages.map((page) => (
<div key={page.id}>
<h3>{page.title}</h3>
<p>{page.content}</p>
<p>{page.pageNumber}</p>
</div>
));
return (
<div>
{index}
<hr />
{content}
</div>
);
}
表单处理的流行选择是什么?
Formik
是一个用于 React 的表单库,它提供验证、跟踪访问的字段和处理表单提交等解决方案。
具体来说,您可以将其分类如下,
- 获取表单状态中的值和从表单状态中获取值
- 验证和错误消息
- 处理表单提交
它用于创建一个可扩展、高性能的表单助手,并使用最少的 API 来解决烦人的问题。
formik 相对于 redux 表单库有哪些优势?
以下是推荐 formik 而不是 redux 表单库的主要原因,
- 表单状态本质上是短期和本地的,因此在 Redux(或任何类型的 Flux 库)中跟踪它是不必要的。
- Redux-Form 每次按键都会多次调用整个顶层 Redux Reducer。这会增加大型应用的输入延迟。
- Redux-Form 压缩后大小为 22.5 kB,而 Formik 压缩后大小为 12.7 kB
为什么不需要使用继承?
在 React 中,建议使用组合而不是继承来实现组件间的代码复用。Props 和组合都提供了所需的灵活性,让您能够以明确且安全的方式自定义组件的外观和行为。然而,如果您想在组件间复用非 UI 功能,建议将其提取到单独的 JavaScript 模块中。后续组件可以导入并使用该函数、对象或类,而无需对其进行扩展。
我可以在 React 应用程序中使用 Web 组件吗?
是的,你可以在 React 应用程序中使用 Web Components。虽然很多开发者不会使用这种组合,但当你使用基于 Web Components 编写的第三方 UI 组件时,可能需要这样做。
例如,让我们使用Vaadin
如下所示的日期选择器 Web 组件,
import React, { Component } from 'react';
import './App.css';
import '@vaadin/vaadin-date-picker';
class App extends Component {
render() {
return (
<div className="App">
<vaadin-date-picker label="When were you born?"></vaadin-date-picker>
</div>
);
}
}
export default App;
什么是动态导入?
动态 import() 语法是 ECMAScript 的一个提案,目前并非语言标准的一部分。预计不久的将来会被接受。您可以使用动态导入在应用中实现代码拆分。
让我们举一个加法的例子,
- 正常导入
import { add } from './math';
console.log(add(10, 20));
- 动态导入
import('./math').then((math) => {
console.log(math.add(10, 20));
});
什么是可加载组件?
如果要在服务器渲染的应用中进行代码拆分,建议使用可加载组件 (Loadable Components),因为 React.lazy 和 Suspense 尚不支持服务器端渲染。可加载组件 (Loadable Components) 允许你将动态导入的组件渲染为常规组件。
让我们举个例子,
import loadable from '@loadable/component';
const OtherComponent = loadable(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
现在 OtherComponent 将被加载到单独的 bundle 中
什么是悬念组件?
如果包含动态导入的模块在父组件渲染时尚未加载,则必须在等待其加载期间使用加载指示器显示一些后备内容。这可以使用Suspense组件来实现。
例如,下面的代码使用了 Suspense 组件,
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
如上面的代码所述,Suspense 被包裹在惰性组件之上。
什么是基于路由的代码拆分?
代码拆分的最佳位置之一是路由。整个页面将立即重新渲染,因此用户不太可能同时与页面中的其他元素进行交互。因此,用户体验不会受到影响。
让我们以使用 React Router 和 React.lazy 等库的基于路由的网站为例,
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
在上面的代码中,代码拆分将在每个路由级别发生。
举一个如何使用上下文的例子?
Context旨在共享可被视为React 组件树的全局数据。
例如,在下面的代码中,可以手动穿过“主题”道具来设置按钮组件的样式。
//Lets create a context with a default theme value "luna"
const ThemeContext = React.createContext('luna');
// Create App component where it uses provider to pass theme value in the tree
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="nova">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A middle component where you don't need to pass theme prop anymore
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// Lets read theme value in the button component to use
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
上下文中默认值的目的是什么?
defaultValue 参数仅当组件树中没有匹配的上级 Provider 时使用。这有助于在不包装组件的情况下单独测试组件。
下面的代码片段提供了默认主题值 Luna。
const MyContext = React.createContext(defaultValue);
如何使用 contextType?
ContextType 用于使用上下文对象。contextType 属性有两种使用方式:
- contextType 作为类的属性:类的 contextType 属性可以赋值给 React.createContext() 创建的 Context 对象。之后,你可以在任何生命周期方法和渲染函数中使用 this.context 来获取该 Context 类型最接近的当前值。
让我们在 MyClass 上分配 contextType 属性,如下所示,
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
- 静态字段您可以使用静态类字段通过公共类字段语法来初始化您的 contextType。
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
什么是消费者?
Consumer 是一个订阅上下文变化的 React 组件。它需要一个子函数作为子函数,该函数接收当前上下文值作为参数,并返回一个 React 节点。传递给该函数的 value 参数将等于树中距离此上下文最近的 Provider 的 value prop。
让我们举一个简单的例子,
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
在使用上下文时如何解决性能极端情况?
上下文使用参考标识来确定何时重新渲染,当提供商的父级重新渲染时,有些陷阱可能会触发消费者的无意渲染。
例如,下面的代码将在每次提供程序重新渲染时重新渲染所有消费者,因为总是为值创建一个新对象。
class App extends React.Component {
render() {
return (
<Provider value={{ something: 'something' }}>
<Toolbar />
</Provider>
);
}
}
这可以通过将值提升到父状态来解决,
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: { something: 'something' },
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
HOC 中的 forward ref 的用途是什么?
Refs 不会被传递,因为 ref 不是 prop。React 的处理方式与key不同。如果将 ref 添加到 HOC,则 ref 将引用最外层的容器组件,而不是被包装的组件。在这种情况下,可以使用 Forward Ref API。例如,我们可以使用 React.forwardRef API 将 ref 显式转发到内部的 FancyButton 组件。
下面的 HOC 记录了所有的 props,
```javascript
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
```
让我们使用这个 HOC 来记录传递给“花式按钮”组件的所有 props,
```javascript
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
export default logProps(FancyButton);
```
现在让我们创建一个 ref 并将其传递给 FancyButton 组件。这样,你就可以将焦点设置到按钮元素上了。
```javascript
import FancyButton from './FancyButton';
const ref = React.createRef();
ref.current.focus();
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
```
ref 参数是否适用于所有函数或类组件?
常规函数或类组件不会接收 ref 参数,并且 ref 在 props 中也不可用。第二个 ref 参数仅在使用 React.forwardRef 调用定义组件时才存在。
为什么在使用前向引用时需要额外注意组件库?
当您开始在组件库中使用 forwardRef 时,应将其视为重大变更,并发布库的新主要版本。这是因为您的库可能具有不同的行为,例如将 ref 赋值给哪些对象以及导出哪些类型。这些更改可能会破坏依赖旧行为的应用和其他库。
如何在没有 ES6 的情况下创建 React 类组件?
如果您不使用 ES6,则可能需要使用 create-react-class 模块。对于默认 props,您需要将 getDefaultProps() 定义为传入对象的函数。而对于初始状态,您必须提供一个单独的 getInitialState 方法来返回初始状态。
var Greeting = createReactClass({
getDefaultProps: function () {
return {
name: 'Jhohn',
};
},
getInitialState: function () {
return { message: this.props.message };
},
handleClick: function () {
console.log(this.state.message);
},
render: function () {
return <h1>Hello, {this.props.name}</h1>;
},
});
注意:如果您使用 createReactClass,则自动绑定可用于所有方法。即,您不需要.bind(this)
在事件处理程序的构造函数中使用。
不使用 JSX 可以使用 React 吗?
是的,使用 React 时 JSX 并非必需。实际上,当你不想在构建环境中设置编译时,它很方便。每个 JSX 元素只是调用 的语法糖React.createElement(component, props, ...children)
。
例如,让我们用 JSX 来举一个问候语的例子,
class Greeting extends React.Component {
render() {
return <div>Hello {this.props.message}</div>;
}
}
ReactDOM.render(<Greeting message="World" />, document.getElementById('root'));
您可以像下面这样编写不使用 JSX 的代码,
class Greeting extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.message}`);
}
}
ReactDOM.render(
React.createElement(Greeting, { message: 'World' }, null),
document.getElementById('root'),
);
什么是 diffing 算法?
React 需要使用算法来找出如何高效地更新 UI 以匹配最新的树。差异算法旨在生成将一棵树转换为另一棵树的最少操作数。然而,这些算法的复杂度约为 O(n3),其中 n 是树中元素的数量。
在这种情况下,显示 1000 个元素需要进行大约十亿次比较。这太昂贵了。因此,React 基于两个假设实现了启发式 O(n) 算法:
- 两种不同类型的元素将产生不同的树。
- 开发人员可以使用关键属性暗示哪些子元素可能在不同的渲染中保持稳定。
差异算法涵盖哪些规则?
在比较两棵树的差异时,React 首先比较两个根元素。根据根元素的类型,其行为会有所不同。在协调算法中,它涵盖了以下规则:
- 不同类型的元素:当根元素的类型不同时,React 会拆除旧树并从头开始构建新树。例如,如果元素到
,或 从 到 的类型不同,则会导致完全重建。
- 相同类型的 DOM 元素:当比较两个相同类型的 React DOM 元素时,React 会比较两者的属性,保留相同的底层 DOM 节点,并仅更新更改的属性。让我们举一个除了 className 属性之外其他属性相同的 DOM 元素的例子:
<div className="show" title="ReactJS" />
<div className="hide" title="ReactJS" />
- 相同类型的组件元素:当组件更新时,实例保持不变,因此状态在渲染过程中保持不变。React 会更新底层组件实例的 props 以匹配新元素,并在底层实例上调用 componentWillReceiveProps() 和 componentWillUpdate()。之后,会调用 render() 方法,diff 算法会针对先前的结果和新结果进行递归。
- 递归子节点:当对 DOM 节点的子节点进行递归时,React 会同时迭代两个子节点列表,并在出现差异时生成一个突变。例如,在子节点末尾添加一个元素时,在这两棵树之间进行转换效果很好。
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
- 处理键: React 支持 key 属性。当子节点拥有键时,React 会使用该键将原始树中的子节点与后续树中的子节点进行匹配。例如,添加键可以提高树的转换效率,
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
什么时候需要使用 ref?
参考用例很少,
- 管理焦点、文本选择或媒体播放。
- 触发命令式动画。
- 与第三方 DOM 库集成。
对于渲染 props,prop 是否必须命名为 render?
尽管该模式名为“render props”,但你不必使用名为 render 的 prop 来使用此模式。也就是说,任何组件用来判断需要渲染什么的函数 prop,从技术上来说都属于“render prop”。让我们以 children prop 为例,
<Mouse
children={(mouse) => (
<p>
The mouse position is {mouse.x}, {mouse.y}
</p>
)}
/>
实际上,children 属性不需要在 JSX 元素的“属性”列表中命名。相反,你可以直接将其放在元素内部,
<Mouse>
{(mouse) => (
<p>
The mouse position is {mouse.x}, {mouse.y}
</p>
)}
</Mouse>
在使用上述技术(没有任何名称)时,明确指出 children 应该是 propTypes 中的一个函数。
Mouse.propTypes = {
children: PropTypes.func.isRequired,
};
使用纯组件的渲染道具有什么问题?
如果在 render 方法中创建一个函数,则会违背纯组件的初衷。因为浅层 prop 比较对于新的 props 总是会返回 false,而在这种情况下,每次渲染都会为 render props 生成一个新值。你可以通过将 render 函数定义为实例方法来解决这个问题。
如何使用渲染道具创建 HOC?
你可以使用带有 render prop 的常规组件来实现大多数高阶组件 (HOC)。例如,如果你更喜欢 withMouse HOC 而不是组件,那么你可以轻松地使用带有 render prop 的常规组件来创建一个。
function withMouse(Component) {
return class extends React.Component {
render() {
return <Mouse render={(mouse) => <Component {...this.props} mouse={mouse} />} />;
}
};
}
通过这种方式,渲染道具可以灵活地使用任一模式。
什么是开窗技术?
窗口化是一种在给定时间内仅渲染一小部分行的技术,可以显著减少重新渲染组件所需的时间以及创建的 DOM 节点数量。如果您的应用程序需要渲染长列表数据,则建议使用此技术。react-window 和 react-virtualized 都是流行的窗口化库,它们提供了一些可复用的组件来显示列表、网格和表格数据。
如何在 JSX 中打印虚假值?
诸如 false、null、undefined 和 true 之类的虚假值是有效的子元素,但它们不会渲染任何内容。如果您仍然想显示它们,则需要将其转换为字符串。让我们举一个如何转换为字符串的例子,
<div>My JavaScript variable is {String(myVariable)}.</div>
门户的典型用例是什么?
当父组件具有溢出:隐藏或具有影响堆叠上下文(z-index、position、opacity 等样式)的属性并且您需要在视觉上“突破”其容器时,React 门户非常有用。
例如,对话框、全局消息通知、悬停卡和工具提示。
如何为不受控制的组件设置默认值?
在 React 中,表单元素的 value 属性会覆盖 DOM 中的值。对于非受控组件,你可能希望 React 指定初始值,但后续更新不受控制。为了处理这种情况,你可以指定defaultValue属性来代替value 属性。
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
User Name:
<input
defaultValue="John"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
同样适用于select
和textArea
输入。但是您需要对和输入使用defaultChecked 。checkbox
radio
你最喜欢的 React 堆栈是什么?
尽管不同开发者的技术栈各不相同,但 React Boilerplate 项目代码中使用的是最流行的技术栈。它主要使用 Redux 和 redux-saga 进行状态管理和异步副作用,使用 react-router 进行路由,使用 styled-components 进行 React 组件样式设置,使用 axios 调用 REST API,以及其他受支持的技术栈,例如 webpack、reselect、ESNext 和 Babel。您可以克隆项目https://github.com/react-boilerplate/react-boilerplate并开始开发任何新的 React 项目。
真实 DOM 和虚拟 DOM 有什么区别?
以下是真实 DOM 和虚拟 DOM 之间的主要区别,
真实 DOM | 虚拟 DOM |
---|---|
更新很慢 | 更新很快 |
DOM 操作非常昂贵。 | DOM 操作非常简单 |
您可以直接更新 HTML。 | 你不能直接更新 HTML |
造成过多的内存浪费 | 没有内存浪费 |
如果元素更新则创建一个新的 DOM | 如果元素更新,它会更新 JSX |
如何将 Bootstrap 添加到 React 应用程序?
可以通过三种方式将 Bootstrap 添加到你的 React 应用中,
- 使用 Bootstrap CDN:这是添加 Bootstrap 最简单的方法。在 head 标签中添加 Bootstrap 的 CSS 和 JS 资源。
- Bootstrap 作为依赖项:如果您正在使用构建工具或模块捆绑器(例如 Webpack),那么这是将 Bootstrap 添加到 React 应用程序的首选选项
npm install bootstrap
- React Bootstrap 包:在这种情况下,你可以使用一个已重建 Bootstrap 组件的包,将其添加到我们的 React 应用中,使其能够像 React 组件一样工作。以下是此类中比较流行的包:
- 反应引导
- 反应带
你能列出使用 React 作为前端框架的顶级网站或应用程序吗?
以下是top 10 websites
使用 React 作为前端框架的,
- 优步
- 可汗学院
- Airbnb
- Dropbox
- Netflix
- PayPal
建议在 React 中使用 CSS In JS 技术吗?
React 对于样式的定义方式没有任何意见,但如果你是初学者,那么一个好的起点是像往常一样在单独的 *.css 文件中定义样式,并使用 className 引用它们。此功能并非 React 的一部分,而是来自第三方库。但如果你想尝试不同的方法(CSS-In-JS),那么 styled-components 库是一个不错的选择。
我是否需要用钩子重写所有类组件?
不。但是你可以在一些组件(或新组件)中尝试使用 Hook,而无需重写任何现有代码。因为 ReactJS 中没有移除类的计划。
如何使用 React Hooks 获取数据?
调用的效果钩子useEffect
用于通过 axios 从 API 中获取数据,并使用状态钩子的更新功能将数据设置到组件的本地状态中。
让我们举一个例子,它从 API 中获取 React 文章列表
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios('http://hn.algolia.com/api/v1/search?query=react');
setData(result.data);
}, []);
return (
<ul>
{data.hits.map((item) => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
请记住,我们为效果钩子提供了一个空数组作为第二个参数,以避免在组件更新时激活它,而仅用于组件的安装。即,它仅为组件安装而获取。
Hooks 是否涵盖了类的所有用例?
Hooks 并未涵盖所有类的用例,但我们计划很快添加它们。目前,尚无与不常用的getSnapshotBeforeUpdate和componentDidCatch生命周期等效的 Hook 实现。
钩子支持的稳定版本是什么?
React 在 16.8 版本中为以下软件包提供了 React Hooks 的稳定实现
- React DOM
- React DOM 服务器
- React 测试渲染器
- React 浅渲染器
为什么我们在中使用数组解构(方括号符号)useState
?
当我们用 声明一个状态变量时useState
,它会返回一个包含两个元素的数组,即一个 pair 。第一个元素是当前值,第二个元素是更新该值的函数。使用 [0] 和 [1] 来访问它们有点令人困惑,因为它们有特定的含义。这就是为什么我们使用数组解构来代替。
例如,数组索引访问如下所示:
var userStateVariable = useState('userProfile'); // Returns an array pair
var user = userStateVariable[0]; // Access first item
var setUser = userStateVariable[1]; // Access second item
而通过数组解构,可以按如下方式访问变量:
const [user, setUser] = useState('userProfile');
引入钩子的来源有哪些?
Hooks 的灵感来源于多个不同的来源。以下是其中一些:
- 之前在 react-future 仓库中使用函数式 API 进行的实验
- 社区对 Reactions 组件等渲染属性 API 进行了实验
- DisplayScript 中的状态变量和状态单元。
- Rx 中的订阅。
- ReasonReact 中的 Reducer 组件。
如何访问 Web 组件的命令式 API?
Web 组件通常会公开命令式 API 来实现其功能。如果您想访问 Web 组件的命令式 API,则需要使用ref直接与 DOM 节点交互。但是,如果您使用的是第三方 Web 组件,那么最好的解决方案是编写一个 React 组件,将其作为Web 组件的包装器。
什么是 formik?
Formik 是一个小型的 React 表单库,可以帮你解决三大问题,
- 获取表单状态中的值和从表单状态中获取值
- 验证和错误消息
- 处理表单提交
在 Redux 中处理异步调用的典型中间件选择有哪些?
Redux 生态系统中用于处理异步调用的一些流行中间件选择是Redux Thunk, Redux Promise, Redux Saga
。
浏览器能理解 JSX 代码吗?
不,浏览器无法理解 JSX 代码。你需要一个转译器将 JSX 代码转换为浏览器可以理解的常规 JavaScript 代码。目前使用最广泛的转译器是 Babel。
描述一下 React 中的数据流?
React 使用 props 实现单向响应数据流,这减少了样板并且比传统的双向数据绑定更容易理解。
什么是反应脚本?
该react-scripts
软件包是 create-react-app 入门包中的一组脚本,可帮助您无需配置即可启动项目。该react-scripts start
命令设置开发环境并启动服务器,以及热模块重新加载。
create react app 有哪些功能?
以下是 create react app 提供的一些功能的列表。
- React、JSX、ES6、Typescript 和 Flow 语法支持。
- 自动添加前缀的 CSS
- CSS 重置/标准化
- 实时开发服务器
- 一个快速交互式单元测试运行器,内置覆盖率报告支持
- 一个用于捆绑 JS、CSS 和图像以供生产的构建脚本,其中包含哈希和源映射
- 离线优先服务工作者和 Web 应用程序清单,满足所有渐进式 Web 应用程序标准。
renderToNodeStream 方法的目的是什么?
该ReactDOMServer#renderToNodeStream
方法用于在服务器上生成 HTML,并在初始请求时将标记发送至服务器,以加快页面加载速度。它还能帮助搜索引擎轻松抓取您的页面,从而实现 SEO 目标。注意:请记住,此方法仅在服务器端可用,在浏览器中不可用。
什么是 MobX?
MobX 是一个简单、可扩展且久经考验的状态管理解决方案,适用于函数式响应式编程 (TFRP)。对于 ReactJS 应用程序,你需要安装以下软件包:
npm install mobx --save
npm install mobx-react --save
Redux 和 MobX 之间有什么区别?
以下是 Redux 和 MobX 之间的主要区别,
话题 | Redux | MobX |
---|---|---|
定义 | 它是一个用于管理应用程序状态的 JavaScript 库 | 它是一个用于被动管理应用程序状态的库 |
编程 | 它主要用 ES6 编写 | 它是用 JavaScript(ES5)编写的 |
数据存储 | 只有一个大型存储用于数据存储 | 有多个商店可供存储 |
用法 | 主要用于大型复杂的应用 | 用于简单的应用程序 |
表现 | 需要改进 | 提供更好的性能 |
如何存储 | 使用 JS 对象来存储 | 使用可观察对象来存储数据 |
在学习 ReactJS 之前我应该学习 ES6 吗?
不,你不必学习 ES2015/ES6 来学习 React。但你可能会发现许多资源或 React 生态系统广泛使用 ES6。让我们来看看一些常用的 ES6 特性,
- 解构:获取 props 并在组件中使用它们
// in es 5
var someData = this.props.someData;
var dispatch = this.props.dispatch;
// in es6
const { someData, dispatch } = this.props;
- 扩展运算符:帮助将 props 传递到组件中
// in es 5
<SomeComponent someData={this.props.someData} dispatch={this.props.dispatch} />
// in es6
<SomeComponent {...this.props} />
- 箭头函数:语法紧凑
// es 5
var users = usersList.map(function (user) {
return <li>{user.name}</li>;
});
// es 6
const users = usersList.map((user) => <li>{user.name}</li>);
什么是并发渲染?
并发渲染功能可在不阻塞主 UI 线程的情况下渲染组件树,从而提高 React 应用的响应速度。它允许 React 中断长时间运行的渲染以处理高优先级事件。也就是说,启用并发模式后,React 会密切关注其他需要完成的任务,如果有更高优先级的任务,它会暂停当前正在渲染的任务,并让其他任务优先完成。您可以通过两种方式启用此功能:
// 1. Part of an app by wrapping with ConcurrentMode
<React.unstable_ConcurrentMode>
<Something />
</React.unstable_ConcurrentMode>;
// 2. Whole app using createRoot
ReactDOM.unstable_createRoot(domNode).render(<App />);
异步模式和并发模式有什么区别?
两者指的是同一件事。之前,React 团队将并发模式称为“异步模式”。现在更名是为了突出 React 能够以不同的优先级执行任务的能力。这样可以避免与其他异步渲染方法混淆。
我可以在 react16.9 中使用 javascript url 吗?
是的,您可以使用 javascript: URL,但它会在控制台中记录警告。因为以 javascript: 开头的 URL 很危险,因为它们会在类似这样的标签中包含未经过滤的输出,<a href>
从而造成安全漏洞。
const companyProfile = {
website: "javascript: alert('Your website is hacked')",
};
// It will log a warning
<a href={companyProfile.website}>More details</a>;
请记住,未来的版本将会引发 javascript URL 错误。
eslint 插件的 hooks 用途是什么?
ESLint 插件强制执行 Hook 规则以避免 bug。它假定任何以 ”use” 开头且紧跟其后的大写字母的函数都是 Hook。具体来说,该规则强制执行:
- 对 Hooks 的调用要么在 PascalCase 函数(假定为组件)内,要么在另一个 useSomething 函数(假定为自定义 Hook)内。
- 每次渲染时都会按照相同的顺序调用钩子。
React 中的命令式和声明式有什么区别?
想象一下一个简单的 UI 组件,比如一个“赞”按钮。当你点击它时,如果它之前是灰色的,它会变成蓝色;如果它之前是蓝色的,它会变成灰色。
执行此操作的必要方式是:
if (user.likes()) {
if (hasBlue()) {
removeBlue();
addGrey();
} else {
removeGrey();
addBlue();
}
}
基本上,你必须检查屏幕上当前的内容,并处理所有必要的更改,以便根据当前状态重新绘制,包括撤消先前状态的更改。你可以想象这在实际场景中会多么复杂。
相反,声明式方法将是:
if (this.state.liked) {
return <blueLike />;
} else {
return <greyLike />;
}
因为声明式方法分离了关注点,所以这部分只需要处理 UI 在特定状态下的外观,因此更容易理解。
使用 typescript 和 reactjs 有什么好处?
以下是在 React.js 中使用 TypeScript 的一些好处,
- 可以使用最新的 JavaScript 功能
- 使用接口进行复杂类型定义
- VS Code 等 IDE 是为 TypeScript 开发的
- 通过易读性和验证来避免错误
使用 Context API 状态管理时,如何确保用户在页面刷新时仍保持身份验证?
当用户登录并重新加载时,为了保持状态,我们通常会在主 App.js 的 useEffect 钩子中添加加载用户操作。使用 Redux 时,可以轻松访问 loadUser 操作。
App.js
import { loadUser } from '../actions/auth';
store.dispatch(loadUser());
- 但是,在使用Context API访问 App.js 中的上下文时,需要将 AuthState 包装在 index.js 中,以便 App.js 可以访问身份验证上下文。现在,无论何时页面重新加载,无论您在哪个路由上,用户都将通过身份验证,因为每次重新渲染时都会触发loadUser操作。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import AuthState from './context/auth/AuthState';
ReactDOM.render(
<React.StrictMode>
<AuthState>
<App />
</AuthState>
</React.StrictMode>,
document.getElementById('root'),
);
App.js
const authContext = useContext(AuthContext);
const { loadUser } = authContext;
useEffect(() => {
loadUser();
}, []);
加载用户
const loadUser = async () => {
const token = sessionStorage.getItem('token');
if (!token) {
dispatch({
type: ERROR,
});
}
setAuthToken(token);
try {
const res = await axios('/api/auth');
dispatch({
type: USER_LOADED,
payload: res.data.data,
});
} catch (err) {
console.error(err);
}
};
新的 JSX 转换有哪些好处?
新的 JSX 转换有三大好处,
- 无需导入 React 包即可使用 JSX
- 编译后的输出可能会稍微改善包的大小
- 未来的改进提供了灵活性,可以减少学习 React 的概念数量。
新的 JSX 转换与旧的转换有何不同?
新的 JSX 转换不需要 React 在范围内。即,您不需要为简单的场景导入 React 包。
让我们举个例子来看看新旧变换之间的主要区别,
旧变换:
import React from 'react';
function App() {
return <h1>Good morning!!</h1>;
}
现在 JSX 转换将上述代码转换为常规 JavaScript,如下所示,
import React from 'react';
function App() {
return React.createElement('h1', null, 'Good morning!!');
}
新变换:
新的 JSX 转换不需要任何 React 导入
function App() {
return <h1>Good morning!!</h1>;
}
JSX 转换在底层编译为以下代码
import { jsx as _jsx } from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Good morning!!' });
}
注意:您仍然需要导入 React 才能使用 Hooks。
文章来源:https://dev.to/sakhnyuk/300-react-interview-questions-2ko4