React

介绍目前常见前端面试题,React 基础原理题

参考慕课网实战课 前端框架及项目面试-聚焦 Vue3 React Webpack 总结的知识点

React vs Vue

  • React 和 Vue 一样重要(特别是大厂面试),力求两者都学会
  • React 和 Vue 有很多相通之处,而且正在趋于一致
  • React 比 Vue 学习成本高,尤其对于初学者

React 使用

  • 基本使用——常用,必须会
  • 高级特性——不常用,但体现深度
  • Redux 和 React-router 使用

自己去看文档不行吗?

  • 行。但这种一种最低效的方式
  • 文档是一个备忘录,给会用的人查阅,并不是入门教程
  • 文档全面冗长且细节过多,不能突出面试考点

回顾之前的 React 面试题

  • React 组件如何通讯
  • JSX 本质是什么
  • context 是什么,有何用途?
  • shouldComponentUpdate 的用途
  • 描述 redux 单项数据流
  • setState 是同步还是异步?(场景图,见下页)

针对上述题目

  • 先自己思考,不着急解答
  • 可能会涉及 React 原理(本来应用和原理就是分不开的)
  • 这几道题是“开胃菜”,后面还有真题演练“大餐”

关于React 17

  • 没有明显的新特性,和 React16 的使用是一致的
  • 面试时也不会被重点考察
  • 放心学习,需要注意的地方,视频中会重点提醒出来

React 基本使用

  • 日常使用,必须掌握,面试必考(不一定全考)
  • 梳理知识点,从冗长的文档中摘出考点和重点
  • 考察形式不限(参考后面的面试真题),但都在范围之内

JSX 基本使用

  • 变量、表达式
  • class style
  • 子元素和组件

代码示例用到 react-code-demo 项目的 BaseUse 组件的 JSXBaseDemo

JSX 基本使用

条件判断

  • if else
  • 三元表达式
  • 逻辑运算符 && ||

代码示例用到 react-code-demo 项目的 BaseUse 组件的 ConditionDemo

条件判断

渲染列表

  • map
  • key

代码示例用到 react-code-demo 项目的 BaseUse 组件的 ListDemo

渲染列表

事件

  • bind this
  • 关于 event 参数
  • 传递自定义参数

代码示例用到 react-code-demo 项目的 BaseUse 组件的 EventDemo

事件

React 17 事件绑定到 root

  • React 16 绑定到 document
  • React 17 事件绑定到 root 组件
  • 有利于多个 React 版本并存,例如微前端
React17 事件绑定到 root

表单

  • 受控组件
  • input textarea select 用 value
  • checkbox radio 用 checked

代码示例用到 react-code-demo 项目的 BaseUse 组件的 FormDemo

表单

组件使用

  • props 传递数据
  • props传递函数
  • props类型检查

代码示例用到 react-code-demo 项目的 BaseUse 组件的 PropsDemo

进度:看到 7-8 React父子组件通讯 视频

React面试题

❤ React 生命周期的各个阶段

  • 得分点: constructorcomponentDidMountcomponentDidUpdatecomponentWillUnmountgetDerivedStateFromPropsshouldComponentUpdategetSnapshotBeforeUpdatecomponentWillUpdate
  • 标准回答
    • React的生命周期中常用的有:
      • constructor,负责数据初始化。
      • render,将 jsx 转换成真实的dom节点。
      • componentDidMount,组件第一次渲染完成时触发。
      • componentDidUpdate,组件更新完成时触发。
      • componentWillUnmount,组件销毁和卸载时触发。
    • 不常用的有:
      • getDerivedStateFromProps,更新state和处理回调。
      • shouldComponentUpdate,用于性能优化。
      • getSnapshotBeforeUpdate,替代了componentWillUpdate
  • 加分回答 React 的生命周期中有常用的和不常用的。
    • 常用的有:
      • constructor(): 完成了数据的初始化。注意:只要使用了constructor()就必须写super(),否则this指向会出错。 -
      • render(): render()函数会将jsx生成的dom插入到目标节点中。在每次组件更新时,react通过diff算法比较更新前和更新之后的dom节点,找到最小的有差异的dom位置并更新,花费最小的开销。
      • componentDidMount(): 组件第一次渲染完成,此时dom节点已经生成,在这里调用接口请求,返回数据后使用setState()更新数据后重新渲染。
      • componentDidUpdate(prevProps,prevState): 组件更新完成。每次react重新渲染之后都会进入这个生命周期,可以拿到更新之前的props和state。
      • componentWillUnmount(): 在这个生命周期完成组件的数据销毁和卸载,移除所有的定时器和监听。
    • 不常用的有:
    • getDerivedStateFromProps(nextProps,prevState): 代替老版的componentWillReceiveProps()。官方将更新state与触发回调重新分配到了componentWillReceiveProps()中,让组件整体的更新逻辑更加清晰,并且在当前生命周期中,禁止使用this.props,强制让开发者们通过比较nextProps和PrevState去保证数据的正确行为。
    • shouldComponentUpdate(): return true可以渲染,return false不重新渲染。为什么会出现这个SCU生命周期?主要用于性能优化。也是唯一可以控制组件渲染的生命周期,在setState之后state发生改变组件会重新渲染,在当前生命周期内return false会阻止组件的更新。因为react中父组件重新渲染导致子组件也重新渲染,这时在子组件的当前生命周期内做判断是否真的需要重新渲染。
    • getSnapshotBeforeUpdate(prevProps, prevState): 代替componentWillUpdate(),核心区别在于getSnapshotBeforeUpdate()中读取到的dom元素状态是可以保证和componentDidUpdate()中的一致。

