React常见面试题

1,027 阅读29分钟

虚拟DOM(Virtual DOM)diff算法与key机制

参考 juejin.cn/post/684490…

  • 虚拟DOM

Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述。

我们都知道在前端性能优化的一个秘诀就是尽可能减少操作DOM,不仅仅是DOM相对较慢,更因为频繁变动DOM会造成浏览器的回流或者重绘,这些都会导致性能问题。因此需要这一层虚拟DOM的比较来尽可能地一次性将差异更新到DOM中,这样就会保证了DOM不会出现性能很差的情况。

而且虚拟DOM能更好得跨平台,比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM,因为Virtual DOM本身是JavaScript对象.

  • diff算法

当我们实际开发使用React的时候,使用 render()函数创建了一棵React元素树,模拟了一个虚拟 DOM 树。在下一个state或者props更新的时候,又创建了一棵新的React元素树, 模拟了一个新的虚拟 DOM 树。模拟出两颗树后,就会使用diff算法对新旧两棵树进行对比,最后再决定如何修改DOM。传统 diff 算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3)reactO(n^3)复杂度简化成了O(n)

  • key值

React利用key来识别组件,它是一种身份标识。每个key对应一个组件,相同的key React认为是同一个组件,这样后续相同的key对应的组件都不会重新被创建。

key的值必须保证唯一且稳定。不稳定的key(如由其生成的key Math.random())将导致许多组件实例和DOM节点被不必要地重新创建,这可能导致性能下降和子组件丢失状态

key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。

key值不同,则react先销毁该组件(有状态组件的componentWillUnmount会执行),然后重新创建该组件(有状态组件的constructor和componentWillUnmount都会执行)

在项目开发中,key属性的使用场景最多的还是由数组动态创建子组件的情况,需要为每个子组件添加唯一的key属性值

不要轻易的把index作为key

举例说明

变化前的数组[1,2,3,4],key对应的下标0,1,2,3
变化后的数组[4,3,2,1],key对应的下标0,1,2,3
  • 那么diff算法在变化前的数组找到key=0的值1,变化后找到的key=0的值时4
  • 因为子元素不一样就重新删除并更新

但如果加了唯一的key

变化前的数组[1,2,3,4],key对应的下标a,b,c,d
变化后的数组[4,3,2,1],key对应的下标d,c,b,a
  • 那么diff算法在变化前的数组找到key=a的值时1,在变化后找到key=a的值也是1
  • 因为子元素相同,就不删除并更新。只做了移动操作,提高了性能。

解决方案:

全局定义一个变量:let ONE = 1然后在组件中使用 key = {ONE++} 。这样 setState() 的时候每次key都产生了变化,也一定程度上避免了key的不稳定性质

React生命周期

react生命周期主要分为三个阶段挂载、更新、卸载

挂载:

  • constructor 在react组件挂载之前被调用

在我们为React.Component子类实现构造函数时,应在其他语句之前调用super()super就是为了将父类的this对象继承给子类。 

react构造函数主要是用来初始化函数内部state,为事件处理函数绑定实例。 

不能在constructor函数内部里面调用this.setState, 因为此时第一次render还未执行,DOM节点还未挂载 

  • static getDerivedStateFromProps 在调用render方法之前调用,在初始化和后续更新都会被调用

返回值:返回一个对象来更新state,返回null则不更新任何内容 

参数:第一个参数为即将更新的props,第二个参数为上个状态的state, 可以比较两者来限制条件,防止无用的更新。

getDerivedStateFromProps 是一个静态函数,不能使用this

  • render 它是class组件唯一必须实现的方法,用来渲染DOM

不能在render里面setState,否则会造成死循环

  • componentDidMount 在组件挂载后插入DOM树立即调用,在这里可以更新状态,设置事件监听器,AJAX请求等操作

更新:

  • static getDerivedStateFromProps
  • shouldComponentUpdate 在组件更新之前调用,可以控制组件是否进行更新, 返回true时组件更新, 返回false则不更新

