通宵整理的 react 面试题并附上自己的答案
什么是 React Fiber?
Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟 DOM 的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中。
类组件(Class component)和函数式组件(Functional component)之间有何不同
类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问
store
并维持状态当组件仅是接收
props
,并将组件自身渲染到页面时,该组件就是一个 '无状态组件(stateless component)',可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件
React 必须使用 JSX 吗?
React 并不强制要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。
每个 JSX 元素只是调用 React.createElement(component, props, ...children)
的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。
例如,用 JSX 编写的代码:
可以编写为不使用 JSX 的代码:
react 实现一个全局的 dialog
子类:
css:
React 的严格模式如何使用,有什么用处?
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。可以为应用程序的任何部分启用严格模式。例如:
在上述的示例中,不会对 Header
和 Footer
组件运行严格模式检查。但是,ComponentOne
和 ComponentTwo
以及它们的所有后代元素都将进行检查。
StrictMode
目前有助于:
识别不安全的生命周期
关于使用过时字符串 ref API 的警告
关于使用废弃的 findDOMNode 方法的警告
检测意外的副作用
检测过时的 context API
React 声明组件有哪几种方法,有什么不同?
React 声明组件的三种方式:
函数式定义的
无状态组件
ES5 原生方式
React.createClass
定义的组件ES6 形式的
extends React.Component
定义的组件
(1)无状态函数式组件 它是为了创建纯展示组件,这种组件只负责根据传入的 props 来展示,不涉及到 state 状态的操作组件不会被实例化,整体渲染性能得到提升,不能访问 this 对象,不能访问生命周期的方法
(2)ES5 原生方式 React.createClass // RFC React.createClass 会自绑定函数方法,导致不必要的性能开销,增加代码过时的可能性。
(3)E6 继承形式 React.Component // RCC 目前极为推荐的创建有状态组件的方式,最终会取代 React.createClass 形式;相对于 React.createClass 可以更好实现代码复用。
无状态组件相对于于后者的区别: 与无状态组件相比,React.createClass 和 React.Component 都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。
React.createClass 与 React.Component 区别:
① 函数 this 自绑定
React.createClass 创建的组件,其每一个成员函数的 this 都有 React 自动绑定,函数中的 this 会被正确设置。
React.Component 创建的组件,其成员函数不会自动绑定 this,需要开发者手动绑定,否则 this 不能获取当前组件实例对象。
② 组件属性类型 propTypes 及其默认 props 属性 defaultProps 配置不同
React.createClass 在创建组件时,有关组件 props 的属性类型及组件默认的属性会作为组件实例的属性来配置,其中 defaultProps 是使用 getDefaultProps 的方法来获取默认组件属性的
React.Component 在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。
③ 组件初始状态 state 的配置不同
React.createClass 创建的组件,其状态 state 是通过 getInitialState 方法来配置组件相关的状态;
React.Component 创建的组件,其状态 state 是在 constructor 中像初始化组件属性一样声明的。
对虚拟 DOM 的理解?虚拟 DOM 主要做了什么?虚拟 DOM 本身是什么?
从本质上来说,Virtual Dom 是一个 JavaScript 对象,通过对象的方式来表示 DOM 结构。将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次 DOM 修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改 DOM 的重绘重排次数,提高渲染性能。
虚拟 DOM 是对 DOM 的抽象,这个对象是更加轻量级的对 DOM 的描述。它设计的最初目的,就是更好的跨平台,比如 node.js 就没有 DOM,如果想实现 SSR,那么一个方式就是借助虚拟 dom,因为虚拟 dom 本身是 js 对象。 在代码渲染到页面之前,vue 或者 react 会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实 dom 结构,最终渲染到页面。在每次数据发生变化前,虚拟 dom 都会缓存一份,变化之时,现在的虚拟 dom 会与缓存的虚拟 dom 进行比较。在 vue 或者 react 内部封装了 diff 算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
另外现代前端框架的一个基本要求就是无须手动操作 DOM,一方面是因为手动操作 DOM 无法保证程序性能,多人协作的项目中如果 review 不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动 DOM 操作可以大大提高开发效率。
为什么要用 Virtual DOM:
(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能
下面对比一下修改 DOM 时真实 DOM 操作和 Virtual DOM 的过程,来看一下它们重排重绘的性能消耗∶
真实 DOM∶ 生成 HTML 字符串+ 重建所有的 DOM 元素
Virtual DOM∶ 生成 vNode+ DOMDiff+必要的 DOM 更新
Virtual DOM 的更新 DOM 的准备工作耗费更多的时间,也就是 JS 层面,相比于更多的 DOM 操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。 (2)跨平台 Virtual DOM 本质上是 JavaScript 的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp 等。
React key 是干嘛用的 为什么要加?key 主要是解决哪一类问题的
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。
在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。
注意事项:
key 值一定要和具体的元素—一对应;
尽量不要用数组的 index 去作为 key;
不要在 render 的时候用随机数或者其他操作给元素加上不稳定的 key,这样造成的性能开销比不加 key 的情况下更糟糕。
hooks 父子传值
可以使用 TypeScript 写 React 应用吗?怎么操作?
(1)如果还未创建 Create React App 项目
直接创建一个具有 typescript 的 Create React App 项目:
(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中
通过命令将 typescript 引入项目:
将项目中任何 后缀名为 ‘.js’ 的 JavaScript 文件重命名为 TypeScript 文件即后缀名为 ‘.tsx’(例如 src/index.js 重命名为 src/index.tsx )
对于 store 的理解
Store 就是把它们联系到一起的对象。Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener)注册监听器;
通过 subscribe(listener)返回的函数注销监听器
虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么
虚拟 DOM 相对原生的 DOM 不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不可能比真实的 DOM 操作更快。在首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,虚拟 DOM 也会比 innerHTML 插入慢。它能保证性能下限,在真实 DOM 操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探讨。
在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。**虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。
Redux 原理及工作流程
(1)原理 Redux 源码主要分为以下几个模块文件
compose.js 提供从右到左进行函数式编程
createStore.js 提供作为生成唯一 store 的函数
combineReducers.js 提供合并多个 reducer 的函数,保证 store 的唯一性
bindActionCreators.js 可以让开发者在不直接接触 dispacth 的前提下进行更改 state 的操作
applyMiddleware.js 这个方法通过中间件来增强 dispatch 的功能
(2)工作流程
const store= createStore(fn)生成数据;
action: {type: Symble('action01), payload:'payload' }定义行为;
dispatch 发起 action:store.dispatch(doSomething('action001'));
reducer:处理 action,返回新的 state;
通俗点解释:
首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
State—旦有变化,Store 就会调用监听函数,来更新 View
以 store 为核心,可以把它看成数据存储中心,但是他要更改数据的时候不能直接修改,数据修改更新的角色由 Reducers 来担任,store 只做存储,中间人,当 Reducers 的更新完成以后会通过 store 的订阅来通知 react component,组件把新的状态重新获取渲染,组件中也能主动发送 action,创建 action 后这个动作是不会执行的,所以要 dispatch 这个 action,让 store 通过 reducers 去做更新 React Component 就是 react 的每个组件。
refs 的作用是什么,你在什么样的业务场景下使用 refs
操作 DOM,为什么操作 DOM?
场景
图片渲染好后,操作图片宽高。比如做个放大镜功能
vue 或者 react 优化整体优化
虚拟 dom
为什么虚拟 dom 会提高性能?(必考)
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
React-Router 4 的 Switch 有什么用?
Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
,它里面不能放其他元素。
假如不加 <Switch>
:
Route 组件的 path 属性用于匹配路径,因为需要匹配 /
到 Home
,匹配 /login
到 Login
,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" />
和<Route path="/login" />
都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch>
来做到只显示一个匹配组件:
此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是精确匹配路径,经常与<Switch>
联合使用。只有当 URL 和该 <Route>
的 path 属性完全一致的情况下才能匹配上:
概述下 React 中的事件处理逻辑
抹平浏览器差异,实现更好的跨平台。
避免垃圾回收,React 引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁。
方便事件统一管理和事务机制。
为了解决跨浏览器兼容性问题,
React
会将浏览器原生事件(Browser Native Event
)封装为合成事件(SyntheticEvent
)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React
并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React
在更新DOM
的时候就不需要考虑如何去处理附着在DOM
上的事件监听器,最终达到优化性能的目的
在 React 中怎么使用 async/await?
async/await 是 ES7 标准中的新特性。如果是使用 React 官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的 webpack 配置的项目中使用,可能会遇到 regeneratorRuntime is not defined 的异常错误。那么我们就需要引入 babel,并在 babel 中配置使用 async/await。可以利用 babel 的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,虽然没有性能的提升,但对于代码编写体验要更好。
哪个生命周期发送 ajax
componentWillMount 在新版本 react 中已经被废弃了
在做 ssr 项目时候,componentWillMount 要做服务端数据的获取,不能被占用
所以在 componentDidMount 中请求
React 生命周期函数
挂载阶段
挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。
constructor
getDerivedStateFromProps
~~UNSAFE_componentWillMount~~
render
(React Updates DOM and refs)
componentDidMount
constructor
组件的构造函数,第一个被执行。显式定义构造函数时,需要在第一行执行 super(props)
,否则不能再构造函数中拿到 this
。
在构造函数中,我们一般会做两件事:
初始化 state
对自定义方法进行 this 绑定
getDerivedStateFromProps
是一个静态函数,所以不能在这里使用 this,也表明了 React 官方不希望调用方滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps 都会被调用,这样我们有机会根据新的 props 和当前的 state 来调整一个新的 state。
这个函数会在收到新的 props,调用了 setState 或 forceUpdate 时被调用。
render
React 最核心的方法,class 组件中必须实现的方法。
当 render 被调用时,它会检查 this.props
和 this.state
的变化并返回一下类型之一:
原生的 DOM,如 div
React 组件
数组或 Fragment
Portals(传送门)
字符串或数字,被渲染成文本节点
布尔值或 null,不会渲染任何东西
componentDidMount
在组件挂载之后立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法比较适合添加订阅的地方,如果添加了订阅,请记得在卸载的时候取消订阅。
你可以在 componentDidMount 里面直接调用 setState,它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使 render 了两次,用户也不会看到中间状态。
更新阶段
更新阶段是指当组件的 props 发生了改变,或者组件内部调用了 setState 或者发生了 forceUpdate,这个阶段的过程包括:
UNSAFE_componentWillReceiveProps
getDerivedStateFromProps
sholdComponentUpdate
UNSAFE_componentWIllUpdate
render
getSnapshotBeforeUpdate
(React Updates DOM and refs)
componentDidUpdate
shouldComponentUpdate
它有两个参数,根据此函数的返回值来判断是否重新进行渲染,首次渲染或者是当我们调用了 forceUpdate 时并不会触发此方法,此方法仅用于性能优化。
但是官方提倡我们使用内置的 PureComponent 而不是自己编写 shouldComponentUpdate。
getSnapshotBeforeUpdate
这个生命周期函数发生在 render 之后,在更新之前,给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot,这个 snapshot 会作为 componentDidUpdate 第三个参数传入。
componentDidUpdate
这个函数会在更新后被立即调用,首次渲染不会执行此方法。在这个函数中我们可以操作 DOM,可以发起请求,还可以 setState,但注意一定要用条件语句,否则会导致无限循环。
卸载阶段
componentWillUnmount
这个生命周期函数会在组件卸载销毁之前被调用,我们可以在这里执行一些清除操作。不要在这里调用 setState,因为组件不会重新渲染。
评论