semlinker/reactjs-interview-questions(新しいタブで開きます)
主要言語: JavaScript
List of top 304 ReactJS Interview Questions & Answers
- star 数
- 2,167
- watcher 数
- 67
- fork 数
- 307
- issue 数
- 2
- 最終更新
- 2021/12/27
主要言語: JavaScript
List of top 304 ReactJS Interview Questions & Answers
本项目的面试题来源于 sudheerj/reactjs-interview-questions 这个项目。一时兴起就动起了翻译的念头,由于本人的 React 功力尚浅,翻译的内容难免有误或不妥的地方,望请各位见谅。如果你喜欢这个项目,请 Star,更感谢你的 Pull Request。
以下是现阶段本项目的短期计划:
这里再次感谢 liaoyongfu 的大力支持🌹!
阅读更多关于 Angular、TypeScript、Node.js/Java 、Spring 等技术文章,欢迎访问我的个人博客或关注我的公众号 —— 全栈修仙之路
<<前端进阶篇 v1.1>> PDF 出炉了 — 「阿宝哥」,精心准备的 9 万多字 242 页的前端进阶资料。发布后 5 天内下载量为 3170。
点击此 全栈修仙之路 - 前端进阶篇 链接,即可免费下载电子书。
React 是一个开源前端 JavaScript 库,用于构建用户界面,尤其是单页应用程序。它用于处理网页和移动应用程序的视图层。React 是由 Facebook 的软件工程师 Jordan Walke 创建的。在 2011 年 React 应用首次被部署到 Facebook 的信息流中,之后于 2012 年被应用到 Instagram 上。
阅读资源:
React 的主要特性有:
JSX 是 ECMAScript 一个类似 XML 的语法扩展。基本上,它只是为 React.createElement() 函数提供语法糖,从而让在我们在 JavaScript 中,使用类 HTML 模板的语法,进行页面描述。
在下面的示例中,<h1> 内的文本标签会作为 JavaScript 函数返回给渲染函数。
class App extends React.Component { render() { return( <div> <h1>{'Welcome to React world!'}</h1> </div> ) } }
以上示例 render 方法中的 JSX 将会被转换为以下内容:
React.createElement("div", null, React.createElement( "h1", null, 'Welcome to React world!'));
这里你可以访问 Babeljs 在线体验一下。
阅读资源:
一个 Element 是一个简单的对象,它描述了你希望在屏幕上以 DOM 节点或其他组件的形式呈现的内容。Elements 在它们的属性中可以包含其他 Elements。创建一个 React 元素是很轻量的。一旦元素被创建后,它将不会被修改。
React Element 的对象表示如下:
const element = React.createElement( 'div', {id: 'login-btn'}, 'Login' )
上面的 React.createElement() 函数会返回一个对象。
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
最后使用 ReactDOM.render() 方法渲染到 DOM:
<div id='login-btn'>Login</div>
而一个组件可以用多种不同方式声明。它可以是一个含有 render() 方法的类。或者,在简单的情况中,它可以定义为函数。无论哪种情况,它都将 props 作为输入,并返回一个 JSX 树作为输出:
const Button = ({ onLogin }) => <div id={'login-btn'} onClick={onLogin} />
然后 JSX 被转换成 React.createElement() 函数:
const Button = ({ onLogin }) => React.createElement( 'div', { id: 'login-btn', onClick: onLogin }, 'Login' )
阅读资源:
有两种可行的方法来创建一个组件:
Function Components: 这是创建组件最简单的方式。这些是纯 JavaScript 函数,接受 props 对象作为第一个参数并返回 React 元素:
function Greeting({ message }) { return <h1>{`Hello, ${message}`}</h1> }
Class Components: 你还可以使用 ES6 类来定义组件。上面的函数组件若使用 ES6 的类可改写为:
class Greeting extends React.Component { render() { return <h1>{`Hello, ${this.props.message}`}</h1> } }
通过以上任意方式创建的组件,可以这样使用:
<Greeting message="semlinker"/>
在 React 内部对函数组件和类组件的处理方式是不一样的,如:
// 如果 Greeting 是一个函数 const result = Greeting(props); // <p>Hello</p> // 如果 Greeting 是一个类 const instance = new Greeting(props); // Greeting {} const result = instance.render(); // <p>Hello</p>
阅读资源:
如果组件需要使用状态或生命周期方法,那么使用类组件,否则使用函数组件。
React.PureComponent 与 React.Component 完全相同,只是它为你处理了 shouldComponentUpdate() 方法。当属性或状态发生变化时,PureComponent 将对属性和状态进行浅比较。另一方面,一般的组件不会将当前的属性和状态与新的属性和状态进行比较。因此,在默认情况下,每当调用 shouldComponentUpdate 时,默认返回 true,所以组件都将重新渲染。
组件的状态是一个对象,它包含某些信息,这些信息可能在组件的生命周期中发生更改。我们应该尽量使状态尽可能简单,并尽量减少有状态组件的数量。让我们创建一个包含消息状态的 User 组件:
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> ) } }
状态(State)与属性(Props)类似,但它是私有的,完全由组件控制。也就是说,除了它所属的组件外,任何组件都无法访问它。
Props 是组件的输入。它们是单个值或包含一组值的对象,这些值在创建时使用类似于 HTML 标记属性的命名约定传递给组件。它们是从父组件传递到子组件的数据。
Props 的主要目的是提供以下组件功能:
render() 方法中通过 this.props.reactProp 使用。例如,让我们使用 reactProp 属性创建一个元素:
<Element reactProp={'1'} />
然后,reactProp 将成为附加到 React props 对象的属性,该对象最初已存在于使用 React 库创建的所有组件上。
props.reactProp
state 和 props 都是普通的 JavaScript 对象。虽然它们都保存着影响渲染输出的信息,但它们在组件方面的功能不同。Props 以类似于函数参数的方式传递给组件,而状态则类似于在函数内声明变量并对它进行管理。
States vs Props
| Conditions | States | Props |
|---|---|---|
| 可从父组件接收初始值 | 是 | 是 |
| 可在父组件中改变其值 | 否 | 是 |
| 在组件内设置默认值 | 是 | 是 |
| 在组件内可改变 | 是 | 否 |
| 可作为子组件的初始值 | 是 | 是 |
如果你尝试直接改变状态,那么组件将不会重新渲染。
//Wrong this.state.message = 'Hello world'
正确方法应该是使用 setState() 方法。它调度组件状态对象的更新。当状态更改时,组件通将会重新渲染。
//Correct this.setState({ message: 'Hello World' })
注意: 你可以在 constructor 中或使用最新的 JavaScript 类属性声明语法直接设置状态对象。
当 setState 完成和组件渲染后,回调函数将会被调用。由于 setState() 是异步的,回调函数用于任何后续的操作。
注意: 建议使用生命周期方法而不是此回调函数。
setState({ name: 'John' }, () => console.log('The name has updated and component re-rendered'))
阅读资源:
<button onclick='activateLasers()'>
而在 React 中它遵循 camelCase (驼峰) 惯例:
<button onClick={activateLasers}>
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.') }
实现这一点有三种可能的方法:
class Component extends React.Componenet { 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>
注意: 如果回调函数作为属性传给子组件,那么这些组件可能触发一个额外的重新渲染。在这些情况下,考虑到性能,最好使用 .bind() 或 public class fields syntax 方案。
你可以使用箭头函数来包装事件处理器并传递参数:
<button onClick={() => this.handleClick(id)} />
这相当于调用 .bind:
<button onClick={this.handleClick.bind(this, id)} />
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> }
当然如果只是想判断 if,可以如下直接判断:
{
isLogin && <span>Your have been login!</span>
}
在上面的代码中,不需要使用isLogin ? <span>Your have been login!</span> : null这样的形式。
key 是一个特殊的字符串属性,你在创建元素数组时需要包含它。Keys 帮助 React 识别哪些项已更改、添加或删除。
我们通常使用数据中的 IDs 作为 keys:
const todoItems = todos.map((todo) => <li key={todo.id}> {todo.text} </li> )
在渲染列表项时,如果你没有稳定的 IDs,你可能会使用 index 作为 key:
const todoItems = todos.map((todo, index) => <li key={index}> {todo.text} </li> )
注意:
li 标签。key 属性,在控制台会显示警告消息。ref 用于返回对元素的引用。但在大多数情况下,应该避免使用它们。当你需要直接访问 DOM 元素或组件的实例时,它们可能非常有用。
这里有两种方案
React.createRef() 方法创建的,并通过 ref 属性添加到 React 元素上。为了在整个组件中使用refs,只需将 ref 分配给构造函数中的实例属性。class MyComponent extends React.Component { constructor(props) { super(props) this.myRef = React.createRef() } render() { return <div ref={this.myRef} /> } }
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} /> ); } }
你也可以在使用 closures 的函数组件中使用 refs。
注意: 你也可以使用内联引用回调,尽管这不是推荐的方法。
Ref forwarding 是一个特性,它允许一些组件获取接收到 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>
最好是使用 callback refs 而不是 findDOMNode() API。因为 findDOMNode() 阻碍了将来对 React 的某些改进。
使用 findDOMNode 已弃用的方案:
class MyComponent extends Component { componentDidMount() { findDOMNode(this).scrollIntoView() } render() { return <div /> } }
推荐的方案是:
class MyComponent extends Component { componentDidMount() { this.node.scrollIntoView() } render() { return <div ref={node => this.node = node} /> } }
如果你以前使用过 React,你可能会熟悉旧的 API,其中的 ref 属性是字符串,如 ref={'textInput'},并且 DOM 节点的访问方式为this.refs.textInput。我们建议不要这样做,因为字符串引用有以下问题,并且被认为是遗留问题。字符串 refs 在 React v16 版本中被移除。
它们强制 React 跟踪当前执行的组件。这是有问题的,因为它使 React 模块有状态,这会导致在 bundle 中复制 React 模块时会导致奇怪的错误。
它们是不可组合的 - 如果一个库把一个 ref 传给子元素,则用户无法对其设置另一个引用。
它们不能与静态分析工具一起使用,如 Flow。Flow 无法猜测出 this.refs 上的字符串引用的作用及其类型。Callback refs 对静态分析更友好。
使用 "render callback" 模式(比如: ),它无法像大多数人预期的那样工作。
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} /> } }
Virtual DOM (VDOM) 是 Real DOM 的内存表示形式。UI 的展示形式被保存在内存中并与真实的 DOM 同步。这是在调用的渲染函数和在屏幕上显示元素之间发生的一个步骤。整个过程被称为 reconciliation。
Real DOM vs Virtual DOM
| Real DOM | Virtual DOM |
|---|---|
| 更新较慢 | 更新较快 |
| 可以直接更新 HTML | 无法直接更新 HTML |
| 如果元素更新,则创建新的 DOM | 如果元素更新,则更新 JSX |
| DOM 操作非常昂贵 | DOM 操作非常简单 |
| 较多的内存浪费 | 没有内存浪费 |
阅读资源:
Virtual DOM 分为三个简单的步骤。
Shadow DOM 是一种浏览器技术,它解决了构建网络应用的脆弱性问题。Shadow DOM 修复了 CSS 和 DOM。它在网络平台中引入作用域样式。 无需工具或命名约定,你即可使用原生 JavaScript 捆绑 CSS 和标记、隐藏实现详情以及编写独立的组件。Virtual DOM 是一个由 JavaScript 库在浏览器 API 之上实现的概念。
Fiber 是 React v16 中新的 reconciliation 引擎,或核心算法的重新实现。React Fiber 的目标是提高对动画,布局,手势,暂停,中止或者重用任务的能力及为不同类型的更新分配优先级,及新的并发原语等领域的适用性。
React Fiber 的目标是提高其在动画、布局和手势等领域的适用性。它的主要特性是 incremental rendering: 将渲染任务拆分为小的任务块并将任务分配到多个帧上的能力。
在随后的用户输入中,能够控制表单中输入元素的组件被称为受控组件,即每个状态更改都有一个相关联的处理程序。
例如,我们使用下面的 handleChange 函数将输入框的值转换成大写:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}) }
非受控组件是在内部存储其自身状态的组件,当需要时,可以使用 ref 查询 DOM 并查找其当前值。这有点像传统的 HTML。
在下面的 UserProfile 组件中,我们通过 ref 引用 name 输入框:
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> ); } }
在大多数情况下,建议使用受控组件来实现表单。
JSX 元素将被转换为 React.createElement() 函数来创建 React 元素,这些对象将用于表示 UI 对象。而 cloneElement 用于克隆元素并传递新的属性。
当多个组件需要共享相同的更改数据时,建议将共享状态提升到最接近的共同祖先。这意味着,如果两个子组件共享来自其父组件的相同数据,则将状态移动到父组件,而不是在两个子组件中维护局部状态。
组件生命周期有三个不同的生命周期阶段:
Mounting: 组件已准备好挂载到浏览器的 DOM 中. 此阶段包含来自 constructor(), getDerivedStateFromProps(), render(), 和 componentDidMount() 生命周期方法中的初始化过程。
Updating: 在此阶段,组件以两种方式更新,发送新的属性并使用 setState() 或 forceUpdate() 方法更新状态. 此阶段包含 getDerivedStateFromProps(), shouldComponentUpdate(), render(), getSnapshotBeforeUpdate() 和 componentDidUpdate() 生命周期方法。
Unmounting: 在这个最后阶段,不需要组件,它将从浏览器 DOM 中卸载。这个阶段包含 componentWillUnmount() 生命周期方法。
值得一提的是,在将更改应用到 DOM 时,React 内部也有阶段概念。它们按如下方式分隔开:
Render 组件将会进行无副作用渲染。这适用于纯组件(Pure Component),在此阶段,React 可以暂停,中止或重新渲染。
Pre-commit 在组件实际将更改应用于 DOM 之前,有一个时刻允许 React 通过getSnapshotBeforeUpdate()捕获一些 DOM 信息(例如滚动位置)。
Commit React 操作 DOM 并分别执行最后的生命周期: componentDidMount() 在 DOM 渲染完成后调用, componentDidUpdate() 在组件更新时调用, componentWillUnmount() 在组件卸载时调用。
React 16.3+ 阶段 (也可以看交互式版本)
React 16.3 之前
React 16.3+
render()之前调用,并在 每次 渲染时调用。 需要使用派生状态的情况是很罕见得。值得阅读 如果你需要派生状态.true。 如果你确定在更新状态或属性后不需要渲染组件,则可以返回false值。 它是一个提高性能的好地方,因为它允许你在组件接收新属性时阻止重新渲染。shouldComponentUpdate()返回false,则不会触发。Before 16.3
render()前执行,用于根组件中的应用程序级别配置。应该避免在该方法中引入任何的副作用或订阅。this.props和nextProps并在该方法中使用this.setState()处理状态改变。true。 如果你确定在更新状态或属性后不需要渲染组件,则可以返回false值。 它是一个提高性能的好地方,因为它允许你在组件接收新属性时阻止重新渲染。shouldComponentUpdate返回true后重新渲染组件之前执行,注意你不能在这调用this.setState()shouldComponentUpdate()返回false,则不会触发。高阶组件(HOC) 就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种模式,这种模式是由react自身的组合性质必然产生的。
我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent)
HOC 有很多用例:
state)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主题等。
// 创建一个 theme Context, 默认 theme 的值为 light const ThemeContext = React.createContext('light'); function ThemedButton(props) { // ThemedButton 组件从 context 接收 theme return ( <ThemeContext.Consumer> {theme => <Button {...props} theme={theme} />} </ThemeContext.Consumer> ); } // 中间组件 function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } }
Children 是一个属性(this.props.chldren),它允许你将组件作为数据传递给其他组件,就像你使用的任何其他组件一样。在组件的开始和结束标记之间放置的组件树将作为children属性传递给该组件。
React API 中有许多方法中提供了这个不透明数据结构的方法,包括:React.Children.map、React.Children.forEach、React.Children.count、React.Children.only、React.Children.toArray。
const MyDiv = React.createClass({ render: function() { return <div>{this.props.children}</div> } }) ReactDOM.render( <MyDiv> <span>{'Hello'}</span> <span>{'World'}</span> </MyDiv>, node )
React/JSX 中的注释类似于 JavaScript 的多行注释,但是是用大括号括起来。
单行注释:
<div> {/* 单行注释(在原生 JavaScript 中,单行注释用双斜杠(//)表示) */} {`Welcome ${user}, let's play React`} </div>
多行注释:
<div> {/* 多行注释超过 一行 */} {`Welcome ${user}, let's play React`} </div>
在调用super()方法之前,子类构造函数不能使用this引用。这同样适用于ES6子类。将props参数传递给super()的主要原因是为了在子构造函数中访问this.props。
带 props 参数:
class MyComponent extends React.Component { constructor(props) { super(props) console.log(this.props) // prints { name: 'John', age: 42 } } }
不带 props 参数:
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> }
这是必要的,因为组件不是 DOM 元素,它们是构造函数。 此外,在 JSX 中,小写标记名称是指 HTML 元素,而不是组件。
class 是 JavaScript 中的关键字,而 JSX 是 JavaScript 的扩展。这就是为什么 React 使用 className 而不是 class 的主要原因。传递一个字符串作为 className 属性。
render() { return <span className={'menu navigation-menu'}>{'Menu'}</span> }
在实际项目中,我们经常使用classnames来方便我们操作className。
它是 React 中的常见模式,用于组件返回多个元素。Fragments 可以让你聚合一个子元素列表,而无需向 DOM 添加额外节点。
render() { return ( <React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> ) }
以下是简洁语法,但是在一些工具中还不支持:
render() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ) }
译注:
React 16以前,render函数的返回必须有一个根节点,否则报错。
Portal 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
ReactDOM.createPortal(child, container)
第一个参数是任何可渲染的 React 子节点,例如元素,字符串或片段。第二个参数是 DOM 元素。
如果行为独立于其状态,则它可以是无状态组件。你可以使用函数或类来创建无状态组件。但除非你需要在组件中使用生命周期钩子,否则你应该选择函数组件。无状态组件有很多好处: 它们易于编写,理解和测试,速度更快,而且你可以完全避免使用this关键字。
如果组件的行为依赖于组件的state,那么它可以被称为有状态组件。这些有状态组件总是类组件,并且具有在constructor中初始化的状态。
class App extends Component { constructor(props) { super(props) this.state = { count: 0 } } render() { // ... } }
当应用程序以开发模式运行的时,React 将会自动检查我们在组件上设置的所有属性,以确保它们具有正确的类型。如果类型不正确,React 将在控制台中生成警告信息。由于性能影响,它在生产模式下被禁用。使用 isRequired 定义必填属性。
预定义的 prop 类型:
PropTypes.numberPropTypes.stringPropTypes.arrayPropTypes.objectPropTypes.funcPropTypes.nodePropTypes.elementPropTypes.boolPropTypes.symbolPropTypes.any我们可以为 User 组件定义 propTypes,如下所示:
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 中,PropTypes 从 React.PropTypes 被移动到 prop-types 库中。
错误边界是在其子组件树中的任何位置捕获 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 a