React常考考点

236 阅读25分钟

1. React事件机制

React并不是将click事件绑定到了div的真实DOM上,而是在document(React17是root)处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并提交由真正的处理函数执行。这样的方式不仅仅减少了内存的消耗,还能在组件在挂载销毁时统一处理订阅和移除事件。

2. React事件和普通事件(HTML事件)有什么不同

区别

  • 事件命名方式:原生事件为全小写,React采用小驼峰。
  • 事件函数处理语法:原生事件为字符串,React事件为函数。

React事件不能采用return false的方式来阻止浏览器的默认行为,而必须要明确的调用preventDefault()阻止默认行为

合成事件是React模拟原生DOM事件所有能力的一个事件对象,优点如下:

  • 兼容所有浏览器,更好的跨平台。
  • 将事件统一存放在一个数组,避免频繁的新增与删除。
  • 方便React统一管理事务机制。

尽量避免原生事件和合成事件的混合,如果原生事件阻止事件冒泡,可能会导致合成事件不执行,因为原生事件需要冒泡到document上才会执行。

3. React组件中怎么做事件代理,它的原理是什么

React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接受到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层。

在React底层,主要对合成事件做了两件事:

  • 事件委托:React会把所有的事件绑定到HTML结构最外层document(React17是root),使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和函数处理。
  • 自动绑定:React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。

4. render Props、HOC和hooks的区别

  • Render Props:指一种在React组件之间使用一个值为函数的prop共享代码的技术。
  • HOC:HOC高阶组件是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC是纯函数,没有副作用。
  • Hooks。是React16.8新增的特性。它可以让我们在不编写class的情况下使用state以及其他的React特性。通过自定义hook可以复用代码逻辑。

总结:HOC、Render Props和Hooks都是为了解决代码复用的问题,但是HOC和Render Props都有特定的使用场景和明显的缺点。Hook是React新增的API,它可以让组件逻辑更加简洁明了,同时也解决了HOC和Render Props的一些缺点

5. 对React-fiber的理解,它解决了什么问题

Fiber也称协程或者纤程。它和线程不一样,Fiber本身并不具备并发和并行的能力,它只是一种控制流程的让出机制。让出CPU的执行权,使得CPU能在这段时间执行其它的操作。使得React渲染的过程可以被中断,将控制权交回给浏览器,让位给高优先级的任务,当浏览器空闲后再恢复渲染。

解决了什么问题

React15在渲染时,会递归的比对Virtual DOM树,找出需要改动的节点,然后同步更新它们,这个过程是一气呵成不能被打断。此时React会占据者浏览器资源,使得用户触发的事件得不到响应,导致用户感觉页面卡顿。

总结:Fiber架构让React的更新过程变成可被中断。适时的让出CPU执行权,除了可以让浏览器及时响应用户的交互,还有以下好处:

  • 分批延时的对DOM进行操作,避免一次性操作大量的DOM节点。
  • 给浏览器一点喘息的机会,让它可以做一些其它高优先级的事情。

6. React.component和React.pureComponent的区别

PureCompnent表示一个纯组件,可以用来优化React程序,减少render函数的执行次数,从而提高组件的性能。

在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数执行retern false来阻止页面的更新。从而减少不必要的render执行。PureComponent会自动执行sholdComponentUpdate函数。PureComponen中shouldComponentUpdate进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较是不是同一个地址,而不会比较这个地址里的数据是否一致。当组件更新时,如果组件的props和state都没有改变,render函数就不会执行。省去虚拟DOM的生成和比对过程,提升组件的性能。

7. 哪些方法会触发React重新渲染?重新渲染render会做些什么?

触发组件重新渲染的方法

  • 组件的state或者props发生变化。
  • 父组件重新渲染,子组件也会重新渲染。即使传入的props未发生变化,子组件也会重新渲染。

重新渲染render会做些什么

  1. diff算法比较新旧VirtualDOM。
  2. 对新旧两棵树进行一个深度优先遍历,每遍历到一个节点,就把改节点和新的节点树进行对比,如果有差异就放到一个对象里。
  3. 遍历差异对象,根据差异的类型,和对应规则更新VirtualDOM。

8. 对React中Fragment的理解

在React中,组件返回的元素只能有一个根标签。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹所有的元素,Fragment标签不会渲染出任何元素

9. 对React Portal的理解

Portal提供了一种将子节点渲染到父组件以外的DOM节点的方案。使得组件可以脱离父组件层级挂载在DOM数的任何位置,通俗的讲就是render了一个组件,但是这个组件的DOM结构并不在本组件内。

