面试题汇总(react)

225 阅读10分钟

接下来几个还是React中遇到的面试题,问的多的就是组件之间数据传递,还有一个对于redux,React-Router,这几个是问的比较多的

详细介绍下Redux

当介绍redux的时候,我们首先应该知道什么是redux

什么是redux

redux可以理解为我们前端管理数据的一个容器,可以帮助实现前端数据状态的管理

redux的作用

redux可以帮助我们管理这些共享的状态。它让我们能够以有条理的存储数据,而且让我们能够在应用的任何位置快速获取已存储的数据,而不需要通过状态提取到父组件一层一层地向下传递。

redux的设计原理

链接:www.jianshu.com/p/e98420655…

Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
redux 有三个设计原则

  • 唯一数据源
  • 保持只读性
  • 数据改变只能通过纯函数
唯一数据源

整个应用的state都被存储到一个状态树里面,并且这个状态树,只存在于唯一的store中

保持只读性

state是只读的,唯一改变state的方法就是触发action,action是一个用于描述以发生时间的普通对象

数据改变只能通过纯函数

使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers

redux的作用

redux可以帮助我们管理这些共享的状态。它让我们能够以有条理的存储数据,而且让我们能够在应用的任何位置快速获取已存储的数据,而不需要通过状态提取到父组件一层一层地向下传递。

Redux 的概念解析

  • Store

store就是保存数据的地方,你可以把它看成一个数据,整个应用智能有一个store

Redux提供createStore这个函数,用来生成Store

  • State

state就是store里面存储的数据,store里面可以拥有多个state,Redux规定一个state对应一个View,只要state相同,view就是一样的,反过来也是一样的,可以通过store.getState( )获取

  • Action

state的改变会导致View的变化,但是在redux中不能直接操作state也就是说不能使用this.setState来操作,用户只能接触到View。在Redux中提供了一个对象来告诉Store需要改变state。Action是一个对象其中type属性是必须的,表示Action的名称,其他的可以根据需求自由设置。

  • store.dispatch( )

store.dispatch接收一个Action作为参数,将它发送给store通知store来改变state。

  • Reducer

Store收到Action以后,必须给出一个新的state,这样view才会发生变化。这种state的计算过程就叫做Reducer。 Reducer是一个纯函数,他接收Action和当前state作为参数,返回一个新的state

React中的refs的作用是什么

原文链接 [www.jianshu.com/p/64a9cfdf7…]
在react中,父子组件之间的数据流,我们只能通过props的传递的属性来修改其组件的值,来重新渲染,当我们需要获取一个真实DOM的时候,比如文本框的聚焦,触发动画等 这时候就需要refs来触发了

1 为DOM元素添加ref属性

首先可以在react中可以给任何元素,组件添加ref属性
给html添加ref属性,该属性是接收一个回调函数,而该回调接收底层当前的DOM元素作为回调参数,我们可以将参数添加到组件所对应的组件实例当中。


class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.handle = this.handle.bind(this);
  }
  
  render() {
    return <div>
            <input ref={el => {this.eleInput = el}}/> // el 指向当前DOM元素(input)
            <button onClick = {this.handle}>聚焦</button>
           </div>
  }
  
  handle() {
    this.eleInput.focus();
  }
}

ReactDOM.render(
  <Foo/>,
  document.getElementById('root')
);

Foo组件在加载时,将DOM元素传入ref的回调参数中,然后将其赋值给组件实例的自定义属性eleInput。

在组件卸载时,会传入null。ref回调会在componentDidMount和 componentWillUpdate 生命周期之前执行。

2 给类组件添加ref属性

如果给 class 声明的自定义组件添加ref属性,则ref回调的参数将是已经加载的该组件实例。

class Foo extends React.Component {
  render() {
    return <div>
            <input type="text"/>
          </div>
  }
}

class Fn extends React.Component {
  render() {
    return <Foo ref = {el => {this.componEle = el; console.log(el)}}/> // el指向当前组件实例
  }
}