参数:第一个是即将更新的props,第二个是即将更新后的state, 可以根据更新前后的 propsstate 来比较加一些限制条件,决定是否更新,进行性能优化 。

不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。 这样非常影响效率,且会损害性能 

不要 shouldComponentUpdate 中直接调用 setState(), 否则会导致无限循环调用更新、渲染,直至浏览器内存崩溃 

可以使用内置 PureComponent 组件替代或者使用第三方库(react-immutable-render-mixin)

react-immutable-render-mixinPureComponent原理一样,唯一的区别就是新旧数据的对比,它用了immutable-jsis()方法去做对比,性能强,复杂类型数据也能对比,相比于React.PureComponent更方便

React.PureComponentReact.Component 几乎完全相同,但React.PureComponent 通过propsstate的浅对比来实现 shouldComponentUpate()。如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断(比如对象深层的数据已改变视图却没有更新)

针对以上规则我们在项目开发种可以做出如下优化:

尽量将复杂类型数据所关联的视图单独拆成PureComonent,这样就有助于提高渲染性能,比如表单、文本域和复杂列表在同一个 render()中,表单域的输入字段改变会频繁地触发 setState() 从而导致组件重新 render()。而用于渲染复杂列表的数据其实并没有变化,但由于重新触发 render(),列表还是会重新渲染。

  • render
  • getSnapshotBeforeUpdate 在最近一次的渲染输出被提交之前调用。也就是说,在 render 之后,即将对组件进行挂载时调用。

它可以使组件在 DOM 真正更新之前捕获一些信息(例如滚动位置),此生命周期返回的任何值都会作为参数传递给 componentDidUpdate()。 如不需要传递任何值,就返回null

  • componentDidUpdate 会在更新后会被立即调用。首次渲染不会执行

包含三个参数,第一个是上一次props值。 第二个是上一次state值。 如果组件实现了 getSnapshotBeforeUpdate() 生命周期, 第三个是“snapshot” 参数传递

卸载:

  • componentWillUnMount 在组件即将被卸载或销毁时进行调用,可以在该生命周期取消网络请求,移除监听事件、清理定时器等操作。

在React16中componentWillMountcomponentWillReceivePropscomponentWillUpdate已经废弃。

为什么废弃呢,这就要讲到react 16中的React Fiber

什么是React Fiber

由于JS的单线程的特点,就不能让每个同步任务耗时太久,不然程序将会对其他输入没有响应。React更新过程就是犯了这个禁忌,为了改变这种现状,就出现了Fiber

Fiber是对React的核心算法进行重构。利用浏览器requestIdleCallback这个方法

window.requestIdleCallback()方法将一个(即将)在浏览器空闲时间执行的函数加入队列

将可中断的任务进行分片处理,让每一个小片的运行时间很短,这样唯一的线程就不会被独占。

因为这个过程有可能暂停然后再继续,所以这些生命周期钩子就有可能不执行或执行多次,这也就失去了这几个废弃钩子函数的原本意义,如果在这几个废弃钩子函数中使用setState也有可能触发多次重绘,影响性能。

什么是React Hooks

Hooksreact16.8中新添加的内容,它可以允许在不编写类的情况下使用state和其他React特性。使用Hooks,可以从组件中提取有状态逻辑,这样就可以独立的测试和重用它。

Hooks会取代render props 和高阶组件吗

目前是不会的,hooks是无法共享组件状态逻辑的。但在大多数情况下Hooks就足够了,可以减少树中的嵌套,提高性能。

  • React Hooks有哪些好处

1. 类定义会更复杂

不同生命周期会使逻辑变得分散而混乱
时刻需要关注this的指向问题
代码复用代价更高,高阶组件的使用会使整个组件树变得臃肿

2. 状态与UI隔离。由于Hooks的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义的hooks,组件中的状态和UI变得更为清晰和隔离。

  • 为什么不能在循环、条件或嵌套函数中调用Hooks