使用方式:ReactDOM.createPortal(child, container)。child是可渲染的React子项,container需要挂载到的DOM元素。一般情况下组件的DOM会挂载到父组件的DOM上,但是有时需要将元素挂载到比父组件更高层级的位置。

10. React如何避免不必要的render

  • 合理使用shouldComponentUpdate方法和PureComponent。
  • 使用React.momo缓存函数组件。

11. 对React-intl的理解

React-Intl是雅虎的语言国际化项目FormatJS的一部分,通过其提供的组件和API可以和ReactJS绑定。React-Intl提供了两种方法,一种引用React组件,另一种使用API。官方推荐使用第一种。在React-Intl中,可以配置不同的语言包,根据需要在语言包之间进行切换。

12. 为什么React并不推荐优先考虑Context

Context目前还处于实验阶段,可能会在后面的发行版本中有较大的变化,为了避免给今后升级带来大的麻烦和影响,不建议在app中使用context。对于组件之间的数据通信或者状态管理,尽量有效的使用props或者state解决。或者考虑使用第三方的库进行解决。context的更新需要通过setState触发,但是这并不是可靠的,比如某个子组件通过一些方法不更新,shouldComponentUpdate返回false,那么不能保证context的更新一定。可以触发子组件的更新。

13. React中refs的作用是什么?有哪些应用场景

refs提供了一种方式,用于访问render方法中返回的React元素或DOM节点。refs应该谨慎使用。以下场景使用refs比较合适:

  • 处理焦点、文本选择或者媒体控制
  • 触发必要的动画
  • 集成第三方DOM库

ref通过React.createRef/useRef方法创建。通过ref属性附加到React元素上。

14. React.fowardRef是什么?有什么作用

React.forwardRef会创建一个React组件,这个组件能够将接受的ref属性转发到组件树下的另一个组件中。

使用场景

  • 转发refs到DOM组件
  • 在高阶组件中转发ref

15. React中的受控组件和非受控组件

  • 受控组件:在使用表单收集用户输入时,需要给对应元素绑定一个change事件,当表单的状态发生变化时,就会触发onChange事件,更新组件的state。在受控组件中,组件渲染出的状态与它的value或checked属性相对应,React通过这种方式消除了组件的局部状态,React官方推荐使用受控表单组件。
  • 非受控组件:如果一个表单组件没有value props时,就可以称该组件为非受控组件。在非受控中,可以通过一个ref来从DOM获得表单值,而不是为了每个状态更新编写一个事件处理函数。

总结:页面中所有输入类的DOM如果是现用现取的称为非受控组件。通过setState将输入的值维护到state中,需要时从state中取出,这里DOM的数据就受到了state的控制,该组件称之为受控组件。

16. React setState调用后发生了什么?是同步还是异步?

React中调用setState后并不是简单同步更新还是异步更新,会根据调用的情况进行区分。

1. setState调用发生了什么

在React中调用setState函数后,React会将传入的参数对象与组件当前状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React会以相对高效的方式根据新的状态构建React元素并且着手重新渲染UI界面。在React得到元素树之后,React会自动计算出新的树与旧的树节点的差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React能够相对精确地知道哪些位置发生了改变以及如何改变,保证了按需更新,而不是全部重新渲染。如果在短时间内频繁触发setState,React会将state的改变压入栈中,在合适的时机,批量更新stae和试图,达到提高性能的效果。

2. setState同步还是异步

setState并不是单纯的同步或者异步。它的表现会因调用场景不同而有不同的变化。在源码中,通过isBatchingUupdates来判断setState是先存进state队列还是直接更新。如果isBatchingUpdates为true则先存进state队列执行异步操作,反之则直接更新。

  • setState异步情况:在React可以控制的地方,isBatchingUpdates为true。如在React生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
  • setState同步情况:在React无法控制的地方,isBatchingUpdates为false。如在addEventListener、setTimeout、setInterval等事件中,就只能同步更新。

总结:异步触发setState是为了性能设计,减少渲染次数。

17. React中的setState批量处理的过程是什么

调用setState时,组件的state并不会立即改变,setState只是把修改的state放入一个队列,React会优化真正的执行时机,并处于性能考虑,会将Reeact事件处理程序中的多次setState的调用合并成一次状态修改。最终更新只产生一次组件及其子组件的重新渲染,对大型应用程序中的性能提升至关重要。

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

  • Props: 一个从外部传入组件的参数,主要作用就是从父组件向子组件传递数据,它具有可读性不可变性,只能通过外部组件主动传入新的Props来重新渲染子组件。
  • State:State的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改。修改state属性会导致组件的重新渲染。

