关于react.js:阿里前端二面高频react面试题

42次阅读

共计 11428 个字符,预计需要花费 29 分钟才能阅读完成。

当调用 setState 时,React render 是如何工作的?

咱们能够将 ”render“ 分为两个步骤:

  1. 虚构 DOM 渲染: 当 render 办法被调用时,它返回一个新的组件的虚构 DOM 构造。当调用 setState() 时,render会被再次调用,因为默认状况下 shouldComponentUpdate 总是返回true,所以默认状况下 React 是没有优化的。
  2. 原生 DOM 渲染:React 只会在虚构 DOM 中批改实在 DOM 节点,而且批改的次数非常少——这是很棒的 React 个性,它优化了实在 DOM 的变动,使 React 变得更快。

什么是 Props

Props 是 React 中属性的简写。它们是只读组件,必须放弃纯,即不可变。它们总是在整个利用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于保护单向数据流,通常用于出现动静生成的数据。

React.Component 和 React.PureComponent 的区别

PureComponent 示意一个纯组件,能够用来优化 React 程序,缩小 render 函数执行的次数,从而进步组件的性能。

在 React 中,当 prop 或者 state 发生变化时,能够通过在 shouldComponentUpdate 生命周期函数中执行 return false 来阻止页面的更新,从而缩小不必要的 render 执行。React.PureComponent 会主动执行 shouldComponentUpdate。

不过,pureComponent 中的 shouldComponentUpdate() 进行的是 浅比拟,也就是说如果是援用数据类型的数据,只会比拟不是同一个地址,而不会比拟这个地址外面的数据是否统一。浅比拟会疏忽属性和或状态渐变状况,其实也就是数据援用指针没有变动,而数据产生扭转的时候 render 是不会执行的。如果须要从新渲染那么就须要从新开拓空间援用数据。PureComponent 个别会用在一些纯展现组件上。

应用 pureComponent 的 益处:当组件更新时,如果组件的 props 或者 state 都没有扭转,render 函数就不会触发。省去虚构 DOM 的生成和比照过程,达到晋升性能的目标。这是因为 react 主动做了一层浅比拟。

React 组件的构造函数有什么作用?它是必须的吗?

构造函数次要用于两个目标:

  • 通过将对象调配给 this.state 来初始化本地状态
  • 将事件处理程序办法绑定到实例上

所以,当在 React class 中须要设置 state 的初始值或者绑定事件时,须要加上构造函数,官网 Demo:

class LikeButton extends React.Component {constructor() {super();
    this.state = {liked: false};
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {this.setState({liked: !this.state.liked});
  }
  render() {
    const text = this.state.liked ? 'liked' : 'haven\'t liked';
    return (<div onClick={this.handleClick}>
        You {text} this. Click to toggle.      </div>
    );
  }
}
ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

构造函数用来新建父类的 this 对象;子类必须在 constructor 办法中调用 super 办法;否则新建实例时会报错;因为子类没有本人的 this 对象,而是继承父类的 this 对象,而后对其进行加工。如果不调用 super 办法;子类就得不到 this 对象。

留神:

  • constructor () 必须配上 super(), 如果要在 constructor 外部应用 this.props 就要 传入 props , 否则不必
  • JavaScript 中的 bind 每次都会返回一个新的函数, 为了性能等思考, 尽量在 constructor 中绑定事件

React 组件的 state 和 props 有什么区别?

(1)props

props 是一个从内部传进组件的参数,次要作为就是从父组件向子组件传递数据,它具备可读性和不变性,只能通过内部组件被动传入新的 props 来从新渲染子组件,否则子组件的 props 以及展示模式不会扭转。

(2)state

state 的次要作用是用于组件保留、管制以及批改本人的状态,它只能在 constructor 中初始化,它算是组件的公有属性,不可通过内部拜访和批改,只能通过组件外部的 this.setState 来批改,批改 state 属性会导致组件的从新渲染。

(3)区别