此时,react会抛出error。因为在react hooks底层设计的数据结构中,react用链表来严格保证hooks的顺序。而条件循环或者嵌套函数中,有可能会导致顺序变化,所以不能放在里面。

  • 列举几个常见的 Hook

1. useState: 用于定义组件的 State

2. useEffect:类定义中有许多生命周期函数, 而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里可以看做 componentDidMountcomponentDidUpdatecomponentWillUnmount的结合

3. useContext:获取context对象

4. useCallBack:缓存的是函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染, 具有性能优化的效果

5. useMemo:缓存的是函数返回的结果,避免依赖的组件每次都重新渲染

6. useRef:获取组件的真实节点

7. useReducer:提供类似Redux的功能

  • useEffect

useEffect可以看成是componentDidMountcomponentDidUpdatecomponentWillUnmount三者的结合。useEffect(callback, [source])接收两个参数

1. source参数不传,当组件中任何一个数据发生变化,useEffect这个钩子函数每次都会执行。

2. 传空数组,初始化时,会调用一次

3. 有值时,会监听值的变化而重新执行钩子函数。

  • useReducer

useReducer本质是一个纯函数,没有任何UI和副作用。它有两个参数,第一个参数接收的是函数,第二个参数接收的是初始化的值。函数总是会返回一个新的state,可以使用解构赋值更新state。它还有个action参数,用type来表示具体的行为类型,用payload提供操作的数据信息。

Class组件和Hooks的之间的区别

  • 最明显的区别是语法不同,函数组件只是一个普通的js对象,它可以返回JSX。类组件是一个继承React.ComponentJS类,它有一个render函数
  • 组件传参不同,在函数组件里面是把组件参数作为函数参数来传递的。而类组件中,需要this来引用。
  • 处理state不同,之前是只能在类组件里面处理state,在React16.8中,引用了react hook useState,许开发人员编写有state的函数组件
  • 生命周期不同,类组件是有componentDidMount生命周期的,在函数组件中,我们使用 useEffect hook 来替代componentDidMount

React组件通信方式

  • 父组件通过props向子组件传递需要的信息

  • 子组件在this.props绑定方法,父组件接收方法

  • 跨级组件层层组件传递props、使用context,也可以用一些全局的机制去实现通信,比如reduxmobx

什么是上下文Context:

Context通过组件树提供一个传递数据的方法,从而避免了一层一层手动传递props

react-router原理

参考 juejin.cn/post/688629…

首先我们得知道什么是单应用,其实就是使用一个html,一次性加载jscss等资源,所有页面都在一个容器页面下,页面切换实质是组件的切换。

react-router-dom和react-router和history库三者什么关系

**history** 可以理解为react-router的核心,也是整个路由原理的核心,里面集成了popState, history.pushState等底层路由实现的原理方法。

**react-router**可以理解为是**react-router-dom**的核心,里面封装了Router,Route,Switch等核心组件,实现了从路由的改变到组件的更新的核心功能,在我们的项目中只要一次性引入react-router-dom就可以了。

**react-router-dom**,在react-router的核心基础上,添加了用于跳转的Link组件,histoy模式下的BrowserRouterhhash模式下的HashRouter组件等。所谓**BrowserRouter和HashRouter,也只不过是用了history库中createBrowserHistory和createHashHistory方法**

history模式原理

  • 改变路由

history.pushState ,history.replaceState

  • 监听路由

一个文档的 history 对象出现变化时,就会触发 popstate 事件。 history.pushState 可以使浏览器地址改变,但是无需刷新页面。注意⚠️的是:用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()、history.forward()、history.go()方法。

hash模式原理

  • 改变路由

通过window.location.hash 属性获取和设置 hash 值。

  • 监听路由 window.addEventListener('hashchange',function(e){ /* 监听改变 */ })

**history**提供了核心api,如监听路由,更改路由的方法,保存路由状态state

**react-router**提供路由渲染组件,路由唯一匹配组件,重定向组件等功能组件。