❤ React 组件间传值的方法

  • 得分点 props、context、发布/订阅。
  • 标准回答 React 中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。
    • 父子组件常用的传值方法是当父组件给子组件传值时通过props,子组件向父组件传值通过回调函数来传值。
    • 跨级组件常用的传值方法是props一层一层的传,或者使用context对象,将父组件设置为生产者而子组件都设置对应的contextType即可。
    • 非嵌套关系组件传值的方法是使用共同的父级组件进行props传值,或者通过context传值,推荐使用发布/订阅的自定义事件传值。
  • 加分回答 React中组件间传值方法有很多,按照不同的组件间关系可以把组件间传值的方法分为:
    • 父子组件传值:父子组件传值是最常见的应用场景也是非常简单的一种通信方式,父组件通过向子组件传递props,子组件得到props之后做处理就行。而子组件向父组件传值需要通过回调函数触发,具体操作是父组件将一个函数作为props的属性传递给子组件,子组件通过this.props.xxx()调用父组件的函数,参数根据需要自己进行搭配即可。
    • 跨级组件传值:跨级的组件之间传值无非就是重复多个父子组件传值的过程。一般跨级的传值方式有两种,分别是:
      • 一层一层的传递props:写法很复杂,耦合程度也很高,如果两个组件之间隔了很多层,那么也会影响中间组件的性能,开销大。不过这种方法也是可以的,如果组件之间的层级不是非常多,可以考虑使用这个方法。
    • context对象:context相当于一个全局的变量,是一个大的容器,可以把需要传递的数据放在这个容器中,不论嵌套多深都可以轻易的使用。具体操作是创建一个context对象,需要输入默认值。在父组件中使用生产者标签将需要传值的所有子组件包裹起来。子组件通过指定contextType获取到这个context对象,直接调用this.context即可获得值。
    • 非嵌套关系组件传值:就是没有任何包含关系的组件之间的传值,包括了兄弟组件。对于肺嵌套关系组件传值一般用两种方法: - 通过相同的父组件传值:子组件通过回调函数的形式将数据传给父组件,父组件直接通过属性将数据传递给子组件。
    • context:利用共同的父组件context对象进行传值
    • 通过发布/订阅进行传递:也可以说是自定义事件。重点是在子组件的componentDidMount生命周期通过声明一个自定义事件,然后在componentWillUnmount生命周期组件销毁时移除自定义事件。

❤ React 事件绑定原理

  • 得分点 非原生事件、SyntheticBaseEvent
  • 标准回答
    • React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过event.nativeEvent可以获取到原生事件。
    • React17版本开始所有事件都绑定在root根组件上,之前都是绑定在document上。
    • React中event和DOM事件不一样,和Vue也不一样。
  • 加分回答 React并不是将click事件绑在该div的真实DOM上,而是在root处监听所有支持的事件,当事件发生并冒泡至root处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。另外冒泡到 root上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticBaseEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用event.preventDefault

