九 React interview-questions
React
1. 对react的理解?有哪些特性?优势? 和vue的区别
React是一个简单的javascript UI库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从父组件到子组件的单向数据流。
特点:
- JSX 语法
- 单向数据绑定
- 虚拟 DOM
- 声明式编程
- 组件
优势:
通过上面的初步了解,可以感受到 React
存在的优势:
- 高效灵活
- 声明式的设计,简单使用
- 组件式开发,提高代码复用率
- 单向响应的数据流会比双向绑定的更安全,速度更快
react采用单向数据绑定,推崇结合immutable来实现数据不可变;vue思想是 响应式的,双向数据绑定
vue 采用了template, react采用了jsx
很多人认为 React 是 MVC 中的 V(视图),vue采用的是mvvm架构模式
2. 说说 Real DOM 和 Virtual DOM 的区别?优缺点?
是什么?
Real DOM,真实 DOM
,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实 DOM
结构。
Virtual Dom
,本质上是以 JavaScript
对象形式存在的对 DOM
的描述。
创建虚拟 DOM
目的就是为了更好将虚拟的节点渲染到页面视图中,虚拟 DOM
对象的节点与真实 DOM
的属性一一照应。
两者的区别如下:
- 虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
- 虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”
优缺点:
真实 DOM
的优势:
- 易用
缺点:
- 效率低,解析速度慢,内存占用量过高
- 性能差:频繁操作真实 DOM,易于导致重绘与回流
使用虚拟 DOM
的优势如下:
- 简单方便:如果使用手动操作真实
DOM
来完成页面,繁琐又容易出错。 - 性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
- 跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行(将真实DOM映射为JavaScript对象,可以使代码不仅仅局限于对浏览器DOM的操作,只要支持JavaScript即可使用。)
- 防范xss攻击
缺点:
- 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
- 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢
3. react中的diff算法
React 的 diff 策略,将 将传统的复杂度为O(n3) 的问题转换成复杂度为 O(n) 的问题
diff策略分为三个层级
tree diff
只会对树相同层级的节点进行比较。只有删除、创建的操作
component diff
如果是同一类型的组件,则会继续往下diff运算。
不同类型的组件,则将该组件判断为 dirty component,删除组件和其所有子节点。
element diff
当节点处于同一层级时,每个节点在对应的层级用唯一的key
作为标识。react diff提供了三种结点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。
面对全新的节点时,执行插入操作, 面对多余的节点时,执行删除操作, 面对换位的节点时,执行移动操作
移动的细节:
新旧节点会遍历后对比下标,新的下标称为lastIndex,旧的称为index,如果lastIndex大于index,需要将节点旧的节点移动到新的位置,相反则位置不变,index的值赋给lastIndex(lastIndex=index)。
如果没有找到对应位置节点,则执行新增; 如果旧的节点在新的节点组用不到,则执行删除;一般是在最后做删除操作。
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2. 如果确定只是简单的展示数据,用index也是可以的。
4. 生命周期
可以分成三个阶段:
- 创建阶段
- 更新阶段
- 卸载阶段
创建阶段
创建阶段主要分成了以下几个生命周期方法:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
更新阶段
该阶段的函数主要为如下方法:
- getDerivedStateFromProps 当state需要从props初始化时,使用
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate 查看更新前 state 对象的值
- componentDidUpdate
卸载阶段
componentWillUnmount
此方法用于组件卸载前,清理一些定时器,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
5. state 和 props 有什么区别?
一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是 state
,外部参数就是props
区别
相同点:
- 两者都是 JavaScript 对象
- 两者都是用于保存信息
- props 和 state 都能触发渲染更新
不同点:
- props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
- props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
- state 是多变的、可以修改
6. 调用setState/useState发生异步吗?
会
为了提高性能React将setState设置为批次更新,即是异步操作函数,将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患
1 | constructor(props) { |
useState
useState 返回的更新对象的方法是异步的,要在下次重绘才能获取新值,不要试图在更改状态之后立即获取状态。
1 | //方法一 不确定是否有效 |
7. react事件机制
react自身实现了一套事件机制叫合成事件。合成事件是 React
模拟原生 DOM
事件所有能力的一个事件对象。
总结:
React 16上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM
React17注册的事件绑定容器结点,比如root
如果想要获得原生DOM
事件,可以通过e.nativeEvent
属性获取
React 自身实现了一套事件冒泡机制,阻止合成事件向上的冒泡,用e.stopPropagation()。阻止合成事件本级间的冒泡以及向上冒泡,用e.nativeEvent.stopImmediatePropagation()
react16事件执行顺序。先执行原生事件,再执行react事件。原生捕获->原生冒泡->react捕获->react冒泡
react17事件执行顺序。react捕获->原生捕获->原生冒泡->react冒泡
8. react构建组件的方式
- 函数式创建
- 通过 React.createClass 方法创建
- 继承 React.Component 创建
在React Hooks
出来之前,函数式组件可以视为无状态组件,只负责根据传入的props
来展示视图,不涉及对state
状态的操作。
React.createClass
是react刚开始推荐的创建组件的方式,目前这种创建方式已经不怎么用了
同样在react hooks
出来之前,有状态的组件只能通过继承React.Component
这种形式进行创建
有状态的组件也就是组件内部存在维护的数据,在类创建的方式中通过this.state
进行访问
当调用this.setState
修改组件的状态时,组价会再次会调用render()
方法进行重新渲染
9. React refs的理解?
ref 一种访问DOM
节点的方式。
- 字符串形式的ref
1 | <p ref='wenben'>文本</p> |
React.createRef()
创建
1 | this.myRef = React.createRef(); |
- useRef中的hooks
保存一个值,在整个生命周期中维持不变
1 | function App(props) { |
10. 类组件和函数式组件的区别?
类组件,顾名思义,也就是通过使用ES6
类的编写形式去编写组件,该类必须继承React.Component
如果想要访问父组件传递过来的参数,可通过this.props
的方式去访问
在组件中必须实现render
方法,在return
中返回React
对象
1 | class Welcome extends React.Component { |
函数式组件:就是通过函数编写的形式去实现一个React
组件,是React
中定义组件最简单的方式
函数第一个参数为props
用于接收父组件传递过来的参数
区别:
函数组件语法更短、更简单,这使得它更容易开发、理解和测试
而类组件也会因大量使用 this
而让人感到困惑
11. 受控组件与非受控组件
受控组件的状态全程响应外部数据的变化,给input提供onchange事件,一旦检测到文本框内容有变化,马上执行onchange事件获取表单的内容。
非受控组件将真实数据存在 DOM 节点中,通过ref来获取。,ref属性接受一个回调函数,返回一个element节点 , 通过节点获取到数据 ref={(element)=>this.addressElement = element }
12. 高阶组件
高阶组件是一个函数:即接受一个或多个组件作为参数并且返回一个组件。
1 | import React, { Component } from 'react'; |
通过对传入的原始组件 WrappedComponent
做一些你想要的操作(比如操作 props,提取 state,给原始组件包裹其他元素等),从而加工出想要的组件 EnhancedComponent
好处:高阶组件能够提高代码的复用性
13. 引入css的方式有哪几种?
- 在组件内直接使用
- 组件中引入 .css 文件
- 组件中引入 .module.css 文件
- CSS in JS
区别
- 在组件内直接使用
css
该方式编写方便,容易能够根据状态修改样式属性,但是大量的演示编写容易导致代码混乱 - 组件中引入 .css 文件符合我们日常的编写习惯,但是作用域是全局的,样式之间会层叠(样式污染)
- 引入.module.css 文件能够解决局部作用域问题。
- 通过css in js 这种方法,可以满足大部分场景的应用,可以类似于预处理器一样样式嵌套、定义、修改状态等
至于使用react
用哪种方案引入css
,并没有一个绝对的答案,可以根据各自情况选择合适的方案
14. react组件间过度动画如何实现?
在日常开发中,页面切换时的转场动画是比较基础的一个场景,当一个组件在显示与消失过程中存在过渡动画,可以很好的增加用户的体验。在react
中实现过渡动画效果会有很多种选择,如react-transition-group
,react-motion
,Animated
,以及原生的CSS
都能完成切换动画
在react
中,react-transition-group
是一种很好的解决方案,其可以导出3个组件:
CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果
SwitchTransition:两个组件显示和隐藏切换时,使用该组件
- TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画
15. redux的工作原理
redux可以对react组件做全局的状态管理。
三大原则
- 单一数据源
- state 是只读的
- 使用纯函数来执行修改
三大核心 store,action,reducer
store:数据state都放在 store
中统一管理 (将state、action、reducer联系在一起的对象)
reducer: 是个纯函数,根据传入的action,去返回新的state更新store中的数据
action:UI每一次状态的改变都会产生一个action
简易执行流程:组件的状态存放在store中,UI状态改变生成一个action,reducer接受到action然后进行处理产生新的state, 放到了store中,UI又从store中取得新状态。
API
- createStore可以帮助创建 store
- store.dispatch 帮助派发 action , action 会传递给 store
- store.getState 这个方法可以帮助获取 store 里边所有的数据内容
- store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行
16. redux中间件
那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程需要用上中间件
其本质上一个函数,对store.dispatch
方法进行了改造,在发出 Action
和执行 Reducer
这两步之间,添加了其他功能。
- redux-thunk:用于异步操作
- redux-logger:用于日志记录
上述的中间件都需要通过applyMiddlewares
进行注册,作用是将所有的中间件组成一个数组,依次执行
然后作为第二个参数传入到createStore
中
1 | const store = createStore( |
17. 说说你对React Router的理解?
可以实现无刷新的条件下切换显示不同的页面。路由的本质就是页面的URL
发生改变时,页面的显示结果可以根据URL
的变化而变化,但是页面不会刷新。因此,可以通过前端路由可以实现单页(SPA)应用
组件:
Hooks
useRoutes() 使用路由表
useNavigate() 作用:返回一个函数用来实现编程式导航。
useParams() 返回当前匹配路由的params
参数,类似于5.x中的match.params
。
useSearchParams() 用于读取和修改当前位置的 URL 中的查询字符串。返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
useLocation() 获取当前 location 信息
18. React Router的模式是什么
hash 模式 对应HashRouter组件
history 模式 对应BrowserRouter组件
19. immutable 的理解?如何应用在react项目中?
不可改变的,在计算机中,即指一旦创建,就不能再被更改的数据。
Immutable
实现的原理是 Persistent Data Structure
(持久化数据结构):
也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有的节点都复制一遍带来的性能损耗,immutable使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享
使用Immutable
对象最主要的库是immutable.js
react中的应用
使用 Immutable
可以给 React
应用带来性能的优化,主要体现在减少渲染的次数。
在做react
性能优化的时候,为了避免重复渲染,我们会在shouldComponentUpdate()
中做对比,当返回true
执行render
方法
Immutable
通过is
方法则可以完成对比,而无需像一样通过深度比较的方式比较
在使用redux
过程中也可以结合Immutable
,不使用Immutable
前修改一个数据需要做一个深拷贝
主要优势:
1、节省CPU
避免深拷贝,复杂对象比较
2、节省内存
结构共享,复用已有结构
20. 说说React render方法的原理?在什么时候触发更新
- render函数里面可以编写JSX,经过bable转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM
- 在React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render比如 当数组的地址值不发生改变,就不会触发
render
。解决方法重新创建数组 - 组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state。
父组件发生了render子组件就一定会render
21.如何提高组件的渲染效率的?在React中如何避免不必要的render?
我们了解到render
的触发时机,简单来讲就是类组件通过调用setState
方法, 就会导致render
,父组件一旦发生render
渲染,子组件一定也会执行render
渲染
父组件渲染导致子组件渲染,子组件并没有发生任何改变,这时候就可以从避免无谓的渲染,具体实现的方式有如下:
- shouldComponentUpdate
- PureComponent
- React.memo
通过shouldComponentUpdate
生命周期函数来比对 state
和 props
,确定是否要重新渲染
默认情况下返回true
表示重新渲染,如果不希望组件重新渲染,返回 false
即可
PureComponent和shouldComponentUpdate差不多。
React.memo
用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent
十分类似。但不同的是, React.memo
只能用于函数组件
23.React Jsx转换成真实DOM过程?
书写JSX代码 => Babel编译JSX =>生成React.createElement的js形式 => 生成虚拟Dom => render渲染生成真实Dom
24. React性能优化的手段?
React
凭借virtual DOM
和diff
算法拥有高效的性能,但是某些情况下,性能明显可以进一步提高。
- 避免不必要的
render
通过shouldComponentUpdate
、PureComponent
、React.memo
useCallback、useMemo等 - 避免使用内联函数 使用内联函数,则每次调用
render
函数时都会创建一个新的函数实例 - 使用 React.Fragments 避免额外标记 使用空标签<>>
- 使用 Immutable shouldComponentUpdate时复杂对象比较 redux的reducer避免深拷贝浪费性能
- 懒加载组件 路由懒加载就是加载首页时不加载其他组件 使用到了
Suspense
和lazy
组件 - 利用debounce、throttle 避免重复回调 在搜索组件中,当 input 中内容修改时就触发搜索回调。
- 服务端渲染 1 利于SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面 2 加速首屏加载,解决首屏白屏问题
25. usememo和useCallback的区别与应用场景
react性能的优化点在于:
- 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
- 父组件更新,子组件也会自动的更新
useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。
use Memo 和 useCallback
接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据
共同作用:
1.仅仅 依赖数据
发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
1.useMemo
计算结果是 return
回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 如果你的页面上展示的数据是通过某个(某些)state计算得来的一个数据,那么你每次这个组件里面无关的state变化引起的重新渲染,都会去计算一下这个数据,这时候就需要用useMemo(()=>{}, [])去包裹你的计算的方法体,这样那些无关的state改变引起的渲染不会重新计算这个方法体,而是返回之前计算的结果,达到一种缓存的效果。
2.useCallback
计算结果是 函数
, 主要用于 缓存函数,应用场景如: 父组件中需要传递函数到子组件,父组件每次更新都会重新声明内部的函数,导致传递给子组件的函数变化,子组件也会进行没有必要的更新,这种情况就可以利用 useCallback 处理传递给子组件的函数,避免每次父组件更新导致子组件更新的情况,因为只要useCallback 的依赖项没有发生变化,传递给子组件的函数始终都是缓存的同一个函数。
26、react-redux模型图
(1)所有的UI组件都应该包裹一个容器组件,他们是父子关系;
(2)容器组件是真正和redux打交道的,里面可以随意使用redux的api;负责处理业务逻辑,向UI组件传递参数。
(3)UI组件中不能使用任何redux的api;只负责渲染页面,没有逻辑功能。
(4)容器组件会传给UI组件:1.redux中保存的状态。 2.用于操作状态的方法;
(5)备注:容器给UI传递:状态、操作状态的方法,均通过props传递;