当地址栏改变url,组件的更新渲染都经历了什么?

history模式做参考。当url改变,首先触发histoy,调用事件监听popstate事件, 触发回调函数handlePopState,触发history下面的setstate方法,产生新的location对象,然后通知Router组件更新location并通context上下文传递,switch通过传递的更新流,匹配出符合的Route组件渲染,最后由Route组件取出context内容,传递渲染更新页面。

当我们调用history.push方法,切换路由,组件的更新渲染又都经历了什么呢?

我们还是拿history模式作为参考,当我们调用history.push方法,首先调用historypush方法,通过history.pushState来改变当前url,接下来触发history下面的setState方法,接下来的步骤就和上面一模一样了,这里就不一一说了。

Redux

参考 juejin.cn/post/684490…

Redux是什么:

ReduxReact的一个状态管理库,它基于fluxRedux简化了React中的单向数据流。它将状态管理完全从React中抽象出来。

  • Redux是的诞生是为了给 React 应用提供「可预测化的状态管理」机制。
  • Redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store
  • 这个store里面保存一棵状态树(state tree)
  • 组件改变state的唯一方法是通过调用storedispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新
  • 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
  • 其它组件可以通过订阅store中的状态(state)来刷新自己的视图

redux是如何工作的:

React中,组件连接到redux ,如果要访问 redux,需要派出一个包含 id和负载(payload) 的 actionaction 中的 payload 是可选的,action 将其转发给 Reducer

reducer收到action时,通过 swithc...case 语法比较 actiontype。 匹配时,更新对应的内容返回新的 state

Redux状态更改时,连接到Redux的组件将接收新的状态作为props。当组件接收到这些props时,它将进入更新阶段并重新渲染UI

redux是怎么处理异步的

1. store本身的dispatch派发action更新数据这个动作是同步的。   

2. 所谓异步action,是通过引入中间件的方案增强dispatch后实现的。具体是applyMiddleware返回dispatch覆盖原始storedispatch,当action为函数时,进行定制的异步场景dispatch派发。   

3. 为何会采取这种中间件增强的模式,我个人看来一是集中在一个位置方便统一控制处理,另一个则是减少代码中的冗余判断模板。 

Mobx

参考 juejin.cn/post/685041…

他的原理非常简洁:

对于Mobx,本质就是在使用了被观察数据的组件上面套了一个父的组件,而这个父的组件是一个有状态组件。 然后通过观察者模式,发现数据更改时,通知观察者,然后观察者调用setState,更新Observer,从而最后达到刷新子组件的效果。

  • 事件调用Actions它是唯一允许修改state而且可能有其他副作用的函数。

  • 然后修改State它是一组可观察的状态,而且不应该包含冗余的数据(例如不会被更新的状态)

  • 更新Computed Value是一些纯函数,返回通过state可以推导出的值

  • 触发Reactions类似于 Computed Value,它允许副作用的产生(如更新 UI 状态)

常用装饰器

  • @observable将一个变量变得可观察

  • @observer 常用于React组件,可监视其render函数里使用的可观察变量,从而作出相应reactions

  • @autorun 常用于组件类或store类的constructor里,用来创建实例时,可监视其函数参数里使用的可观察变量,从而作出相应reactions,一般是将函数再执行一遍。

  • @when有条件的@autorun

  • @computed通过可观察变量经过纯函数计算得来的值,使用时才会计算,没有使用时不会去计算

  • @action能改变可观察变量值的操作(一般是函数方法)

Mobx 和 Redux 的比较

  • Redux 认为,数据的一致性很重要,为了保持数据的一致性,要求Store中的数据尽量范式化,也就是减少一切不必要的冗余,为了限制对数据的修改,要求 Store中数据是不可改的(Immutable),只能通过 action 触发reducer 来更新 Store

  • Mobx也认为数据的一致性很重要,但是它认为解决问题的根本方法不是让数据范式化,而是不要给机会让数据变得不一致。所以,Mobx 鼓励数据干脆就“反范式化”,有冗余没问题,只要所有数据之间保持联动,改了一处,对应依赖这处的数据自动更新,那就不会发生数据不一致的问题。

