react面试题
1.什么是虚拟 DOM?什么是 MVVM?
-
目前市场上最流行的 vue 和 react 框架都是基于虚拟 dom 和 mvvm 架构的。所以我对虚拟 dom 和 mvvm 也是有所了解的。
-
什么是虚拟 DOM?
-
虚拟 DOM 本质上是 JS 和 DOM 之间的一个映射缓存,他在形态上表现为能够描述 DOM 结构及其属性信息的 JS 对象。
-
虚拟 DOM 如何工作?
- 在挂载阶段时 React 结合 JSX 描述,构建虚拟 DOM 树,然后通过 reactDOM.render 实现虚拟 DOM 到真实 DOM 的映射
- 在更新阶段时,虚拟 DOM 将在 JS 层借助算法先对比出具体有哪些真实 DOM 需要被改变,然后将这些改变作用于真实 DOM
-
虚拟 DOM 相当于一层缓冲层,好处是:当 DOM 操作比较频繁时,它会先将前后两次的虚拟 DOM 进行对比,定位出具体需要更新的布冯,生成一个“补丁集”,最后只把“补丁”打在需要更新的那部分真实 DOM 上,实现精准的“差量更新”
-
虚拟 DOM 的真正价值?
-
不在于更好的性能。因为 js 运算耗时很高,在数据内容变化非常大,耗时差不多。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。
-
真正的价值
- 研发体验/研发效率的问题
- 跨平台的问题,一套虚拟 dom 可以在多端运行,web 端,native 端
-
-
-
什么是 MVVM?
- MVVM 是 Model-View-ViewModel 的缩写,也就是把 MVC 中的 Controller 演变成了 ViewModel。
- M:代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑。我们把 Model 称为数据层,因为它仅仅关注数据本身,不关心任何行为
- V:用户操作界面。当 ViewModel 对 Model 进行更新时,会通知数据绑定更新到 View 上
- VM:业务逻辑层,View 需要什么数据,ViewModel 要提供这个数据。View 有些操作,ViewModel 就要响应这些操作,可以说是 Model for View
- 总结:MVVM 模式简化了界面于业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化
-
虚拟 DOM 存在的价值,大大的降低了“jQuery 时代,人为寻找 DOM 变化差异”的不足(因为每个人的主观不同,寻找的差异不一样),最小化的去更新 DOM(尽可能的减少了 DOM 操作)。所以虚拟 DOM 是 MVVM 的本质,VM 代表的就是虚拟 DOM
2.在 React 中如何处理事件?什么是合成事件?合成事件有什么特点?
-
在我们日常工作中在 react 中定义事件是非常普遍的。其实 React 元素的事件处理和 DOM 元素是很相似的,但是在语法上有一点不同:
- React 事件的命名采用小驼峰式,而不是纯小写。
- 使用 JSX 语法时,你需要传入一个函数作为事件处理函数,而不是一个字符串。
-
React 如何处理事件?(原则尽可能使用合成事件来绑定事件)
-
如何绑定事件
- 使用 ES5 语法绑定事件,需要通过 this.handle.bind(this),转化 this 的指向
- 使用 ES6 语法绑定事件,使用箭头函数()=>this.handle(),无需转化 this 指向(建议)
-
如何拿到事件对象
- 如果用的是 ES5 方式绑定事件,事件处理器的最后一个参数永远是事件对象
- 如果用的是 ES6 方式绑定事件,需要手动传递参数
-
事件处理器如何自定义传递参数
- 如果是 ES5 方式传参,onClick={this.handle.bind(this,'args',...)}
- 如果是 ES6 方式传参,onClick={ev=>this.handle('arg',ev,'arg2')}
-
如何阻止事件冒泡,捕获
- 用事件对象的 api 来实现,e.preventDefault();
-
如何监听键盘事件?
- 用事件对象的 api 来实习,e.keyCode
-
-
什么是合成事件?
-
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件封装为合成事件,传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过他们屏蔽了底层浏览器的细节差异,保证了行为的一致性。React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,达到性能优化的目的。
-
所有的事件挂在 document 上,DOM 事件触发后冒泡到 document;React 找到对应的组件,造出一个合成事件出来;并按组件树模拟一遍事件冒泡。
-
为什么要有合成事件
- 兼容性和跨平台
- 挂载在统一的 document 上,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(事务机制)
- dispatchEvent 事件机制
-
-
合成事件的特点
- React 上注册的事件最终会绑定在 document 这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
- React 自身实现了一套事件冒泡机制,所以这也就是为什么我们
event.stopPropagation()无效的原因。 - React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
- React 有一套自己的合成事件
SyntheticEvent,不是原生的 - React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能。
3.React 类组件和函数组件之间有什么区别?
-
React 的类组件和函数式组件,我们在工作中用得都是非常多的。
-
类组件
- 相比于函数式组件,类组件主要多了 this,state,ref,context,生命周期等 东西。
-
函数式组件
- 而函数式组件只有 props。所以相比之下,函数式组件的性能较好,而类组件的性能较差。但是在 React16.8 之后,函数式组件多了一系列 hooks 的 api,用于模拟类组件 state,生命周期等。
-
所以我们现在基本上都是使用 hooks 的函数式编程进行开发。
4.结合生命周期,谈一谈 React 类组件的运行机制。
-
从生命周期的角度,我把 React 类组件主要分为以下几个阶段。
-
第一:挂载阶段,这个阶段重要发生了 constructor,我们可以在这个生命周期钩子函数中声明 state 声明式变量,render 渲染阶段,我们在这个阶段进行 dom 渲染,diff 调合运算,生成虚拟 dom,找出最小脏节点。然后就是 componentDidmont 我们在这个阶段可以进行 dom 操作,开启长连接,或调接口获取数据。在这个阶段可以 setState。
-
第二:更新阶段,主要有 render。。。。还有 componentDIdUpdate,当声明式变量发生变化时就会执行改阶段,在这个阶段可以使用 setState,但是注意要设置终止条件。初次之外还有一个 componentShouldUpdate 的生命周期,我们可以在这个生命周期中,进行判断如果是参与视图渲染的 state 发生变化,我们返回 ture 更新页面,如果是无关的就返回 false,不进行视图更新,节省性能。在最新的 React 中提供了 pureComponent 来实现浅层次的判断。
-
第三:卸载阶段。主要有 compon bn entDidUnmount。主要是关闭长连接,清缓存,关闭定时器。。。。。
-
拓展:在 16 之前,我们每促发一次组件更新,React 主要是通过虚拟 dom,diff 运算,今昔同步渲染,而同步渲染的递归调用栈是非常深的,只有地城调用返回,整个渲染过程才会返回,会一直占用主线程,损耗了大量性能。而在 16 之后,React 使用的是 fible 双向链表架构。将一个大的更新任务,拆分成多个小任务。每执行完一个小任务。渲染线程就把主线程交还回去。渲染可被打断,形成所谓的‘异步渲染’。从声明周期的角度来看。Fiber 根据能否被打断将生命周期划分为 render 和 comit 阶段。render 是纯净且没有副作用,可以被 React 暂停,终止。而 comit 中可以读取 dom,使用都没,运行副作用,安排更新。总的来说,render 阶段在执行过程中允许被打断(因为该阶段用户不可见,不会影响体验), 而 commit 阶段则总是同步执行的。
延伸:fible 是啥及其原理。。。。。。。。(后续查资料补充)
4.React 中组件间通信,你有哪些方案?
-
我们在工作中组件之间的通信时非常平凡的,现在我从以下几个方面来说说组件之间的通信方案
-
父组件 => 子组件:
- 1.Props,父组件通过 props 把数据传给子组件,子组件通过 this.props 获取相应的数据
- 2.Instance Methods,父组件可以通过使用 refs 来直接调用子组件实例的方法(使用场景:比如子组件是一个 modal 弹窗组件,子组件里有显示/隐藏这个 modal 弹窗的各种方法,我们就可以通过使用这个方法,直接在父组件上调用子组件实例的这些方法来操控子组件的显示/隐藏。)
-
子组件 =>父组件:
- 1.Callback Functions,子组件通过调用父组件传来的回调函数,从而将数据回传给父组件
- 2.Event Bubbling,利用原生 dom 元素的事件冒泡机制。巧妙的利用下事件冒泡机制,我们就可以很方便的在父组件的元素上接收到来自子组件元素的点击事件
-
兄弟组件通信:
- Parent Component(状态提升),一般来说,两个非父子组件想要通信,首先我们可以看看它们是否是兄弟组件,即它们是否在同一个父组件下。如果不是的话,考虑下用一个组件把它们包裹起来从而变成兄弟组件是否合适。这样一来,它们就可以通过父组件作为中间层来实现数据互通了。
-
不相关组件通信
- Context,通常前端应用会有一些“全局”性质的数据,比如当前登录的信息,ui 主题,用户选择的语言等,这些全局数据,很多组件都可能会用到,当组件层级很深时,用我们之前的方法,就得通过 props 一层一层传递下去,通过 Context 能快速解决。通过 Provider 注入,子组件通过 Consumer 接收。
- Redux 等,当大家的项目比较大,前面讲的 9 种方法已经不能很好满足项目需求时,才考虑下使用 redux 这种状态管理库。 参考文章:zhuanlan.zhihu.com/p/326254966