ReactDOM.render(
  <Fn/>,
  document.getElementById('root')
);

给组件添加ref属性,则其回调参数指向当前组件实例。

注意: 这种方式只对class声明的组件有效

3 refs与函数式组件

ref属性不能用在函数式声明的组件上,因为函数声明的组件没有实例。

以下这种方式ref是无效的。

function Foo() {
  return <div>
            <input type="text"/>
          </div>
}

class Fn extends React.Component {
  render() {
    // ref 是无效的,并且会报错
    return <Foo ref = {el => {this.componEle = el}}/>
  }
}

如果要使用ref获取组件实例,需要转换成class声明的组件。

但是可以在函数式声明的组件内部使用ref属性。如下:

function Foo() {
  
  let eleInput = null;
  function handle() {
    eleleInpute.focus();
  }
  return <div>
            <input type="text" ref={el => eleInput = el}/>
            <button onClick = {handle}>聚焦</button>
          </div>
}
class Fn extends React.Component {
  render() {
    return <Foo/>
  }
}
ReactDOM.render(
  <Fn/>,
  document.getElementById('root')
);

4 对父组件暴露DOM子节点

有些情况下,我们需要从父组件中访问子组件中的某一个DOM节点。可以通过在组件上添加自定义属性,传递一个回调给子组件。而且这种方式适用于函数式组件和class组件。 如下:

function Foo(props) {
  return <div>
            <input type="text" ref = {props.inputEle}/>
          </div>
}

class Fn extends React.Component {
  constructor(props) {
    super(props);
    this.handle = this.handle.bind(this);
  }
  render() {
    return <div>
    <Foo inputEle = {el => {this.eleInput = el; console.log(el)}}/> // el为子组件上对应的DOM节点
      <button onClick = {this.handle}>聚焦</button>
      </div>
  }
  
  handle() {
    this.eleInput.focus();
  }
}

注意点:

  • inputEle属性没有特殊含义,就是一般属性名。
  • 回调使用箭头函数。因为此时的this的指向的是Foo组件实例。如果使用普通函数,在需要将this保存在一个变量中。

还可以像Vue中一样,给ref设置字符串类型的值,通过this.refs.xxx获取DOM节点,但是不建议使用它,因为 String 类型的 refs 存在问题。它已过时并可能会在未来的版本是移除(官网这么说滴)。而且,如果在父组件中 获取子组件的DOM节点,在函数式组件中是无效获取不到的。

描述一下事件在React中的处理方式

原文链接 www.jianshu.com/p/07450e414… 在React组件中处理事件最容易出错的地方是事件处理函数中this的指向问题,因为ES6 class并不会为方法自动绑定this到当前对象。React事件处理函数的写法主要有三种方式,不同的写法解决this指向问题的方式也不同。

一、使用箭头函数

直接在React元素中采用箭头函数定义事件的处理函数,例如:

class MyComponent extends React.Component{
  constructor(props){
        super(props);
        this.state = {number:0};
  }
  
   render(){
       return(
          <button onClick={(event)=>{console.log(this.state.number);}} >
       )
   }
}

因为箭头函数中的this指向的是函数定义时的对象,所以可以保证this总是指向当前组件的实例对象。当事件处理逻辑比较复杂时,如果把所有的逻辑直接写在onClick的大括号内,就会导致 render函数变得臃肿,不容易直观地看出组件的UI结构,代码可读性也不好。这时,可以把逻辑封装成组件的一个方法,然后在箭头函数中调用这个方法。代码如下:

class MyComponent extends React.Component{
  constructor(props){
        super(props);
        this.state = {number:0};
  }

//每次点击一次Button,state中的number增加1
handleClick(event){
    const number = ++this.state.number;
    this.setState({
       number:number
    });
}
  
   render(){
       return(
        <div>
             <div>{this.state.number}</div>
              <button onClick={(event)=> {this.handleClick(event);}} >
        </div>
       )
   }
}