  • props 是传递给组件的(相似于函数的形参),而 state 是在组件内被组件本人治理的(相似于在一个函数内申明的变量)。
  • props 是不可批改的,所有 React 组件都必须像纯函数一样爱护它们的 props 不被更改。
  • state 是在组件中创立的,个别在 constructor 中初始化 state。state 是多变的、能够批改,每次 setState 都异步更新的。

useEffect 与 useLayoutEffect 的区别

(1)共同点

  • 使用成果: useEffect 与 useLayoutEffect 两者都是用于解决副作用,这些副作用包含扭转 DOM、设置订阅、操作定时器等。在函数组件外部操作副作用是不被容许的,所以须要应用这两个函数去解决。
  • 应用形式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 办法,在应用上也没什么差别,根本能够间接替换。

(2)不同点

  • 应用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,次要用于解决 DOM 操作、调整款式、防止页面闪动等问题。也正因为是同步解决,所以须要防止在 useLayoutEffect 做计算量较大的耗时工作从而造成阻塞。
  • 应用成果: useEffect 是依照程序执行代码的,扭转屏幕像素之后执行(先渲染,后扭转 DOM),当扭转屏幕内容时可能会产生闪动;useLayoutEffect 是扭转屏幕像素之前就执行了(会推延页面显示的事件,先扭转 DOM 后渲染),不会产生闪动。useLayoutEffect 总是比 useEffect 先执行。

在将来的趋势上,两个 API 是会长期共存的,临时没有删减合并的打算,须要开发者依据场景去自行抉择。React 团队的倡议十分实用,如果切实分不清,先用 useEffect,个别问题不大;如果页面有异样,再间接替换为 useLayoutEffect 即可。

对 React 的插槽 (Portals) 的了解,如何应用,有哪些应用场景

React 官网对 Portals 的定义:

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优良的计划

Portals 是 React 16 提供的官网解决方案,使得组件能够脱离父组件层级挂载在 DOM 树的任何地位。艰深来讲,就是咱们 render 一个组件,但这个组件的 DOM 构造并不在本组件内。

Portals 语法如下:

ReactDOM.createPortal(child, container);
  • 第一个参数 child 是可渲染的 React 子项,比方元素,字符串或者片段等;
  • 第二个参数 container 是一个 DOM 元素。

个别状况下,组件的 render 函数返回的元素会被挂载在它的父级组件上:

import DemoComponent from './DemoComponent';
render() {
  // DemoComponent 元素会被挂载在 id 为 parent 的 div 的元素上
  return (
    <div id="parent">
        <DemoComponent />
    </div>
  );
}

然而,有些元素须要被挂载在更高层级的地位。最典型的利用场景:当父组件具备 overflow: hidden 或者 z-index 的款式设置时,组件有可能被其余元素遮挡,这时就能够思考要不要应用 Portal 使组件的挂载脱离父组件。例如:对话框,模态窗。

import DemoComponent from './DemoComponent';
render() {
  // DemoComponent 元素会被挂载在 id 为 parent 的 div 的元素上
  return (
    <div id="parent">
        <DemoComponent />
    </div>
  );
}

为什么 React 要用 JSX?

JSX 是一个 JavaScript 的语法扩大,或者说是一个相似于 XML 的 ECMAScript 语法扩大。它自身没有太多的语法定义,也不冀望引入更多的规范。

其实 React 自身并不强制应用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于应用 React.createElement 函数。代码如下:

class Hello extends React.Component {render() {
    return React.createElement(
        'div',
        null, 
        `Hello ${this.props.toWhat}`
      );
  }
}
ReactDOM.render(React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

而 JSX 更像是一种语法糖,通过相似 XML 的形容形式,刻画函数对象。在采纳 JSX 之后,这段代码会这样写:

class Hello extends React.Component {render() {return <div>Hello {this.props.toWhat}</div>;
  }
}
ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

通过比照,能够清晰地发现,代码变得更为简洁,而且代码构造档次更为清晰。

因为 React 须要将组件转化为虚构 DOM 树,所以在编写代码时,实际上是在手写一棵构造树。而XML 在树结构的形容上天生具备可读性强的劣势。

但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会应用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。

总结: JSX 是一个 JavaScript 的语法扩大,构造相似 XML。JSX 次要用于申明 React 元素,但 React 中并不强制应用 JSX。即便应用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。

React 团队并不想引入 JavaScript 自身以外的开发体系。而是心愿通过正当的关注点拆散放弃组件开发的纯正性。

对 React-Fiber 的了解,它解决了什么问题?

React V15 在渲染时,会递归比对 VirtualDOM 树,找出须要变动的节点,而后同步更新它们,零打碎敲。这个过程期间,React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿

为了给用户制作一种利用很快的“假象”,不能让一个工作长期霸占着资源。能够将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“过程”,须要通过某些调度策略正当地调配 CPU 资源,从而进步浏览器的用户响应速率, 同时兼顾工作执行效率。

所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了能够让浏览器及时地响应用户的交互,还有其余益处:

  • 分批延时对 DOM 进行操作,防止一次性操作大量 DOM 节点,能够失去更好的用户体验;
  • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修改。

核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程自身是没有并发或者并行能力的(须要配合线程),它只是一种管制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其余的操作。渲染的过程能够被中断,能够将控制权交回浏览器,让位给高优先级的工作,浏览器闲暇后再复原渲染。

参考:前端 react 面试题具体解答

hooks 和 class 比拟的劣势?

一、更容易复用代码

二、清新的代码格调 + 代码量更少

毛病

状态不同步
不好用的 useEffect,

React 中 refs 的作用是什么?有哪些利用场景?

Refs 提供了一种形式,用于拜访在 render 办法中创立的 React 元素或 DOM 节点。Refs 应该审慎应用,如下场景应用 Refs 比拟适宜:

  • 解决焦点、文本抉择或者媒体的管制
  • 触发必要的动画
  • 集成第三方 DOM 库

Refs 是应用 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} />
  }
}