两者区别

  • props是传递给子组件的,state是在组件内自己管理的
  • props是不可修改的,所有React组件都必须像纯函数一样保护它们的props不被改变
  • state在组件内创建

19. 纯函数

纯函数特点:

  • 给定相同的输入,总是返回相同的输出
  • 过程没有副作用
  • 不依赖外部状态

React的Props就是吸取了纯函数的思想。Props的不可变性保证了相同的输入,页面显示的内容是一样的,并且不会产生副作用。

20. 如何配置React-Router实现路由切换

  • 使用<Router>组件。路由匹配是通过比较<Route>的path属性和当前地址的pathname来实现的。当一个<Route>匹配成功时,它将渲染其内容,当它不匹配时就会渲染null。没有路径的Route将始终被匹配。
  • 结合使用Switch组件和Route组件。Switch用于将Router分组,Switch>组件不是分组router所必须的,但他通常很有用。一个switch会遍历所有的子route元素,并仅渲染与当前地址匹配的第一个元素。
  • 使用link、NavLink和Redirect组件。link组件在应用程序中创建链接。无论在何处渲染一个link,都会在应用程序的HTML中渲染成<a>。NavLink是一种特殊的Link当它的to属性与当前地址匹配时,可以将其定义为活跃的。当我们想强制导航时,可以渲染一个Redirect,当一个Redirect渲染时,它将使用它的to属性进行定向。

21.React-router里的link标签和a标签的区别

从最终渲染的DOM来看,这两者都是链接,都是标签。

区别

  • Link:Link标签是React-router里实现路由跳转的链接,一般配合Router使用,react-router接管了其默认的链接跳转行为,区别于传统的页面跳转,Link的跳转行为只会触发相匹配的Router对应的页面内容的更新。
  • a标签:跳转会刷新整个页面。