虽然 Mobx 最初的一个卖点就是直接修改数据,但是实践中大家还是发现这样无组织无纪律不好,所以后来 Mobx 还是提供了 action 的概念。和 Reduxaction 有点不同,Mobx 中的 action 其实就是一个函数,不需要做 dispatch ,调用就修改对应数据。

如果想强制要求使用 action,禁止直接修改 observable 数据,使用 Mobxconfigure ,如下:

import {configure} from 'mobx';

configure({enforceActions: true});

总结一下 ReduxMobx 的区别,包括这些方面:

  1. Redux鼓励一个应用只用一个 StoreMobx 鼓励使用多个 Store

  2. Redux 使用“拉”的方式使用数据,这一点和React是一致的,但 Mobx 使用“推”的方式使用数据,和 RxJS 这样的工具走得更近;

  3. Redux 鼓励数据范式化,减少冗余,Mobx 容许数据冗余,但同样能保持数据一致。

SetState

1. 合成事件

说到setState首先得了解什么是合成事件,合成事件是React为了解决跨平台,兼容性问题,自己封装的一套事件机制,代理了原生事件,像JSX中常见的onClickonChange这些都是合成事件。在源码中try finally语法中。setState是在try里面执行的,当try执行完后才会执行finally。这个时候才会去更新你的state,并渲染到ui上,所以,在合成事件中调用setState会立马拿不到state的值,这就导致了所谓的异步。但是可以通过setStatecallback函数拿到更新后的值。

2. 生命周期

其实还是和合成事件一样,当 componentDidmount 执行的时候,React内部并没有更新,执行完componentDidmount 后才去更新。这就导致在 componentDidmount 中是无法立即拿到setState后的值的

3. 原生事件

原生事件是指非React合成事件,比如原生自带的事件监听 addEventListener ,或者像document.querySelector().onclick 这种绑定事件的形式都属于原生事件。

原生事件的调用栈就比较简单,没有走合成事件的那一大堆逻辑,会直接触发click事件,所以在原生事件中setState后,能同步拿到更新后的state值。

4. setTimeout

setTimeout是可以在很多场景下使用的,比如说合成事件,钩子函数,原生事件中。但是不管是哪个场景下,基于event loop 的模型下,在 setTimeout 中里去 setState 总能拿到最新的state值。

总结 :

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setStatesetState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

当调用setState时,React render是如何工作的:

  • 首先是虚拟 DOM 渲染:当render方法被调用时,它会返回新的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下 React 是没有优化的。

  • 然后是原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的改变,使React变得更快。

Refs

React中的refs是干嘛用的

DOM节点或者React元素上使用Refs,就可以访问到节点。一般,props是父子组件交互的唯一方式,想要修改子组件,需要使用新的props渲染它。但当我们想要在某些情况下强制修改子代,可以使用Refs

如何创建Refs

在类组件中是用React.createRef()创建的,在函数组件中是用useRef创建的。把ref属性添加到React元素上,该元素便能使用refs,将事件数据传给父组件。

Refs使用场景

在某些情况下,我们可能需要修改子项,而不用新的props重新呈现它,这时候就需要使用refs,比如:

  • 与第三方DOM库集成
  • 触发命令式动画
  • 管理焦点,文本选择或者媒体播放

具体场景

参考 juejin.cn/post/684490…

  • React.createRef():当我们构建了一个按钮,当单击它时,该页面会自动聚焦在输入框
  • Ref中获取值:当我们创建了一个input框来输入值,然后单击提交按钮需要读取到这个值
  • Refs回调:当我们设置Ref时,React会调用这个函数,并将element作为第一个参数传递给它。也可以用此方法获取input标签的文本值
  • 转发Refs: Ref forwarding:复用组件库和高阶组件

