【前端面试题】—30道常见React基础面试题(附答案)

web前端开发

共 12655字,需浏览 26分钟

 · 2021-03-30

当今最流行的框架非 React莫属。 React以其岀色的性能,颠覆了互联网的理念,简单的开发方式受到许多开发者的青睐。
因此,在 React中,虚拟DOM、组件的生命周期、组件的通信、组件的约束性,配合 Reflux、 Redux等框架的使用,基于 EMAScript6语法开发,以及 Webpack编译等都是读者要掌握的内容。
当然, React的三大特色(虚拟DOM、组件开发、多端适配)的具体实现,开发者也要有所了解。
1、当调用 setState的时候,发生了什么操作?
当调用 setState时, React做的第一件事是将传递给setState的对象合并到组件的当前状态,这将启动一个称为和解( reconciliation)的过程。
和解的最终目标是,根据这个新的状态以最有效的方式更新DOM。
为此, React将构建一个新的 React虚拟DOM树(可以将其视为页面DOM元素的对象表示方式)。
一旦有了这个DOM树,为了弄清DOM是如何响应新的状态而改变的, React会将这个新树与上一个虚拟DOM树比较。
这样做, React会知道发生的确切变化,并且通过了解发生的变化后,在绝对必要的情况下进行更新DOM,即可将因操作DOM而占用的空间最小化。
2、在 React中元素( element)和组件( component)有什么区别?
简单地说,在 React中元素(虛拟DOM)描述了你在屏幕上看到的DOM元素。
换个说法就是,在 React中元素是页面中DOM元素的对象表示方式。在 React中组件是一个函数或一个类,它可以接受输入并返回一个元素。
注意:工作中,为了提高开发效率,通常使用JSX语法表示 React元素(虚拟DOM)。在编译的时候,把它转化成一个 React. createElement调用方法。

3、什么时候使用类组件( Class Component)?什么时候使用功能组件(Functional Component)?

如果组件具有状态( state)或生命周期方法,请使用类组件;否则,使用功能组件。

4、什么是 React的refs?为什么它们很重要?

refs允许你直接访问DOM元素或组件实例。为了使用它们,可以向组件添加个ref属性。

如果该属性的值是一个回调函数,它将接受底层的DOM元素或组件的已挂载实例作为其第一个参数。可以在组件中存储它。

export class App extends Component  {showResult ( ) {console. log(this. input. value)}render ( ) {return (<div><input  type="text" ref={input => this .input =input } />< button onClick={this. showResult.bindthis)}>展示结果</ button></div>);}}

如果该属性值是一个字符串, React将会在组件实例化对象的refs属性中,存储一个同名属性,该属性是对这个DOM元素的引用。可以通过原生的 DOM API操作它。

export class App extends Component { showResult( )console .log ( this.refs.username.value)render ( ){  return (<div><input type="text" ref="username"/>< button onClick={this. showResu1t.bind (this)}>展示结果</ button></div>);}}

5、React 中的key是什么?为什么它们很重要?

key可以帮助 React跟踪循环创建列表中的虚拟DOM元素,了解哪些元素已更改、添加或删除。

每个绑定key的虚拟DOM元素,在兄弟元素之间都是独一无二的。在 React的和解过程中,比较新的虛拟DOM树与上一个虛拟DOM树之间的差异,并映射到页面中。key使 React处理列表中虛拟DOM时更加高效,因为 React可以使用虛拟DOM上的key属性,快速了解元素是新的、需要删除的,还是修改过的。如果没有key,Rεat就不知道列表中虚拟DOM元素与页面中的哪个元素相对应。所以在创建列表的时候,不要忽略key。

6、如果创建了类似于下面的 Icketang元素,那么该如何实现 Icketang类?