直接在render方法中为元素事件定义事件处理函数,最大的问题是,每次render调用时,都会重新创建一次新的事件处理函数,带来额外的性能开销,组件所处层级越低,这种开销就越大,因为任何一个上层组件的变化都可能会触发这个组件的render方法。当然,在大多数情况下,这种性能损失是可以不必在意的。

二 使用组件方法

直接将组件的方法赋值给元素事件属性,同时在类的构造函数中,将这个方法的this绑定到当前对象。例如:

class MyComponent extends React.Component{
  constructor(props){
        super(props);
        this.state = {number:0};
       this.handleClick = this.handleClick.bind(this);
  }

//每次点击一次Button,state中的number增加1
handleClick(event){
    const number = ++this.state.number;
    this.setState({
       number:number
    });
}
  
   render(){
       return(
        <div>
             <div>{this.state.number}</div>
              <button onClick={this.handleClick} >
        </div>
       )
   }
}

这种方式的好处是每次render不会重新创建一个回调函数,没有额外的性能损失。但在构造函数中,为事件处理函数绑定this,尤其是存在多个事件处理函数需要绑定时,这种模版式的代码还是会显得繁琐。 有些开发者还习惯在为元素的事件属性赋值时,同时为事件处理函数绑定this,例如:

class MyComponent extends React.Component{
  constructor(props){
        super(props);
        this.state = {number:0};
  }

//每次点击一次Button,state中的number增加1
handleClick(event){
    const number = ++this.state.number;
    this.setState({
       number:number
    });
}
  
   render(){
       return(
        <div>
             <div>{this.state.number}</div>
              <button onClick={this.handleClick.bind(this)} >
        </div>
       )
   }
}

这种方式的好处是每次render不会重新创建一个回调函数,没有额外的性能损失。但在构造函数中,为事件处理函数绑定this,尤其是存在多个事件处理函数需要绑定时,这种模版式的代码还是会显得繁琐。

属性初始化语法(property initializer syntax)

使用ES 7 的property initializers会自动为class中定义的方法绑定this。

class MyComponent extends React.Component{
  constructor(props){
        super(props);
        this.state = {number:0};
  }

//ES7 的属性初始化语法,实际上也是使用了箭头函数
handleClick = (event)=>{
    const number = ++this.state.number;
    this.setState({
       number:number
    });
}
  
   render(){
       return(
        <div>
             <div>{this.state.number}</div>
              <button onClick={this.handleClick} >
        </div>
       )
   }
}

这种方式既不需要在构造函数中手动绑定this,也不需要担心组件重复渲染导致的函数重复创建问题。

介绍一下React不可变数据Immutability

原文链接:www.jianshu.com/p/7a3d0dee5…

更改数据的两个方式

  • 直接更改变量
  • 建立所需修改变量的副本,进行修改后替换原数据

不可变数据的好处

  • 可以帮助我们增强组件和整体应用性能
  • 更简单的撤消/重做和步骤重现
  • 不可变数据(Immutability)还使一些复杂的功能更容易实现。避免数据改变,使我们能够保留对旧数据的引用,如果我们需要在它们之间切换
  • 追踪变更(Tracking Changes)
    确定可变对象是否已更改是复杂的,因为直接对对象进行更改。这样就需要将当前对象与先前的副本进行比较,遍历整个对象树,并比较每个变量和值。这个过程可能变得越来越复杂。确定不可变对象如何改变是非常容易的。如果被引用的对象与之前不同,那么对象已经改变了。仅此而已。
  • 确定何时重新渲染(Determining When to Re-render in React)
    React 中不可变数据最大好处在于当您构建简单的 纯(pure)组件 时。由于不可变数据可以更容易地确定是否已经进行了更改,这也有助于确定组件何时需要重新渲染。
    与shouldComponentUpdate()相关。

何为高阶组件(higher order component)

高阶组件(Higher-Order Components)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。高阶组件就是一个没有副作用的纯函数。