在React中如何处理事件

为了解决跨浏览器的兼容性问题,React提供了跨浏览器的原生事件包装器SyntheticEvent,它还拥有和浏览器原生事件相同的接口,包括stopPropagation()preventDefault()

React其实不将事件附加到子节点本身。它使用单个事件侦听器监听顶层的所有事件。这对性能有好处,也意味着React在更新DOM时不需要跟踪事件监听器。

事件机制:

  • 当用户在为onClick添加函数时,React并没有将click事件绑定到DOM
  • 而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
  • 所以当事件触发的时候,对使用统一的分发函数dispatchEvent将制定函数执行。

state和props区别是啥

propsstate是普通的JS对象。虽然它们都影响渲染输出的信息,但是它们在组件方面的功能是不同的。

  • state是组件自己管理数据,控制自己的状态,可变
  • props是外部传入的数据参数,不可变
  • 没有state的叫做无状态组件,有state的叫做有状态组件
  • 多用props,少用state,也就是多写无状态组件

如何避免组件的重新渲染

  • React.memo():避免函数组件不必要的渲染
  • PureComponent:避免类组件不必要的渲染

这两种方法都依赖于传递给组件的props的浅比较,如果props没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。

我们可以使用React Profiler对性能进行测量

什么是高阶组件

参考 juejin.cn/post/684490…

高阶组件不是组件,是增强函数。如果一个函数接受一个或多个组件作为参数并且返回一个组件就可称之为高阶组件。、

高阶组件的形式:

1. 属性代理props proxy

// 无状态
function HigherOrderComponent(WrappedComponent) {
    return props => <WrappedComponent {...props} />;
}
// or
// 有状态
function HigherOrderComponent(WrappedComponent) {
    return class extends React.Component {
        render() {
            return <WrappedComponent {...this.props} />;
        }
    };
}

2. 反向继承

function HigherOrderComponent(WrappedComponent) {
    return class extends WrappedComponent {
        render() {
            return super.render();
        }
    };
}

高阶组件的缺点:

1. 静态方法丢失:

因为原始组件被包裹于一个容器组件内,也就意味着新组件会没有原始组件的任何静态方法

**2. refs 属性不能透传:**一般来说高阶组件可以传递所有的 props 给包裹的组件,但是有一种属性不能传递,它就是ref

  • 与其他属性不同的地方在于React对其进行了特殊的处理。 如果你向一个由高阶组件创建的组件的元素添加 ref 引用,那么 ref 指向的是最外层容器组件实例的,而不是被包裹的 WrappedComponent 组件。 那如果一定要传递 ref 的话,React 为我们提供了一个名为 React.forwardRefAPI 来解决这一问题

3. 反向继承不能保证完整的子组件树被解析:React 组件有两种形式,分别是 class 类型和 function 类型(无状态组件)。像反向继承的渲染劫持可以控制 WrappedComponent 的渲染过程,也就是说这个过程中我们可以对 elements treestatepropsrender() 的结果做各种操作。 但是如果渲染 elements tree 中包含了 function 类型的组件的话,这时候就不能操作组件的子组件了。

HOC可用于许多用例

  • 代码重用、逻辑和引导抽象
  • 渲染劫持
  • state抽象和操作
  • props处理

HOC应用场景

  • 利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别 和 页面元素级别

  • 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录

  • 页面复用

在构造函数中调用super并将props作为参数传入的作用是啥?

在调用super之前,子类构造函数无法使用this。调用后可在state中使用this.props

什么是受控组件和非受控组件

  • 受状态控制的组件,必须要有onChange方法,否则不能使用。受控组件可以赋予默认值(推荐使用受控组件)实现双向数据绑定
  • 非受控组件也就意味着我可以不需要设置它的state属性,而通过ref来操作真实的DOM

受控组件和非受控组件的区别是啥:

  • 受控组件是React控制的组件,并且是表单数据唯一的真实来源
  • 非受控组件是由DOM处理表单数据的地方,而不是在React组件中

