React基础面试题

482 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

前言

react用于构建前端项目的三大框架之一,使用jsx语法书写代码。由于最近在准备面试的东西,就顺道总结了一部分关于react 相关的面试题,也顺便给自己查缺补漏一下。

问题

对于react 的相关了解,以及通常使用react 的时候还会使用那些技术?

  • React 是用于构建用户界面的JavaScript 库,起源于Facebook 的内部项目,后将其开源。通常使用jsx 语法进行编写代码,每个 JSX 元素都是调用 React.createElement() 的语法糖。通常来说,我们一般在项目中会使用react + react-dom + react-router + react-radux + antd 的结合来开发项目。

react 的生命周期

  • 生命周期详解生命周期概览
  • 通常说到react 生命周期,我们会将其分为两部分,一部分是16.3 以前,一部分是16.3 以后
  • 16.3 以前:
    • 组件挂载
      • constructor:在此初始化state,绑定成员函数this,props本地化
      • componentWillMount:组件预挂载,不能进行修改state的操作,在该函数中做的操作,都可以提前到构造函数中去。
      • render:渲染函数,返回的数值用来渲染都页面上去,如果不想页面渲染,可以直接返回null或false。
      • componentDidMount:挂载成功函数。在组件被渲染到DOM树之后调用。
    • 组件更新
      • componentWillReceiveProps(nextProps):该函数在组件进行更新以及父组件render函数被调用后进行执行。通常来比较this.props 和nextProps 来重新setState。
      • shouldComponentUpdate(nextProps,nextState):返回boolean 值,true表示更新,false表示不更新。通常用来避免不必要的组件渲染。
      • componentWillUpdate:预更新函数,当组件收到新的props 和 state,会在渲染之前调用该方法。
      • render:渲染函数。
      • componentDidUpdate:更新完成函数。会在更新后被立即调用。
    • 组件卸载
      • componentWillUnmount:会在组件卸载和销毁之前直接调用。通常会在此函数中执行必要的清理操作,如:清除timer,取消网络请求等。
  • 16.3 以后:
    • 新增了俩个生命周期:getDerivedStateFromProps,getSnapshotBeforeUpdate
    • 移除了三个生命周期:componentWillMount,componentWillReceiveProps,componentWillUpdate
      • static getDerivedStateFromProps:会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
      • getSnapshotBeforeUpdate:在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。

react 组件之间如何传参

  • 通过props 传递参数
const ChildComp = (props) => {
    const {childInfo, changeChildInfo} = props;
    return <div>
        <div>来自自己的信息</div>
        <div>来自父组件的信息:{childInfo}</div>
        <div 
          onClick={() => {
            changeChildInfo('改变来自父组件的信息')
          }}
        >
            改变来自父组件的信息
        </div>
    </div>
}
const ParentComp = () => {
    const [childInfo, setChildInfo] = useState('from parent comp')
    return <ChildComp childInfo={childInfo} changeChildInfo={setChildInfo} />
}
  • 自己定义发布订阅事件传递参数
  • 通过context 传递参数
  • 通过react-redux 传递参数

类组件和函数式组件的区别

  • 类组件创建的时候需要继承React.Component 或者React.PureComponent,能够通过实例化的this,拿到自身的state 以及props,而函数式组件内部没有this,需要结合一些Hook 方法来保存组件的state,以及替代生命周期函数
  • 当我们需要在循环子组件,进行判断是类组件和函数式组件的时候,可以通过判断子组件原型上是否拥有isReactComponent属性。

受控组件和非受控组件的区别

  • 受控组件:使用state来控制组件的数据源,如果需要改变组件的数据源的话,就需要通过setState来进行改变。
  • 非受控组件:组件数据源没有绑定state,而是通过ref来对组件进行绑定,获取对应的DOM元素

setState 的更新是同步还是异步?

  • setState数据进行更新的时候,有可能是同步,也有可能是异步,具体需要看setState是在什么时机进行执行。
  • 如果setState在一些原生事件中会同步更新,如:addEventListenersetTimeoutsetInterval等。
  • 而当setState 处于React 自身的事件机制中的时候,就会进行异步更新。

类组件和函数式组件如何绑定ref?

  • 类组件绑定ref,先通过React.createRef() 创建ref,然后直接将ref 当作属性传递给对应的组件。
