junerzyz

React面试题

junerzyz · 2017-01-17翻译 · 1434阅读 原文链接

实际上, 想要去了解某人对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元素或者被挂载的组件实例作为它的第一个参数。

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中使用。

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跟踪列表中哪些元素被改变/添加/移除。

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的组件(类定义)应该是什么样的?

<Twitter username='tylermcginnis33'>
  {(user) => user === null
    ? <Loading />
    : <Badge info={user} />}
</Twitter>
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进行调用。

以下是我的答案:

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。

<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。

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。

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_ENVproduction. 它会忽略propType validation和一些警告信息。更重要的是,它也是减少代码的好方法,因为React使用Uglify插件来移除了生产环境下不需要的注释等信息。


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

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

拿以下的代码作为例子

<Parent>
  <h1>Welcome.</h1>
</Parent>

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

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

<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的每一个事件监听器。


createElementcloneElement有什么不同?

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


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

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

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

this.setState(
  { username: 'tylermcginnis33' },
  () => console.log('setState has finished and the component has re-rendered.')
)

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

this.setState((prevState, props) => {
  return {
    streak: prevState.streak + props.count
  }
})

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

译者junerzyz尚未开通打赏功能

相关文章