尽管非受控组件通常更易于实现,因为只需使用refs即可从DOM中获取值,但通常建议优先选择受控组件,而不是非受控组件。因为受控组件支持即时字段验证,允许有条件的禁用启用按钮,强制输入格式等。

什么是JSX

JSX将原始HTML模板嵌入到JS代码中。JSX代码本身不能被浏览器读取,必须使用Babelwebpack等工具将其转换为传统的JS

React中的严格模式是什么

它是React的一种辅助组件,可以帮助我们编写更好的react组件。

  • 验证内部组件是否遵循某些推荐做法
  • 验证是否使用已经废弃的方法
  • 识别潜在的风险预防一些副作用

什么是prop drilling,如何避免

当有多层嵌套组件需要使用源组件的数据时,最简单的方法是将props从一层一层的传递,从源组件传递到深层嵌套组件,这叫prop drilling

为了避免它,一种常用的方法是使用React.creactContextuseContext。通过定义提供数据的Provider组件,允许嵌套的组件获得源数据

描述Flux与MVC

传统的MVC模式在分离数据方面做的很好,但是MVC架构经常遇到两个主要问题:

  • 数据流不够清晰:跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
  • 缺乏数据完整性:模型数据可以在任何地方发生冲突,从而在整个UI中产生不可预测的状况

使用Flux模式的复杂用户界面不再遭受级联更新,任何给定的React组件都能够根据store提供的数据重建其状态。Flux模式还通过限制对共享数据的直接访问来加强数据完整性

如何在ReactJS的Props上应用验证

在开发模式下,React将自动检查有设置props的组件,以确保有正确的数据类型。对于不正确的数据类型,就会在控制台中生成警告消息,但是生产模式中由于性能影响会禁用它。强制的propsisRequired定义的。

在React中使用构造函数和getInitialState有什么区别

它们本质的区别其实也就是ES6和ES5的区别

class MyCom extends React.Component{
    constructor(props){
        super(props);
        this.state={}
    }
}

等价于:

var MyCom = React.createClass({
    getInitialState(){
        return{}
    }
})

什么是纯函数

纯函数是不依赖,并且不会在其作用域之外修改变量状态的函数。

本质上,纯函数始终在给定相同参数的情况下返回相同的结果

如何避免在React重新绑定实例

  • 使用内联箭头函数或箭头函数
  • 使用带有Hooks的函数组件

React中元素与组件的区别

  • React元素(React element)

它是React中最小基本单位,我们可以使用JSX语法创建一个React元素

React元素不是真实的DOM元素,它仅仅是JS的普通对象,所以也没办法直接调用DOM原生API

除了JSX语法,我们还可以使用React.createElement()React.cloneElement()来构建React元素

  • React组件

React构建组件的方式:React.createClass()ES6class、无状态函数、PureComponent

  • 元素与组件的区别

组件是由元素构成的,元素是普通对象,而组件是纯函数

什么是状态提升

使用React经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件中进行管理,这就是状态提升。

React中的Portal是什么

它提供将子节点渲染到父组件以外的DOM节点的方式

ReactDOM.createPortal(child, container)

第一个参数是任何可渲染的React子元素。第二个参数则是一个DOM元素。

React16的错误边界Error Boundaries是什么

是为了防止部分UI中的JS错误破坏整个应用程序。

如何在React中使用innerHTML

增加dangerouslySetInnerHTML属性,并且传入对象的属性名叫_html

function App (props) {
    return <div dangerouslySetInnerHTML={{_html:'<span>1</span>'}}></div>
}

React16版本的reconcilation阶段和commit阶段是什么

  • 前面阶段主要是对current treenew treediff计算,找出变化部分。进行遍历、对比等。

  • commit阶段是对获取到的变化部分应用到真实的DOM树中,是一系列的DOM操作。不仅要维护更复杂的DOM状态,而且中断后再继续,会对用户体验造成影响。