< Icketang username="雨夜清荷">{user = > user ?<Info user={user} />:<Loading />}</Icketang>import React, { Component } fromr "react" export class Icketang extends Component {//请实现你的代码}

在上面的案例中,一个组件接受一个函数作为它的子组件。Icketang组件的子组件是一个函数,而不是一个常用的组件。这意味着在实现 Icketang组件时,需要将props. children作为一个函数来处理。

具体实现如下。

import React,  { Component } from "react"class Icketang extends Component { constructor ( props ){  super ( props ) this .state = { user : props.user }}componentDidMount( ) {//模拟异步获取数据操作,更新状态setTimeout ( ( ) => this .setstate ({user:'有课前端网'}),2000}render ( ) {return this.props.children ( this .state.user )}}class Loading extends Component { render  ( ) { return <p>Loading.</p>}}class Info extends Component  { render ( ) { return <hl> { this .props.user }</h1>}}

调用 Icketang组件,并传递给user属性数据,把 props.children作为一个函数来处理。这种模式的好处是,我们已经将父组件与子组件分离了,父组件管理状态。父组件的使用者可以决定父组件以何种形式渲染子组件。

为了演示这一点,在渲染 Icketang组件时,分别传递和不传递user属性数据来观察渲染结果。

import {render} from  "react-dom"; render (<Icketang>{ user = > user ?<Info user = {user} /><Loading /> }</Icketang> , ickt)

上述代码没有为 Icketang组件传递user属性数据,因此将首先渲染 Loading组件,当父组件的user状态数据发生改变时,我们发现Info组件可以成功地渲染出来。

render(< Icketang user="雨夜清荷">{ user => user ?<Info user = {user} /><Loading />}</Icketang>, ickt)

上述代码为 Icketang组件传递了user属性数据,因此将直接渲染Info组件,当父组件的user状态数据发生改变时,我们发现Info组件产生了更新,在整个过程中, Loading组件都未渲染。

7、约束性组件( controlled component)与非约束性组件( uncontrolled  component)有什么区别?

在 React中,组件负责控制和管理自己的状态。

如果将HTML中的表单元素( input、 select、 textarea等)添加到组件中,当用户与表单发生交互时,就涉及表单数据存储问题。根据表单数据的存储位置,将组件分成约東性组件和非约東性组件。

约束性组件( controlled component)就是由 React控制的组件,也就是说,表单元素的数据存储在组件内部的状态中,表单到底呈现什么由组件决定。

如下所示, username没有存储在DOM元素内,而是存储在组件的状态中。每次要更新 username时,就要调用 setState更新状态;每次要获取 username的值,就要获取组件状态值。

class App extends Component {//初始化状态constructor ( props ) { super ( props )this .state = { username:'有课前端网'}}//查看结果showResult ( ) {//获取数据就是获取状态值console. log ( this .state. username )}changeUsername (e) {//原生方法获取var value =e .target .value//更新前,可以进行脏值检测//更新状态this .setState ( {tusername:value} )}//渲染组件render( ) {//返回虚拟DOM return (<div><p>{/*输入框绑定va1ue*/}<input type="text" onChange={ this.changeUsername .bind (this ) }value= { this .state.username }/></p><p>< button onClick={this.showResult.bind (this)}>查看结果</ button></p></div>)}}

非约束性组件( uncontrolled component)就是指表单元素的数据交由元素自身存储并处理,而不是通过 React组件。表单如何呈现由表单元素自身决定。

如下所示,表单的值并没有存储在组件的状态中,而是存储在表单元素中,当要修改表单数据时,直接输入表单即可。有时也可以获取元素,再手动修改它的值。当要获取表单数据时,要首先获取表单元素,然后通过表单元素获取元素的值。

注意:为了方便在组件中获取表单元素,通常为元素设置ref属性,在组件内部通过refs属性获取对应的DOM元素。

class App extends Component {//查看结果showResult ( ) {//获取值console. log(this. refs. username .value)//修改值,就是修改元素自身的值this.refs.username.value="专业前端学习平台"//渲染组件render ( ) {//返回虚拟DOM return (<div><p>{/*非约束性组件中,表单元素通过 defaultvalue定义*/}< input type="text"  ref=" username"  defaultvalue="有课前端网"/></p><p>< button onClick={this. showResult.bind ( this ) }>查看结果</button></p></div> )  }   }
虽然非约東性组件通常更容易实现,可以通过refs直接获取DOM元素,并获取其值,但是 React建议使用约束性组件。主要原因是,约東性组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式等。
8、在哪个生命周期中你会发出Ajax请求?为什么?
Ajax请求应该写在组件创建期的第五个阶段,即 componentDidMount生命周期方法中。原因如下。
在创建期的其他阶段,组件尚未渲染完成。而在存在期的5个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀Ajax请求显然不是最好的选择。
在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件。
9、shouldComponentUpdate有什么用?为什么它很重要?
组件状态数据或者属性数据发生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期方法 should ComponentUpdate中,允许选择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是根据新的状态,以最有效的方式更新用户界面。如果我们知道用户界面的某一部分不会改变,那么没有理由让 React弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate方法中返回 false, React将让当前组件及其所有子组件保持与当前组件状态相同。
10、如何用 React构建( build)生产模式?

通常,使用 Webpack的 DefinePlugin方法将 NODE ENV设置为 production。这将剥离 propType验证和额外的警告。除此之外,还可以减少代码,因为 React使用 Uglify的dead-code来消除开发代码和注释,这将大大减少包占用的空间。

11、为什么要使用 React. Children. map( props. children,( )=>)而不是props. children. map ( (  ) => )?

因为不能保证 props. children将是一个数组。

以下面的代码为例。

<Parent><h1>有课前端网</h1></Parent>

在父组件内部,如果尝试使用 props.children. map映射子对象,则会抛出错误,因为props. children是一个对象,而不是一个数组。

如果有多个子元素, React会使 props.children成为一个数组,如下所示。

<Parent><h1>有课前端网</h1><h2>前端技术学习平台</h2></Parent>不建议使用如下方式,在这个案例中会抛出错误。class Parent extends Component { render ( ) { return (<div> { this .props.children.map (obj = > obj ) }</div>)} }

建议使用如下方式,避免在上一个案例中抛出错误。

class Parent extends Component  {render ( ) {  return (<div> { React.Children.map ( this .props.children, obj => obj) }</div>)}}

12、描述事件在 React中的处理方式。

为了解决跨浏览器兼容性问题, React中的事件处理程序将传递 SyntheticEvent的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent与你习惯的原生事件具有相同的接口,它们在所有浏览器中都兼容。

React实际上并没有将事件附加到子节点本身。而是通过事件委托模式,使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的。这也意味着在更新DOM时, React不需要担心跟踪事件监听器。

13、createElement和 cloneElement有什么区别?

createElement是JSX被转载得到的,在 React中用来创建 React元素(即虚拟DOM)的内容。cloneElement用于复制元素并传递新的 props。

14、setState方法的第二个参数有什么用?使用它的目的是什么?

它是一个回调函数,当 setState方法执行结束并重新渲染该组件时调用它。在工作中,更好的方式是使用 React组件生命周期之——“存在期”的生命周期方法,而不是依赖这个回调函数。

export class App extends Component {constructor (props) { super ( props )this.state = {username:"雨夜清荷"}}render ( ) {return (<div> { this .state. username) </div>);}componentDidMount ( ) {this .setstate ( {  username :'有课前端网'},( ) => console. log ( 're-rendered success. ' ) )

15、这段代码有什么问题?

class App extends Component { constructor ( props )  {super ( props )this .state = {username:"有课前端网"msg:' '}}render ( ) {return (<div> { this .state. msg }</div>);}componentDidMount ( )  { this .setState ( ( oldState, props ) => {return {msg:oldState .username + ' - ' + props.intro }} )}

render ( < App intro=" 前端技术专业学习平台"></App>,ickt )

在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React允许对 setState方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。

16、请说岀 React从 EMAScript5编程规范到 EMAScript6编程规范过程中的几点改变。

主要改变如下。

(1)创建组件的方法不同。

EMAScript5版本中,定义组件用 React.createClass。EMAScript6版本中,定义组件要定义组件类,并继承 Component类。

(2)定义默认属性的方法不同。

EMAScript5版本中,用 getDefaultProps定义默认属性。EMAScript6版本中,为组件定义 defaultProps静态属性,来定义默认属性。

(3)定义初始化状态的方法不同。EMAScript5版本中,用 getInitialState定义初始化状态。EMAScript6版本中,在构造函数中,通过this. state定义初始化状态。

注意:构造函数的第一个参数是属性数据,一定要用 super继承。

(4)定义属性约束的方法不同。

EMAScript5版本中,用 propTypes定义属性的约束。

EMAScript6版本中,为组件定义 propsTypes静态属性,来对属性进行约束。

(5)使用混合对象、混合类的方法不同。

EMAScript5版本中,通过mixins继承混合对象的方法。

EMAScript6版本中,定义混合类,让混合类继承 Component类,然后让组件类继承混合类,实现对混合类方法的继承。

(6)绑定事件的方法不同。

EMAScript5版本中,绑定的事件回调函数作用域是组件实例化对象。

EMAScript6版本中,绑定的事件回调函数作用域是null。

(7)父组件传递方法的作用域不同。

EMAScript5版本中,作用域是父组件。 EMAScript6版本中,变成了null。

(8)组件方法作用域的修改方法不同。

EMAScript5版本中,无法改变作用域。

EMAScript6版本中,作用域是可以改变的。

17、React中D算法的原理是什么?

原理如下。

(1)节点之间的比较。

节点包括两种类型:一种是 React组件,另一种是HTML的DOM。

如果节点类型不同,按以下方式比较。

如果 HTML DOM不同,直接使用新的替换旧的。如果组件类型不同,也直接使用新的替换旧的。

如果 HTML DOM类型相同,按以下方式比较。

在 React里样式并不是一个纯粹的字符串,而是一个对象,这样在样式发生改变时,只需要改变替换变化以后的样式。修改完当前节点之后,递归处理该节点的子节点。

如果组件类型相同,按以下方式比较。

如果组件类型相同,使用 React机制处理。一般使用新的 props替换旧的 props,并在之后调用组件的 componentWillReceiveProps方法,之前组件的 render方法会被调用。

节点的比较机制开始递归作用于它的子节点。

(2)两个列表之间的比较。

一个节点列表中的一个节点发生改变, React无法很妤地处理这个问题。循环新旧两个列表,并找出不同,这是 React唯一的处理方法。

但是,有一个办法可以把这个算法的复杂度降低。那就是在生成一个节点列表时给每个节点上添加一个key。这个key只需要在这一个节点列表中唯一,不需要全局唯一。

(3)取舍

需要注意的是,上面的启发式算法基于两点假设。

类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树

可以为多次 render都表现稳定的节点设置key。

上面的节点之间的比较算法基本上就是基于这两个假设而实现的。要提高 React应用的效率,需要按照这两点假设来开发。

18、概述一下 React中的事件处理逻辑。

为了解决跨浏览器兼容性问题, React会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。

这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外, React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理(基于事件委托原理)。

这样 React在更新DOM时就不需要考虑如何处理附着在DOM上的事件监听器,最终达到优化性能的目的。

19、传入 setstate函数的第二个参数的作用是什么?

第二个参数是一个函数,该函数会在 setState函数调用完成并且组件开始重渲染时调用,可以用该函数来监听渲染是否完成。

this .setstate ({ username:'有课前端网'}, ( ) => console.log ( 're-rendered success. ' ) )

20、React和vue.js的相似性和差异性是什么?

相似性如下。

(1)都是用于创建UI的 JavaScript库。

(2)都是快速和轻量级的代码库(这里指 React核心库)。

(3)都有基于组件的架构。

(4)都使用虚拟DOM。

(5)都可以放在单独的HTML文件中,或者放在 Webpack设置的一个更复杂的模块中。

(6)都有独立但常用的路由器和状态管理库。

它们最大的区别在于 Vue. js通常使用HTML模板文件,而 React完全使用 JavaScript创建虚拟DOM。 Vue. js还具有对于“可变状态”的“ reactivity”的重新渲染的自动化检测系统。

21、生命周期调用方法的顺序是什么?

React生命周期分为三大周期,11个阶段,生命周期方法调用顺序分别如下。

(1)在创建期的五大阶段,调用方法的顺序如下。

  • getDetaultProps:定义默认属性数据。

  • getInitialState:初始化默认状态数据。

  • component WillMount:组件即将被构建。

  • render:渲染组件。

  • componentDidMount:组件构建完成

(2)在存在期的五大阶段,调用方法的顺序如下。

  • componentWillReceiveProps:组件即将接收新的属性数据。

  • shouldComponentUpdate:判断组件是否应该更新。

  • componnent WillUpdate:组件即将更新。

  • render:渲染组件。

  • componentDidUpdate:组件更新完成。

(3)在销毁期的一个阶段,调用方法 componentWillUnmount,表示组件即将被销毀。

22、使用状态要注意哪些事情?

要注意以下几点。

  • 不要直接更新状态

  • 状态更新可能是异步的

  • 状态更新要合并。

  • 数据从上向下流动

23、说说 React组件开发中关于作用域的常见问题。

在 EMAScript5语法规范中,关于作用域的常见问题如下。

(1)在map等方法的回调函数中,要绑定作用域this(通过bind方法)。

(2)父组件传递给子组件方法的作用域是父组件实例化对象,无法改变。

(3)组件事件回调函数方法的作用域是组件实例化对象(绑定父组件提供的方法就是父组件实例化对象),无法改变。

在 EMAScript6语法规范中,关于作用域的常见问题如下。

(1)当使用箭头函数作为map等方法的回调函数时,箭头函数的作用域是当前组件的实例化对象(即箭头函数的作用域是定义时的作用域),无须绑定作用域。

(2)事件回调函数要绑定组件作用域。

(3)父组件传递方法要绑定父组件作用域。

总之,在 EMAScript6语法规范中,组件方法的作用域是可以改变的。

24、在 Redux中使用 Action要注意哪些问题?

在Redux中使用 Action的时候, Action文件里尽量保持 Action文件的纯净,传入什么数据就返回什么数据,最妤把请求的数据和 Action方法分离开,以保持 Action的纯净。

25、在 Reducer文件里,对于返回的结果,要注意哪些问题?

在 Reducer文件里,对于返回的结果,必须要使用 Object.assign ( )来复制一份新的 state,否则页面不会跟着数据刷新。

return Object. assign ( { }, state, {type:action .type,shouldNotPaint : true})

26、如何使用4.0版本的 React Router?

React Router 4.0版本中对 hashHistory做了迁移,执行包安装命令 npm install react-router-dom后,按照如下代码进行使用即可。

import  { HashRouter, Route, Redirect, Switch  } from " react-router-dom"class App extends Component {render ( ) {return (<div><Switch><Route path="/list"  componen t= { List }></Route><Route path="/detail/:id" component= { Detail } > </Route>
<Redirect from="/ " to="/list"> </Redirect>
</Switch>
</div>
)
}
}
const routes = (
<HashRouter>
<App> </App>
</HashRouter>
)
render(routes, ickt);

27、在 ReactNative中,如何解决8081端口号被占用而提示无法访问的问题?

在运行 react-native start时添加参数port 8082;在 package.json中修改“scripts”中的参数,添加端口号;修改项目下的 node_modules \react-native\local- cli\server\server.js文件配置中的 default端口值。

28、在 ReactNative中,如何解决 adb devices找不到连接设备的问题?

在使用 Genymotion时,首先需要在SDK的 platform-tools中加入环境变量,然后在 Genymotion中单击 Setting,选择ADB选项卡,单击 Use custom Android SDK tools,浏览本地SDK的位置,单击OK按钮就可以了。启动虛拟机后,在cmd中输入 adb devices可以查看设备。

29、React- Router有几种形式?

有以下几种形式。

HashRouter,通过散列实现,路由要带#。

BrowerRouter,利用HTML5中 history API实现,需要服务器端支持,兼容性不是很好。

30、在使用 React Router时,如何获取当前页面的路由或浏览器中地址栏中的地址?

在当前组件的 props中,包含 location属性对象,包含当前页面路由地址信息,在 match中存储当前路由的参数等数据信息。可以直接通过 this .props使用它们。

学习更多技能

请点击下方web前端开发



浏览 158
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报