因为函数组件没有实例,因而不能在函数组件上间接应用 ref

function MyFunctionalComponent() {return <input />;}
class Parent extends React.Component {constructor(props) {super(props);
    this.textInput = React.createRef();}
  render() {
    // 这将不会工作!return (<MyFunctionalComponent ref={this.textInput} />
    );
  }
}

但能够通过闭合的帮忙在函数组件外部进行应用 Refs:

function CustomTextInput(props) {
  // 这里必须申明 textInput,这样 ref 回调才能够援用它
  let textInput = null;
  function handleClick() {textInput.focus();
  }
  return (
    <div>
      <input
        type="text"
        ref={(input) => {textInput = input;}} />      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

留神:

  • 不应该适度的应用 Refs
  • ref 的返回值取决于节点的类型:

    • ref 属性被用于一个一般的 HTML 元素时,React.createRef() 将接管底层 DOM 元素作为他的 current 属性以创立 ref
    • ref 属性被用于一个自定义的类组件时,ref 对象将接管该组件已挂载的实例作为他的 current
  • 当在父组件中须要拜访子组件中的 ref 时可应用传递 Refs 或回调 Refs。

对 React-Intl 的了解,它的工作原理?

React-intl 是雅虎的语言国际化开源我的项目 FormatJS 的一部分,通过其提供的组件和 API 能够与 ReactJS 绑定。

React-intl 提供了两种应用办法,一种是援用 React 组件,另一种是间接调取 API,官网更加举荐在 React 我的项目中应用前者,只有在无奈应用 React 组件的中央,才应该调用框架提供的 API。它提供了一系列的 React 组件,包含数字格式化、字符串格式化、日期格式化等。

在 React-intl 中,能够配置不同的语言包,他的工作原理就是依据须要,在语言包之间进行切换。

constructor 为什么不先渲染?

由 ES6 的继承规定得悉,不论子类写不写 constructor,在 new 实例的过程都会给补上 constructor。

所以:constructor 钩子函数并不是不可短少的,子组件能够在一些状况略去。比方不本人的 state,从 props 中获取的状况

React 中的高阶组件使用了什么设计模式?

应用了装璜模式,高阶组件的使用:

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {windowWidth: window.innerWidth,}
    onResize = () => {
      this.setState({windowWidth: window.innerWidth,})
    }
    componentDidMount() {window.addEventListener('resize', this.onResize)
    }
    componentWillUnmount() {window.removeEventListener('resize', this.onResize);
    }
    render() {return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  return DerivedClass;
}
const MyComponent = (props) => {return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);

装璜模式的特点是不须要扭转 被装璜对象 自身,而只是在里面套一个外壳接口。JavaScript 目前曾经有了原生装璜器的提案,其用法如下:

@testable
   class MyTestableClass {}

对 React 和 Vue 的了解,它们的异同

相似之处:

  • 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库
  • 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板。
  • 都应用了 Virtual DOM(虚构 DOM)进步重绘性能
  • 都有 props 的概念,容许组件间的数据传递
  • 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性

不同之处:

1)数据流

Vue 默认反对数据双向绑定,而 React 始终提倡单向数据流

2)虚构 DOM

