自己搜集的面试题以及 慕课 前端框架及项目面试 聚焦VueReactWebpack 第九章 React 面试题
什么是react
- 单页面应用
- 组件设计模式
- 声明式编程、函数式编程
- 虚拟DOM
声明式编程 vs 命令式编程
- 不同于原生 JS 的 命令式开发 , React 是声名式开发(函数式编程)。
- 命令式开发 更关注 DOM 的每一步挂载,而 声名式开发 更在意数据的改变,只操作 数据,不关心 具体要如何 操作 DOM,改变数据以后 React 会自动帮你完成 DOM 挂载等步骤。
函数式编程
- 函数式编程是声明式编程的一部分,你可以像保存变量一样在应用程序中保存、检索和传递这些函数。
- 核心概念:
- 纯函数
- 不可变值:在函数式编程中,你无法更改数据,也不能更改。 如果要改变或更改数据,则必须复制数据副本来更改。(数组中可以使用concat/slice,他们不修改原数组)
什么是纯函数
- 给定固定的输入,就一定会有固定的输出,没有副作用(即不会偷偷修改其他值,需要修改谁就拷贝出来修改),纯函数不能涉及 异步 或者是关于 时间 的操作,一旦函数中涉及 AJAX请求、new Date()、setTimeout() ,他就 不是纯函数。
- 要求reducer是纯函数
- 重点:不可变值,需要修改谁就拷贝出来修改
- 如arr1=arr.slice()
虚拟DOM及其工作原理
渲染列表为何使用key
- 必须使用key,且不能是index/random,必须是稳定的。(数组下标index是会因为列表的变化而发生改变的,他是不稳定的)
- 因为diff算法中通过tag和key来判断是否是同一个虚拟DOM
- 使用key可以帮助减少渲染次数,提升渲染性能
- 大多数情况下,使用唯一id作为子组件的key是不会有任何问题的
JSX的本质
- JSX是javascript的语法扩展。它就像一个拥有javascript全部功能的模板语言。在SX中结合了javascript和HTML,生成可在DOM中呈现的react元素。
- **JSX的本质就是React.creatElement()**:
- 参数1:组件/html标签(大小写区分)
- 参数2:属性
- 参数3:子元素
- React.creatElement()执行生成vnode(虚拟DOM)
props,state与render函数的关系
- React高级内容(1)”props,state与render函数的关系”
- 每当组件的state或者props发生改变时,render函数就会重新执行。
- 当父组件的render函数被运行时,它的子组件的render都将被重新运行一次。(不管子组件是否需要更新)
- 补充:
- props是只读属性
- 在构造函数中定义状态值(state)
setState场景题(同步与异步,合并)
React的batchUpdate机制导致setState同步异步的情况:
在合成事件和生命周期(和他调用的函数)中是“异步”的
在原生DOM事件和setTimeout 中都是同步的
setState异步更新前合并/不合并:
setState中传入对象,会被合并,多个setState只执行一次
setState中传入函数,不会被合并,多个setState执行多次
组件生命周期
- 被React16弃用并需要替代的函数们:
- componentWillMount → UNSAFE_componentWillMount
- componentWillReceiveProps → UNSAFE_componentWillReceiveProps
- componentWillUpdate → UNSAFE_componentWillUpdate
- 单组件生命周期:如上图
- 父子组件生命周期:
- 首次挂载:相反,卸载的时候父节点先被移除,再从上至下依次触发子组件的卸载钩子
- 子组件主动更新state数据:
- 子组件被动更新:父组件通过props把自己的state传递给子组件。不管父组件有没有把数据传递给子组件,只要父组件setState,都会走一遍子组件的更新周期。而且子组件被动更新会比主动更新所执行的流程多出来一个componentWillReceiveProps 方法。(可以使用react提供的PureComponent来规定只有和本组件有关的数据更新时,本组件的render函数才执行,避免不必要的更新渲染)
- 注意SCU位置
- React高级内容(2)“React 的 生命周期函数”
shouldComponentUpdate用途
- 性能优化
- 配合“不可变值”一起使用,否则会出错
PureComponent
- 实现了浅比较的shouldComponentUpdate
- 需要结合不可变值(immutable)使用
- 优化性能,规定只有和本组件有关的数据更新时,本组件的render函数才执行,避免不必要的更新渲染
react发起ajax的位置
放在componentDidMount上,要在DOM元素渲染结束才发起ajax
组件之间如何通讯
- 父子组件props:
- 父组件向子组件传值/函数:在父组件中调用子组件的同时通过给子组件添加属性的方式向子组件传值/函数,注意如要传递函数,则需在父组件构造函数中使用bind绑定该函数this指向
- 子组件使用父组件传递的值/函数:通过
{this.props.属性名/函数名}
来调用 - 子组件修改父组件中数据的方法:子组件通过调用父组件传递过来的函数间接的操作父组件的数据
- 非嵌套组件间:
- 兄弟组件通信:找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 自定义事件(event模块):发布者(组件1)发布自定义事件,订阅者(组件2)监听事件并做出反应,我们可以通过引入event模块进行通信(例子)
- redux:利用redux等全局状态(数据)管理工具进行通信,这种工具会维护一个全局状态(数据)中心Store,并根据不同的事件产生新的状态(数据)
- context:用props太繁琐,用redux小题大做时可使用,context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。(复杂的公共信息还是要使用redux)
- 父组件做context生产方,创建context对象,并使用context对象的Provider包裹子组件给子组件获取context的能力
- 子组件做context消费方,可以获取到父组件中传递的context。
- class子组件 直接使用
this.context
可获取最近的父组件设置的context - 函数子组件 由于没有实例导致没有this,所以可以通过context对象的Consumer标签包裹的函数获取context
- class子组件 直接使用
函数组件和class组件
- 函数组件(无状态函数组件):
- 纯函数,输入props,输出JSX
- 没有实例,没有this,没有生命周期,没有state
- 不能扩展其他方法
- 但是,React版本16.8中的新功能hooks出现以后给函数组件提供了钩子来实现类组件可做的事情(状态+生命周期,比如useState()用于为函数组件引入状态(state))
- 类组件(有状态组件):
- 具有状态(state),可通过 setState()方法更改组件的状态
- 具有生命周期和this
受控组件与非受控组件
- 受控组件:
- 受控组件是在 React 中处理输入表单的一种技术。
- 组件的值(value/checked)受到state的控制(即state变化时组件值变化)
- 需要自行监听onChange时间来更新state(todolist例子)
- 常见的受控组件:input组件(checkbox、radio)、textarea组件
- 非受控组件:
- 组件值(比如:input的defaultValue属性)不受state控制,只是使用state给组件赋予了初始值,由于赋予初始值以后组件值就和state无关了,所以后面想获取 组件值 就只能 通过ref获取DOM元素 的值的方式来操作了
- 使用场景:由于非受控组件需要搭配ref去操作DOM来获取值,所以按照react设计初衷是不推荐使用的,但是特殊时候只能使用 非受控组件,比如:
- 必须手动操作DOM元素,setState实现不了的时候
- 文件上传必须使用非受控组件来获取上传的文件(DOM.files[0])
<input type=file>
- 某些富文本编辑器,需要传入DOM元素(做加粗、加下划线等操作 )
- 受控组件vs非受控组件:优先使用受控组件,这样符合React设计原则。必须操作DOM时,再使用非受控组件。
异步组件(懒加载)
- React知识点汇总(高级特性)”异步组件(懒加载)”
- 使用场景:组件比较大 或者需要 懒加载 时,我们可以使用 异步组件来做(性能优化)
- react中**可用React.lazy+React.Suspense或者使用react-loadable模块**实现异步组件
容器组件和高阶组件
- **容器组件**:容器组件是处理获取数据、订阅 redux 存储等的组件。它们包含UI组件(无状态组件)和其他容器组件,但是里面从来没有html。
- 高阶组件:
- 高阶组件 是一个函数,这个函数接受一个组件A,在函数内创建组件B将组件A包裹并给他逻辑,最后返回组件B。
- 高阶组件负责公共逻辑,传入的组件 传出后 生成的新组件 就会带上这部分逻辑
- react-redux connect就是高阶组件
- 定义高阶组件时 需要向 传入的组件 传递自己的所有props(包括别人传过来的),这样传入的组件才能获取使用
PropTypes与DefaultProps
- React高级内容(1)“PropTypes与DefaultProps的应用”
- **PropTypes 强校验**:
- 子组件在接受父组件传过来的值(即 属性 )时进行强校验。他可以要求父组件给子组件传的值是什么类型的。如若不是也并不会报错,只会在 开发者工具 中给出警告。
- isRequired强制传值:可以强制要求父组件向子组件传递某个值,如果父组件并未传值就会报出 警告 。
- DefaultProps 设置默认值:
- 当父组件没有向子组件传递某个值,而子组件使用了该值时,可以在子组件中设置DefaultProps来规定该值的默认值。
redux单向数据流
redux如何进行异步请求
- Redux进阶(2)Redux-thunk与Redux-saga中间件
- 可通过中间件redux-thunk/redux-sagas
- redux-thunk只是允许action为一个函数,我们可以将异步操作放在该函数中。
- redux-saga是将异步操作直接分离到sagas.js文件中,当action被dispatch传递时,该sagas.js文件中的一个函数也可拦截action,并通过type值确定要执行sagas.js文件中的某个函数,在这个函数中就可进行异步操作。
- redux-saga分离的更加彻底也更加复杂,更适合大型项目.
- redux-thunk:
- 优点:
- 学习成本低
- 把复杂的逻辑(异步操作)放到action中有利于后期自动化测试,比起放在生命周期函数中,放在action中比较方便自动化测试。
- 缺点:
- 一个异步请求的action代码过于复杂,且异步操作太分散,相对比saga就显得简单多了。
- action形式不统一,thunk允许action为一个函数,而原本的action是一个对象。如果不一样的异步操作,就要写多个了。
- 优点:
- redux-sagas:
- 优点:
- saga将异步操作直接分离到sagas.js文件中,集中处理了所有的异步操作,异步接口部分一目了然(有提供自己的方法)
- action是普通对象,这跟redux同步的action一模一样({type:XXX})
- 通过Effect,方便异步接口的测试
- 通过worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
- 不再使用Promise进行异步操作,所以异步操作的流程是可以控制的,可以随时取消相应的异步操作。
- 缺点:
- 学习成本高
- 体积庞大: 体积略大,代码近2000行,min版25KB左右
- 功能过剩: 实际上并发控制等功能很难用到,但是我们依然需要引入这些代码
- 优点:
React事件和DOM事件的区别
- React事件(SyntheticEvent)是挂载在document上的,而DOM事件(event)绑定在DOM上面的
- react的event不是原生的,是react封装的SyntheticEvent 合成事件对象,模拟出了 DOM 事件对象 所有的能力。event.nativeEvent 才是原生事件对象。
- div上绑定的事件被触发后冒泡到顶层document上,react中所有事件都绑定在document上
- react会实例化成统一的react合成事件(SyntheticEvent合成事件)再进行事件派发(通过target知道谁触发了事件以后就派发给他进行事件处理)将event对象交给对应的处理器执行
- react的合成事件机制作用:
- 更好的兼容性和跨平台
- 所有事件都挂载在document上,减少内存消耗,避免频繁解绑
- 单一标签移除时不需要解绑事件,因为事件都挂载在document上
- 方便事件的统一管理(如事务机制)
react性能优化
- 【异步setSTate】把多次的数据改变结合成一次来做,降低 虚拟DOM 的比对频率。
- 【渲染列表时加key】 diff算法加快虚拟DOM比对速度(
<li key={id}>{item}</li>
) - 【自定义DOM事件及时销毁】
- ComponentwillUnmount()中
element.removeEventListener(type, callback)
/element.onclick = null
- react事件是挂载在document上的,所以不需要我们进行手动销毁
- ComponentwillUnmount()中
- 【合理使用异步组件】:**React.lazy+React.Suspense或者react-loadable模块**,解决首屏加载慢的问题
- 【避免不必要的子组件render函数的渲染更新】使用react提供的PureComponent来规定只有和本组件有关的数据更新时,本组件的render函数才执行,避免不必要的更新渲染
- 函数组件使用 React.memo
- 记得搭配immutable.js不可变值
- 【constructor中绑定事件函数this】,减少bind次数
- 【使用react-router-dom库进行路由跳转】:使用react-router-dom的Link组件进行页面跳转,跳转时不会发送HTTP请求,因此加载速度会快很多,借此也可提高性能。
- 【前端通用的性能优化】,如图片懒加载、防抖、节流
- webpack层面的优化(webpack在production环境下进行打包时,就会自动压缩代码至1/3的大小,在浏览器上进行反解析再进行渲染。)
- 使用SSR服务器端渲染,不需要通过ajax发送请求,减少请求次数达到性能优化,这个需要额外设置。
超越继承的组合
- 在React中,我们总是使用组合而不是继承。
- 组合 是一种结合简单的可重用函数来生成高阶组件的技术。
多个组件的公共逻辑,如何抽离
- React知识点汇总(高级特性)”组件公共逻辑的抽离”
- 高阶组件HOC:模式简单,但会增加组件层级(需要考虑透传props以及是否会被覆盖的问题)
- render props:代码简洁,学习成本较高
- 区别:
- 高阶组件 是高阶组件(负责公共逻辑)包含组件A(负责渲染)(即 逻辑包含渲染)
- render props 是 组件A包含class组件,class组件(负责公共逻辑)中包含函数组件(负责渲染)(即 3层,组件A包含逻辑包含渲染)。
- react-hooks:使用自定义的Hooks,可以把公共逻辑的代码全部写到自定义的 Hooks 中,然后在需要它的组件中调用即可,一般只需要一行代码。
- mixin已被react废弃
HOC、render props、react-hooks的优劣如何
- HOC:
- 优势:
- HOC通过外层组件的 Props 影响内层组件的状态,而不是直接改变其 State不存在冲突和互相干扰,这就降低了耦合度
- 具有天然的层级结构(组件树结构),这又降低了复杂度
- 劣势:
- 扩展性限制: HOC 无法从外部访问子组件的 State因此无法通过shouldComponentUpdate滤掉不必要的更新。React 在支持 ES6 Class 之后提供了React.PureComponent来解决这个问题
- Ref 传递问题: Ref 被隔断,后来的React.forwardRef 解决这个问题
- Wrapper Hell: HOC可能出现多层包裹组件的情况,多层抽象同样增加了复杂度和理解成本
- 命名冲突: 如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,然后覆盖老属性
- 不可见性: HOC相当于在原有组件外层再包装一个组件,你压根不知道外层的包装是啥,对于你是黑盒
- 优势:
- Render Props:
- 优势:上述HOC的缺点Render Props都可以解决
- 劣势:
- 使用繁琐:HOC使用只需要借助装饰器语法通常一行代码就可以进行复用,Render Props学习成本高
- 嵌套过深: Render Props虽然摆脱了组件多层嵌套的问题,但是转化为了函数回调的嵌套
- React Hooks
- 优势:
- 简洁: React Hooks解决了HOC和Render Props的嵌套问题,更加简洁
- 解耦: React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦
- 组合: Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千
- 函数友好: React Hooks为函数组件而生,从而解决了类组件的几大问题:
- this 指向容易错误
- 分割在不同生命周期中的逻辑使得代码难以理解和维护
- 代码复用成本高(高阶组件容易使代码量剧增)
- 劣势:
- 额外的学习成本(Functional Component 与 Class Component 之间的困惑)
- 写法上有限制(不能出现在条件、循环中),并且写法限制增加了重构成本
- 破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)
- 在闭包场景可能会引用到旧的state、props值
- 内部实现上不直观(依赖一份可变的全局状态,不再那么“纯”)
- React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)
- 优势:
对 Time Slice的理解
- 可参考React原理”React-fiber如何优化性能”
- 时间分片(这是React背后在做的事情):时间分片是基于可随时打断、重启的Fiber架构,可打断当前任务,优先处理紧急且重要的任务,保证页面的流畅运行
- React 在渲染(render)的时候,不会阻塞现在的线程
- 如果你的设备足够快,你会感觉渲染是同步的
- 如果你设备非常慢,你会感觉还算是灵敏的
- 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
- 同样书写组件的方式
什么是hook
- Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
- Hook 使用规则: Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)
hooks的作用?产生的背景?hooks的优点
- hooks作用:
- 用于在函数组件中引入状态(state)管理(State Hook/useState())和生命周期方法(Effect Hook/useEffect())
- **取代高阶组件和render props(自定义 Hook/useXXX())**来实现抽象和可重用性
- 公共逻辑放在自定义函数useXXX()中,需要复用的函数组件中直接用函数useXXX()即可,多个不同的组件中useXXX()中的state是相互独立的。
- hooks是针对在使用react时存在以下问题而产生的:
- 组件之间**复用状态(state)和公共逻辑很难,在hooks之前,实现组件复用,一般采用高阶组件和 Render Props,它们本质是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来嵌套地域**。
- 可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。(自定义Hook)
- 组件逻辑变得越来越复杂,复杂组件变得难以理解,尤其是 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题(比如添加和删除订阅分离在componentDidMount 和 componentWillUnmount中)。如此很容易产生 bug,并且导致逻辑不一致。
- 为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),Hook 允许我们按照代码的用途分离他们, 而并非强制按照生命周期划分。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。(Effect Hook跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。)
- 通过使用 Effect Hook/useEffect (),你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不是把它们拆分到不同的生命周期函数里。
- 复杂的class组件,使用class组件,需要理解 JavaScript 中 this 的工作方式,不能忘记绑定事件处理器等操作,代码复杂且冗余。除此之外,class组件也会让一些react优化措施失效。class 不能很好的压缩,并且会使热重载出现不稳定的情况。
- 为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。
- 可以避免使用this,比如State Hook中对state的使用(例子)
- 组件之间**复用状态(state)和公共逻辑很难,在hooks之前,实现组件复用,一般采用高阶组件和 Render Props,它们本质是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来嵌套地域**。
- hooks优点:
- 避免在被广泛使用的函数组件在后期迭代过程中,需要承担一些副作用,而必须重构成类组件,它帮助函数组件引入状态(state)管理和生命周期方法。
- Hooks 出现之后,我们将复用逻辑提取到组件顶层,而不是强行提升到父组件中。这样就能够避免 HOC 和 Render Props 带来的「嵌套地域」,在无需修改组件结构的情况下复用状态逻辑
- 在非 class 的情况下可以使用更多的 React 特性,避免上面陈述的class组件带来的那些问题,比如:忘记绑定事件处理器、class组件让一些react优化措施失效、class 不能很好的压缩,并且会使热重载出现不稳定的情况