❤ ReactRouter 基本用法

  • 得分点 路由的模式有两种、hash模式、history模式、路由的动态传参、重定向、高阶路由组件。
  • 标准回答 react的路由保证了界面和URL的同步,拥有简单的API和强大的功能。react中的路由模式有两种,分别是:hash路由和history路由。
    • 首先用析构的方法引入需要用到的路由方式,需要注意的是路由所有的配置都必须被包裹在hash路由或者history路由里面。
    • 然后在路由标签内先再配置Route标签,它的参数有:path,路由匹配的路径。component,路由匹配成功之后渲染的组件。
    • react中路由的跳转使用Link标签,它的参数to指路由匹配的路径,也需要引入。NavLink标签和Link的区别就是渲染成a标签之后会自带一个class属性,对应的是NavLink标签的active属性。
    • react路由中有高阶路由组件withRouter,它和普通路由一样需要引入,主要作用是增加了路由跳转的方式,可以调用history方法进行函数中路由的跳转。
    • react中路由的动态传值是一个重点,{/:属性名}和{/属性名/值}搭配的方式进行传值,在需要接收参数的组件通过this.props.match.params来进行接收。react中路由的query传值是通过问号的方法将参数拼接在url之后,在需要接收参数的组件通过url.parse(this.props.location.search).query获取参数。
    • 路由的重定向需要用组件Redirect来完成,参数to是目标组件。
    • 路由的懒加载需要从react中引入Suspenselazy,引入组件时通过lazy(() => import())来引入,使用Suspense标签将Route包裹起来即可。
  • 加分回答
    • react中路由模式分为hash路由和history路由。
      • hash路由的原理是window.onhashchange监听,url前面会有一个#号,这个就是锚点,每个路由前面都会有 # 锚点,特点是 # 之后的数据改变不会发起请求,因此改变 hash 不会重新加载页面。
      • history路由的原理是window.history API,在HTML5中新增了pushStatereplaceState方法,这两个方法是在浏览器的历史记录栈上做文章,提供了对历史记录做修改的功能,虽然更改了url但是不会向服务器发起请求。history模式虽然去掉了hash模式的#锚点,但是它怕刷新,因为刷新时是真实的请求。而hash模式下,修改的是#锚点之后的信息,浏览器不会将#锚点之后的数据发送到服务器,所以没有问题。

❤ setState 是同步还是异步的

  • 得分点 是同步也是异步、合成事件、生命周期函数、原生事件、定时器
  • 标准回答 setState在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的。
  • 加分回答
    • setState本身不分同步或者异步,而是取决于是否处于batch update中。组件中的所有函数在执行时临时设置一个变量isBatchingUpdates = true,当遇到setState时,如果isBatchingUpdatestrue,那么setState就是异步的,如果是false,那么setState就是同步的。那么什么时候isBatchingUpdates会被设置成false呢?
    • 当函数运行结束时isBatchingUpdates = false
    • 当函数遇到setTimeoutsetIntervalisBatchingUpdates = false
    • 当dom添加自定义事件时isBatchingUpdates = false

❤ React 中 hooks 的优缺点

  • 得分点 可读性强、组件层级变得更浅、不再需要考虑class组件中this指向、部分生命周期不支持
  • 标准回答
    • 优点:
      • 代码的可读性强,在使用hooks之前比如发布/订阅自定义事件被挂载在componentDidMount生命周期中,然后需要在componentWillUnmount生命周期中将它清楚,这样就不便于开发者维护和迭代。在使用hooks之后,通过useEffect可以将componentDidMount生命周期、componentDidUpdate生命周期和componentWillUnmount生命周期聚合起来,方便代码的维护。
      • 组件层级变得更浅了,在使用hooks之前通常使用高阶组件HOC的方法来复用多个组件公共的状态,增强组建的功能,这样肯定是加大了组件渲染的开销,损失了性能。但是在hooks中可以通过自定义组件useXxx()的方法将多个组件之间的共享逻辑放在自定义hook中,就可以轻松的进行数据互通。
      • 不再需要考虑class组件中this指向的问题,hook在函数组件中不需要通过this.state或者this.fn来获取数据或者方法。
    • 缺点:hooks的useEffect只包括了componentDidMountcomponentDidUpdate还有componentWillUnmount这三个生命周期,对于getSnapshotBeforeUpdatecomponentDidCatch等其他的生命周期没有支持。
  • 加分回答 使用useEffect时候里面不能写太多依赖项,将各个不同的功能划分为多个useEffect模块,将各项功能拆开写,这是遵循了软件设计的“单一职责模式”。如果遇到状态不同步的情况,使用手动传递参数的形式。如果业务复杂,就使用Component代替hooks,hooks的出现并不是取代了class组件,而是在函数组件的基础上可以实现一部分的类似class组件功能。