Vue2.x 开始引入 ”Virtual DOM”,打消了和 React 在这方面的差别,然而在具体的细节还是有各自的特点。

  • Vue 声称能够更快地计算出 Virtual DOM 的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
  • 对于 React 而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate 这个生命周期办法来进行管制,但 Vue 将此视为默认的优化。

3)组件化

React 与 Vue 最大的不同是模板的编写。

  • Vue 激励写近似惯例 HTML 的模板。写起来很靠近规范 HTML 元素,只是多了一些属性。
  • React 举荐你所有的模板通用 JavaScript 的语法扩大——JSX 书写。

具体来讲:React 中 render 函数是反对闭包个性的,所以咱们 import 的组件在 render 中能够间接调用。然而在 Vue 中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 完组件之后,还须要在 components 中再申明下。

4)监听数据变动的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
  • React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的从新渲染。这是因为 Vue 应用的是可变数据,而 React 更强调数据的不可变。

5)高阶组件

react 能够通过高阶组件(Higher Order Components– HOC)来扩大,而 vue 须要通过 mixins 来扩大。

起因高阶组件就是高阶函数,而 React 的组件自身就是纯正的函数,所以高阶函数对 React 来说大海捞针。相同 Vue.js 应用 HTML 模板创立视图组件,这时模板无奈无效的编译,因而 Vue 不采纳 HOC 来实现。

6)构建工具

两者都有本人的构建工具

  • React ==> Create React APP
  • Vue ==> vue-cli

7)跨平台

  • React ==> React Native
  • Vue ==> Weex

React 中 props.children 和 React.Children 的区别

在 React 中,当波及组件嵌套,在父组件中应用 props.children 把所有子组件显示进去。如下:

function ParentComponent(props){
    return (
        <div>
            {props.children}        </div>
    )
}

如果想把父组件中的属性传给所有的子组件,须要应用 React.Children 办法。

比方,把几个 Radio 组合起来,合成一个 RadioGroup,这就要求所有的 Radio 具备同样的 name 属性值。能够这样:把 Radio 看做子组件,RadioGroup 看做父组件,name 的属性值在 RadioGroup 这个父组件中设置。

首先是子组件:

// 子组件
function RadioOption(props) {
  return (
    <label>
      <input type="radio" value={props.value} name={props.name} />
      {props.label}    </label>
  )
}

而后是父组件,不仅须要把它所有的子组件显示进去,还须要为每个子组件赋上 name 属性和值:

// 父组件用,props 是指父组件的 props
function renderChildren(props) {

  // 遍历所有子组件
  return React.Children.map(props.children, child => {if (child.type === RadioOption)
      return React.cloneElement(child, {
        // 把父组件的 props.name 赋值给每个子组件
        name: props.name
      })
    else
      return child
  })
}
// 父组件
function RadioGroup(props) {
  return (
    <div>
      {renderChildren(props)}    </div>
  )
}
function App() {
  return (
    <RadioGroup name="hello">
      <RadioOption label="选项一" value="1" />
      <RadioOption label="选项二" value="2" />
      <RadioOption label="选项三" value="3" />
    </RadioGroup>
  )
}
export default App;

以上,React.Children.map让咱们对父组件的所有子组件又更灵便的管制。

虚构 DOM 的引入与间接操作原生 DOM 相比,哪一个效率更高,为什么

