前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React面试题精选

React面试题精选

作者头像
疯狂的技术宅
发布2019-03-27 16:23:38
2.7K0
发布2019-03-27 16:23:38
举报
文章被收录于专栏:京程一灯京程一灯

实际上, 想要去了解某人对React的理解程度,仅凭这些面试题或许远远不够。 react面试题 这篇文章更应该取名为关于react你不是非知不可的东西,但如果了解了的话总是有好处的。


当你调用setState的时候实际发生了什么?

当你调用setState这个方法,React会做的第一件事就是把你传递给setState的参数对象合并到组件原先的state。这个事件会导致一个“reconciliation”(调和)的过程。reconciliation的最终目标就是,尽可能以最高效的方法,去基于新的state来更新UI。为了达到这个目的,React会构建一个React元素树(你可以把这个想象成一个表示UI的一个对象)。一旦这个树构建完毕,React为了根据新的state去决定UI要怎么进行改变,它会找出这棵新树和旧树的不同之处。React能够相对精确地找出哪些位置发生了改变以及如何发生了什么变化,并且知道如何只通过必要的更新来最小化重渲染。


React元素(Element) 和 React组件(Component)之间的区别 ?

简而言之, React的element可以看作是你在屏幕想看到的东西。高级的说法就是UI的对象表述。

一个React组件是可以接受参数并且返回一个react element的函数或者类(通常通过JSX来触发createElement这个方法)

想了解更多,可以查看这篇文章-> React Elements vs React Components


什么时候使用 Class Component 而非 Functional Component?

如果你的组件有state或者使用了生命周期函数,那么请使用Class component。 否则,使用Functional component。


refs 是什么,还有为什么它很重要?

Refs是你访问DOM元素或者组件实例的一个安全门。为了使用它们,你可以在组件加上一个ref属性,ref的值是一个回调函数,这个回调函数接受底层的DOM元素或者被挂载的组件实例作为它的第一个参数。

代码语言:javascript
复制
class UnControlledForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          ref={(input) => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

从上述所知,我们的输入框有一个ref属性,它的值是一个函数。这个函数接受这个input对应的真实DOM元素,我们绑定到this后得到该实例以在handleSubmit这个方法里访问它。

人们常常会误解,为了使用refs必须使用class component,但实际refs还可以通过闭包在functional component中使用。

代码语言:javascript
复制
function CustomForm ({handleSubmit}) {
  let inputElement
  return (
    <form onSubmit={() => handleSubmit(inputElement.value)}>
      <input
        type='text'
        ref={(input) => inputElement = input} />
      <button type='submit'>Submit</button>
    </form>
  )
}

什么是keys 而且为什么他们很重要

Keys负责帮助React跟踪列表中哪些元素被改变/添加/移除。

代码语言:javascript
复制
render () {
  return (
    <ul>
      {this.state.todoItems.map(({task, uid}) => {
        return <li key={uid}>{task}</li>
      })}
    </ul>
  )
}

在同级元素中每一个元素的key都必须是唯一的,我们已经多次提到了reconciliation和它其中的一个过程是比较新的element tree和上一次的element tree。keys使列表进行diff的过程更加高效,因为React可以利用子元素的key在比较两棵树的时候快速得知一个元素是新的还是刚刚被移除。没有keys,React便不知道当前哪一个对应的item被移除了。所以别小看了keys.


如果你创建了一个像下面的Twitter元素,那么Twitter的组件(类定义)应该是什么样的?

代码语言:javascript
复制
<Twitter username='tylermcginnis33'>
  {(user) => user === null
    ? <Loading />
    : <Badge info={user} />}
</Twitter>
代码语言:javascript
复制
import React, { Component, PropTypes } from 'react'
import fetchUser from 'twitter'
// fetchUser take in a username returns a promise
// which will resolve with that username's data.

class Twitter extends Component {
  // finish this
}

如果你对回调渲染模式(Render Callback Pattern)不熟悉,上面的代码看起来可能有点奇怪。在这种模式下,组件接受某个函数作为它的子元素。注意一下里面包含的东西。与之前看到的嵌入一个组件的方式有所不同,这个Twitter组件的子元素是个函数,也就是说,Twitter元素接受一个函数作为子组件时,我们在渲染函数中以props.children进行调用。

以下是我的答案:

代码语言:javascript
复制
import React, { Component, PropTypes } from 'react'
import fetchUser from 'twitter'

class Twitter extends Component {
  state = {
    user: null,
  }
  static propTypes = {
    username: PropTypes.string.isRequired,
  }
  componentDidMount () {
    fetchUser(this.props.username)
      .then((user) => this.setState({user}))
  }
  render () {
    return this.props.children(this.state.user)
  }
}

从上面看到,props.children这个函数被调用,我可以还可以给它传递user这个状态。

这种模式的好处是父组件和子组件进行解耦。父组件专注于管理状态,可以直接访问子组件的内部状态,从而控制子组件的UI要如何显示。

为了进一步说明,加入我们想要渲染Profile而不是Badge。接下来利用回调渲染模式,我们无需改变我们对父组件(Twitter)的实现,通过修改回调函数就可以很容易的替换需要显示的UI。

代码语言:javascript
复制
<Twitter username='tylermcginnis33'>
  {(user) => user === null
    ? <Loading />
    : <Profile info={user} />}
</Twitter>

Controlled Component和Uncontrolled Component 有什么区别?

React大部分的情况是组件可以控制和管理它们自己的state。当我们引入原生的HTML表单元素(input,select,textarea,等)时,我们是要遵循react的“单一数据源”将数据托管到react组件还是和以往处理HTML表单一样交由DOM进行控制?这两个问题是controlled Component和Uncontrolled Component的主要区别。

一个 controlled 组件是由react进行控制并遵循单一数据源的原则。就像底下的代码,username不存在于DOM中,而是存在于我们组件的state中。我们想要更新username的时候,我们就必须调用setState。

代码语言:javascript
复制
class ControlledForm extends Component {
  state = {
    username: ''
  }
  updateUsername = (e) => {
    this.setState({
      username: e.target.value,
    })
  }
  handleSubmit = () => {}
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          value={this.state.username}
          onChange={this.updateUsername} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

而一个 uncontrolled 组件是由DOM来存放你的表单数据,而不是由React组件中。

通常使用refs 来操作DOM。

代码语言:javascript
复制
class UnControlledForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          ref={(input) => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

虽然非受控组件看起来相对简单,因为你只需要通过refs就可以获取DOM的值,但是通常实际开发中我们都会推荐使用受控组件。主要的原因就是受控组件有助于进行表单验证,控制按钮是否可点击,强制输入格式,并且它也更符合“React way”


在生命周期的哪个阶段发生ajax请求而且为什么?

AJAX请求应该在 componentDidMount函数 进行。 有以下几个原因:

  • Fiber-React下一代的调和算法,会根据渲染性能来开始或者结束渲染。权衡componentWillMount 函数在一次生命周期中可能被调用多次,将Ajax请求放在这个函数里就具有了不确定性。这对Ajax请求来说是个不是个好的选择。
  • 若考虑其它函数,你不能保证AJAX请求在component在被挂载之前被不会进行响应。如果在组件挂载之前,数据请求就已经完成,并且调用了setState函数将数据传递到组件状态中,因为组件未被挂载所以会报错。如果在componentDidMount函数进行AJAX请求则能有效避免这个问题。

shouldComponentUpdate 的作用以及它的重要性?

上面我们了解了reconciliation这个过程和调用setState发生的事情. shouldComponentUpdate是一个允许我们自行决定某些组件(以及他们的子组件)是否进行更新的生命周期函数。为什么想要这么做?原因就是上面提过的“reconciliation的最终目的是尽可能以最有效的方式去根据新的state更新UI”。如果我们已经知道UI的哪些状态无需发生改变,也就没必要去让React去决定它是否该改变。 shouldComponentUpdate返回falss, React就会知道当前的组件和其子组件只需保留原样。


如何告诉React它应该编译生产环节版本?

通常你会使用Webpack的 DefinePlugin方法设置 NODE_ENV 为 production. 它会忽略propType validation和一些警告信息。更重要的是,它也是减少代码的好方法,因为React使用Uglify插件来移除了生产环境下不需要的注释等信息。


为什么使用 React.Children.map(props.children, () => )而非 props.children.map(() => )

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

拿以下的代码作为例子

代码语言:javascript
复制
<Parent>
  <h1>Welcome.</h1>
</Parent>

在Parent里面如果我们用props.children.map来遍历我们的子元素,它就会报错,因为子元素是个对象,不是数组。

只有当子元素个数超过一个的情况下,React会将props.children设置为数组,比如下面的代码:

代码语言:javascript
复制
<Parent>
  <h1>Welcome.</h1>
  <h2>props.children will now be an array</h2>
</Parent>

这就是为什么要优先使用React.Children.map ,因为它考虑了 props.children可能是个数组也可能是个对象。


描述一下React的事件处理逻辑

为了解决浏览器的兼容问题,React的事件处理程序会被传递给SyntheticEvent实例,它是对浏览器的原生事件的一层封装。这种合成的事件和你所使用的原生事件拥有同样的接口,但是它们能保证了不同浏览器行为的一致性。

有趣的一点是,React并不会真正地把事件附着到子节点。React使用一个单独的事件监听器来将所有事件发送到顶层处理。这对性能有很大的好处,因为它让React无需在更新DOM的时候去跟踪附着在DOM的每一个事件监听器。


createElement 和 cloneElement有什么不同?

createElement 是JSX进行编译之后React用来创建一个React Elements(UI的对象表述)的东西。cloneElement则是用来克隆一个元素并且给它传递新的props.它们的名字就是区别 ?。


setState 第二个参数是什么,它有什么作用?

一个可以在setState调用完成component重新渲染后被调用的回调函数,

setState是异步操作函数,这也是它为什么把一个回调函数作为第二个参数的原因。虽然通常我更建议用一个生命周期函数去取代这个回调函数,但是知道这个东西的存在也不是什么坏事。

代码语言:javascript
复制
this.setState(
  { username: 'tylermcginnis33' },
  () => console.log('setState has finished and the component has re-rendered.')
)

下面这段代码有什么问题吗?

代码语言:javascript
复制
this.setState((prevState, props) => {
  return {
    streak: prevState.streak + props.count
  }
})

这段代码并没错 ?. 它只是比较少见,你可以传递一个接受组件的state和props然后计算返回一个新state 的函数给setState ,就像上面这段代码。这段代码不仅没有错,而且如果你是要基于上一次的state来设置新的state,这种做法是值得推荐的。


往期精选文章

ES6中一些超级好用的内置方法

浅谈web自适应

使用Three.js制作酷炫无比的无穷隧道特效

一个治愈JavaScript疲劳的学习计划

全栈工程师技能大全

WEB前端性能优化常见方法

一小时内搭建一个全栈Web应用框架

干货:CSS 专业技巧

四步实现React页面过渡动画效果

让你分分钟理解 JavaScript 闭包


小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京程一灯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档