link标签的特点:

  • 有onClick就执行onClick
  • click的时候阻止a标签默认事件
  • 根据跳转href(Link的to),用history(web前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而。a标签就是普通的超链接跳转会刷新整个页面,用于从当前页面跳转到href指向的另一个页面。

22. React-router的路由有几种方式

React-Router支持使用HashRouter和BrowserRouter两种路由规则.React-router-dom提供BrowserRouter和HashRouter两个组件来实现UI和URL同步。

23. React-router中Switch组件有什么作用

Switch通常被用来包裹Route,用于渲染与路径匹配的第一个子<Router>或<Redirect>,它里面不能放其它元素。

Route 的exact属性的作用。精确匹配路径,经常与<Switch>联合使用。只有当URL和该<Route>的patch属性完全一致的情况下才能匹配上。

24. 对Redux的理解,主要解决什么问题

React是视图层框架,Redux是一个用来管理数据状态和JavaScript应用工具。随着JavaScript单页应用开发日趋复杂,JavaScript需要管理比任何时候都要多的state,Redux就是降低管理难度的。Redux支持React、Angular、JQuery甚至JavaScript。Redux提供了一个叫store的统一仓库,组件通过dispatch将state直接传入store,不用通过其它的组件。并且组件通过subscribe从store获取到state的改变。

使用了Redux的应用,所有的组件都可以从store中获取到所需的state,它们也能从store获取state的改变。这比组件之间相互传递数据清晰明朗的多。

主要解决问题:单纯的Redux只是一个状态机,是没有UI呈现的,react-redux作用是将Redux的状态机和React的UI呈现绑定在一起,当dispatch action改变state的时候,会自动更新页面。

1. Redex中异步的请求怎么处理

使用React-thunk中间件。

Redux的中间件提供的是位于action被发起后,到达reducer之前的扩展点,换而言之,原本view->action->reducer->store->view的数据流加上中间件后变成了view->action->middleware->reducer->store->view,在这一环节可以做一些副作用操作,如异步请求等。

2. Redux中间件是怎么拿到store和action?然后怎么处理

redux中间件本质就是一个函数柯里化。Redux applyMiddleware Api源码中每个middleware接受两个参数,Store的getState函数和dispatch函数,分别获得store和action,最终返回一个函数,该函数会被传入next的下一个middleware的dispatch方法,并返回一个接受action的新函数,这个函数可以直接调用next或者在其它需要的时刻调用,甚至根本不用调用它。调用链中最后一个middleware会接受真实的store的dispatch方法作为next参数,并借此结束调用链。所以,middleware的函数签名是(getState, dispatch) => action

3. Redux怎么实现属性传递,介绍下原理

React-redux数据传输:view -> action ->reducer ->store ->view

4. Redux状态管理器和变量挂载到window中有什么区别

两者都是存储数据以供后期使用。但是Redux状态更改可回溯,数据多了的时候可以清晰的知道改动在哪里发生,提供了一套完整的状态管理模式。

5. Redux中的connect有什么作用

  1. connect连接React和Redux。获取state,connect通过context获取Provider中的store,通过store.getState()获取整个store tree上所有state。
  2. 包装原组件:将state和action通过props的方式注入到组件内部props,wrapWithConnect返回一个ReactComponent对象,connect重新render外部传入的原组件wrappedComponent,并把connect中传入的mapStateToProps,mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给wrappedComponent。
  3. 监听store tree变化:connect缓存了store tree中的state状态,通过当前state状态和变更前state状态进行比较,从而确定是否调用this.setState方法触发connect及其子组件的重新渲染。

25. React Hooks解决了哪些问题

  1. 在组件之间复用状态逻辑很难:使用Hook从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook使我们在无修改组件结构的情况下复用状态逻辑,使得在组件间复用逻辑变得更加便捷。
  2. 从概念上讲,React组件一直更像是函数,而Hook则拥抱了函数,同时也没牺牲React的精神原则。Hook提供了问题的解决方案无需学习复杂的函数式编程或响应式编程技术。

26. useEffect和useLayoutEffect的区别

useEffect和useLayoutEffect都是用于处理副作用,这些副作用包括改变DOM、设置订阅、操作定时器等等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。两者的使用方式是完全一致的。

区别

  • 使用场景:useEffect在React的渲染过程中是被异步调用的,用于绝大多数场景;而useLayoutEffect会在所有DOM变更后同步执行,主要用于DOM操作、样式调整避免闪烁等问题。也正是因为同步处理,所以要避免在useLayoutEffect中做计算量较大的任务从而造成阻塞。
  • 使用效果:useEffect是按照顺序执行代码的,改变屏幕像素之后(先渲染,后改变DOM)改变屏幕内容可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行(DOM改变后执行),不会产生闪烁。useLayoutEffect总是比useEffect先执行

27. React Hooks在平常开发中需要注意的问题和原因

  1. 不在循环、条件或者嵌套函数中调用hook,必须始终在React函数顶层或者自定义hook中使用。因为Hooks的设计是基于链表实现,如果在循环、条件或者嵌套函数中使用可能会导致链表取值错误,执行错误的hook。导致出现不可预测的bug。
  2. 使用useState时,不可以直接使用push、pop、splice等直接改变数组,会导致这些方法更改后无法取到最新的值。
  3. useState设置状态的时候,只有第一次生效,后期要更新状态,必须通过useEffect否则拿不到更新后的值。
  4. 善用useCallback:父组件传递给子组件函数时,可以使用useCallback将需要传递的函数memoized。防止父组件重新渲染时即使子组件没变化也会跟着渲染一次。
  5. 不要滥用useContext:context还在试验阶段,可能会在后面的版本发生较大的变化,可靠性值得关注。

28. React Hooks和生命周期的关系

函数组件本质是函数,没有state的概念。因此不存在生命周期一说,仅仅是一个render函数而已。但是引入Hooks之后就变得不同了,它能让组件在不使用class的情况下拥有state,所以就有了生命周期的概念,所谓的生命周期其实就是useState、useEffect和useLayoutEffect。

29. 对虚拟DOM的理解,虚拟DOM主要做了什么?虚拟DOM本质是什么?

从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使得跨平台渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少页面重排重绘的次数,从而提高页面渲染的性能。

如果想要实现SSR,可以借助virtualDom,因为virtualDom本身就是js对象。在代码渲染到页面之前,vue或者react会把代码换换成对象(virtualDom)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化之前,虚拟dom都会缓存一份,数据变化时,将新的virtualDom和缓存的virtualDom进行diff算法比较。只修改数据发生变化了的地方,原先没有发生数据改变的地方仍然使用之前的真实DOM

VirtualDOM的优点

  • 提高性能下限:virtualDom操作的是js对象,需要操作大量的DOM时VirturalDOM比直接操作DOM的性能消耗更便宜。
  • 跨平台:VirtaulDom的本质是JavaScript对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。

30. VirtualDOM和原生DOM相比,哪一个效率更高

VirtualDom相对原生DOM不一定是效率更高。如果只是修改一个按钮的文案,那么VirtualDom的操作无论如何都是比操作原生DOM慢的。在首次渲染大量的DOM时,由于多了一层VirtualDom的计算,此时VirtualDom的计算是比原生的innerHTMl插入慢。

VirtualDom可以保证性能下限,在真实DOM操作的时候进行针对性优化时,还是更快的。所以原生DOM和virtualDom哪一个更快要看具体的场景进行讨论。在整个DOM操作的演化过程中,其主要的矛盾并不在于性能,而在于开发者写的爽不爽,在于研发的效率。虚拟DOM的优越之处在于,它能够提供更爽、更高效的研发模式的同时,仍然能保持一个不错的性能。

31. React diff算法的原理是什么

diff算法是指生成更新补丁的方式,主要用于虚拟DOM树发生变化后,更新真实DOM的一个过程。diff算法更新过程:触发更新 =》生成补丁=》应用补丁。

React Diff 会帮助我们计算出 Virtual DOM 中真正发生变化的部分,并且只针对该部分进行实际的DOM操作,而不是对整个页面进行重新渲染。

在React中触发更新的时机主要在state变化与hooks变化后。此时触发虚拟DOM树变更遍历,采用了深度优先遍历算法。传统的遍历方式,效率较低。为了优化效率,使用了分治的方式,将单一节点的比对转换为三种类型节点的比对。分别是树、组件以及元素从而提高遍历效率。自React16起,React引入了Fiber架构。使得整个更新过程变得可暂停。节点与树分别采用了FiberNode和FiberTree进行重构,大大提升了用户体验。

32. React的key的作用是什么

kye是React用于追踪哪些列表元素被修改(添加、删除)。在开发中,我们需要保证某个元素的key在其同级元素中具有唯一性。在diff算法中React会借助key值来判断该元素是新创建还是要修改的元素,从而减少不必要元素的重新渲染。

在设置key时需要注意:

  • key值一定要和具体的元素一一对应。
  • 尽量不要用数组的index作为key。
  • 不要再render的时候用随机数或者其它操作给元素添加不稳定的key,这样造成的性能开销比不加key的情况下更糟。

33. React、react-dom和babel的作用

  • react:包含react所必须的核心代码。
  • React-dom: react渲染在不同平台所需要的核心代码。
  • Babel:将jsx转换成React代码的工具。将jsx转换为React.createElement的形式。

34. 为什么使用jsx的组件中没有看到React却需要引入React

本质上来说jsx是React.createElement(component, props, …children)方法的语法糖。在React17之前,如果使用了JSX,其实就是在使用React,需要引入react,babel会把组件转换为React.createElement(component, props, …children)的形式。React17之后,就不再需要引入,因为babel已经帮我们自动引入了。

35. React为什么要用JSX

JSX是一个JavaScript的语法扩展。React本身并不强制使用JSX。在没有JSX的时候,React实现一个组件依赖于React.createElement(component, props,…children)函数。因为React需要将组件转化为虚拟DOM树,所以在编写代码时,实际是在手写一棵结构树,而XML在数结构的描述上天生具有可读性强的优势。在实际运行中,会使用babel将JSX语法的代码还原为React.createElement的代码。

JSX是一个JavaScript的语法扩展,结构类似XML。JSX主要用于声明React元素,但React中并不强制使用JSX。即使使用了JSX,也会在构建中,通过babel插件将jsx编译为React.createElement.所以jsx更像是React.createElement的一种语法糖

36. 对React SSR的理解

服务端渲染是数据与模板组成的html,即HTML = 数据 + 模板。将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记混合为客户端上完全交互的应用程序。

没有使用服务器渲染的页面,当请求页面时,返回的body内容为空,之后执行js将html结构注入到body里,结合css渲染页面。

总结:服务端渲染是先向后端服务请求数据,然后生成完整首屏html返回给浏览器。客户端渲染是等js代码下载、加载、解析完后再请求数据渲染,在等待的过程中页面什么都没有(白屏)。服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完成数据的首屏页面。

1. SSR的优势

  • 对SEO友好。
  • 所有模板、图片等资源都存在服务端,一个html返回所有数据,减少http请求。
  • 响应快、用户体验好,首屏渲染快。

2. SSR的局限

  • 服务端压力较大:本来是通过客户端完成渲染,现在统一到服务端完成。会大量占用服务端CPU资源。
  • 学习成本较高。

37. React事件和普通事件有什么不同

React合成事件是React模拟原生dom事件所有能力的一个事件对象,优点如下:

  • 兼容所有浏览器,更好的跨平台。
  • 方便React统一管理事务机制。