虚构 DOM 绝对原生的 DOM 不肯定是效率更高,如果只批改一个按钮的文案,那么虚构 DOM 的操作无论如何都不可能比实在的 DOM 操作更快。在首次渲染大量 DOM 时,因为多了一层虚构 DOM 的计算,虚构 DOM 也会比 innerHTML 插入慢。它能保障性能上限,在实在 DOM 操作的时候进行针对性的优化时,还是更快的。所以要依据具体的场景进行探讨。

在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验 / 研发效率。虚构 DOM 不是别的,正是前端开发们为了谋求更好的研发体验和研发效率而发明进去的高阶产物。虚构 DOM 并不一定会带来更好的性能,React 官网也素来没有把虚构 DOM 作为性能层面的卖点对外输入过。** 虚构 DOM 的优越之处在于,它可能在提供更爽、更高效的研发模式(也就是函数式的 UI 编程形式)的同时,依然放弃一个还不错的性能。

在 React 中遍历的办法有哪些?

(1)遍历数组:map && forEach

import React from 'react';

class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
    return (
      <ul>
        {arr.map((item, index) => {return <li key={index}>{item}</li>
          })
        }
      </ul>
    )
  }
}

class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
    return (
      <ul>
        {arr.forEach((item, index) => {return <li key={index}>{item}</li>
          })
        }
      </ul>
    )
  }
}

(2)遍历对象:map && for in

class App extends React.Component {render() {
    let obj = {
      a: 1,
      b: 2,
      c: 3
    }
    return (
      <ul>
        {(() => {let domArr = [];
            for(const key in obj) {if(obj.hasOwnProperty(key)) {const value = obj[key]
                domArr.push(<li key={key}>{value}</li>)
              }
            }
            return domArr;
          })()}
      </ul>
    )
  }
}

// Object.entries() 把对象转换成数组
class App extends React.Component {render() {
    let obj = {
      a: 1,
      b: 2,
      c: 3
    }
    return (
      <ul>
        {Object.entries(obj).map(([key, value], index) => {// item 是一个数组,把 item 解构,写法是[key, value]
            return <li key={key}>{value}</li>
          }) 
        }
      </ul>
    )
  }
}

React 中 Diff 算法的原理是什么?

原理如下。
(1)节点之间的比拟。
节点包含两种类型:一种是 React 组件,另一种是 HTML 的 DOM。
如果节点类型不同,按以下形式比拟。
如果 HTML DOM 不同,间接应用新的替换旧的。如果组件类型不同,也间接应用新的替换旧的。
如果 HTML DOM 类型雷同,按以下形式比拟。
在 React 里款式并不是一个纯正的字符串,而是一个对象,这样在款式产生扭转时,只须要扭转替换变动当前的款式。批改完以后节点之后,递归解决该节点的子节点。
如果组件类型雷同,按以下形式比拟。
如果组件类型雷同,应用 React 机制解决。个别应用新的 props 替换旧的 props,并在之后调用组件的 componentWillReceiveProps 办法,之前组件的 render 办法会被调用。
节点的比拟机制开始递归作用于它的子节点。
(2)两个列表之间的比拟。
一个节点列表中的一个节点产生扭转,React 无奈很妤地解决这个问题。循环新旧两个列表,并找出不同,这是 React 惟一的解决办法。
然而,有一个方法能够把这个算法的复杂度升高。那就是在生成一个节点列表时给每个节点上增加一个 key。这个 key 只须要在这一个节点列表中惟一,不须要全局惟一。
(3)取舍
须要留神的是,下面的启发式算法基于两点假如。
类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树
能够为屡次 render 都体现稳固的节点设置 key。
下面的节点之间的比拟算法基本上就是基于这两个假如而实现的。要进步 React 利用的效率,须要依照这两点假如来开发。

diff 虚构 DOM 比拟的规定

  • 【旧虚构 DOM】与【新虚构 DOM】中雷同 key

    若虚构 DOM 中的内容没有产生扭转,间接应用旧的虚构 DOM

    若虚构 DOM 中的内容产生扭转了,则生成新实在的 DOM,随后替换页面中之前的实在 DOM

  • 【旧虚构 DOM】中未找到 与【新虚构 DOM】雷同的 key

    依据数据创立实在 DOM,随后渲染到页面

正文完
 0