class ChildComp extends React.Component {
    render() {
        const { ref } = this.props;
        return <div ref={ref}>
            子组件展示内容
        </div>
    }
}

class ParentComp extends React.Component {
    ref = React.createRef()
    render() {
        <ChildComp ref={this.ref} />
    }
}
  • 函数式组件绑定ref,需要想利用React.forwardRef 将组件包裹,然后在第二个参数上拿到ref,来进行绑定,
const ChildComp = (props, ref) => {
    return <div ref={ref}>子组件展示内容</div>
}
const ChildCompForward = React.forwardRef(ChildComp);

const ParentComp = () => {
    const childRef = useRef();
    
    return <ChildCompForward ref={childRef} />
}
  • 需要注意的是,还可以通过useImperativeHandle 方法来对子组件的state 和方法 来做暴露。
const ChildComp = (props, ref) => {
    useImperativeHandle(ref, () => ({
        log: () => {
            console.log('子组件暴露的log 方法')
        }
    }))
    return <div>子组件展示内容</div>
}
const ChildCompForward = React.forwardRef(ChildComp);

const ParentComp = () => {
    const childRef = useRef();
    
    return <ChildCompForward ref={childRef} />
}

React.Component 和 React.PureComponent 的区别

  • 两者的区别在于React.Component 并未实现shouldComponentUpdate,而React.PureComponent 中以浅层对比propstate 的方法来实现了该函数。需要注意的是,PureComponent 仅做对象的浅层比较,如果对象包含了复杂的数据结构,会产生错误的比对结果。

常用的Hook 有哪些?

  • useState, useEffect, useMemo, useCallback, useRef, useContext等。

自定义Hook 如何编写?

  • 自定义Hook 是一个函数,其名称以‘use’ 开头,函数内部可以调用其他Hook。
/**
* 自定hook,通过该hook 传递点击事件event,来计算出对应元素的 left,top
* 方便将这些信息传递给需要进行定位显示的元素,如:modal弹窗,pop气泡等组件
*/
const usePos = (event) => {
    const [pos, setPos] = useState();
    
    const changePos = (e) => {
        const { left, top } = e.target.getBoundingClientRect();
        setPos({
            left,
            top
        });
    }
    
    changePos(e);
    return [pos, changePos];
}

useEffect 和 useLayoutEffect 的区别

  • useEffect 主要是可以帮助函数式组件模拟一定的生命周期函数,以及在这个函数中,来处理一定的副作用操作,比如发起请求,进行定时器等操作。
  • useLayoutEffectuseEffect 的作用差不多,区别只是在与执行时机的不同,useLayoutEffect 会在所有的DOM变更之后同步调用effect,可以使用它来读取DOM 布局同步触发渲染,在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

useMemo 和 useCallback 的区别

  • useMemo 会返回一个惰性值,如果这个返回值的相关依赖没有进行更新的话,这个惰性值也不会进行更新。
  • useCallback 会返回一个惰性函数,如果相关依赖没有进行更新,那么该函数也不会进行更新。
/**
* 在下面代码中,value,callback 这两个值在初始化后,不会再进行更新
*/
const value = useMemo(() => {
    return 1;
}, []);

const callback = useCallback(() => {
    console.log('调用了callback 函数');
}, []);

react 中代码复用,有哪些方法?

  • 通常来说,进行代码复用或者说是组件复用的话,会有以下三种方法:
    • hoc高阶函数:通过高阶组件,对相应组件进行包裹处理,传递一部分相同的属性给对应组件。
    • render Props:将对应组件通过props 属性传给组件,然后拿到组件内部的state 等参数。
    • hooks:通过编写自定义hook 来实现一段代码或者逻辑的复用。

减少react 组件的重复渲染,有哪些方法?

  • 一般来说,对react 性能的优化,其实就是减少组件的不必要渲染次数,可以通过以下方法来减少次数:
    • React.memo 包裹组件,传递自定义方法,进行比对。
    • useMemo,useCallback 等Hook
    • 通过PureComponent,shouldComponentUpdate 等方法来减少类组件的渲染次数。

如何将某个组件渲染到额外的元素节点上去?以及如何卸载组件?

  • react 自己提供了相关的方法:
    • ReactDom.createPortal(child, container): 将对应的child 组件渲染到container 元素上去。
    • unmountComponentAtNode:从DOM 中卸载组件。

参考文档:

react 官网

react 顶层API

react Hook简介

react Hook API索引