React面试题整理

什么是React

1、React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
2、它遵循基于组件的方法,有助于构建可重用的UI组件。
3、它用于开发复杂和交互式的 Web 和移动 UI。
4、尽管它仅在 2015 年开源,但有一个很大的支持社区

React框架的特点

1.声明式设计:React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据变动时 React 能高效更新并渲染合适的组件

2.组件化: 构建管理自身状态的封装组件,然后对其组合以构成复杂的 UI。

3.高效:React采用虚拟DOM,最大限度地减少与DOM的交互。

4.灵活:无论你现在使用什么技术栈,在无需重写现有代码的前提下,都可以通过引入 React 来开发新功能。

React生命周期

1、生命周期是什么?

react 实例的生命周期,就是react实例从初始化,更新,到销毁的过程

2、React实例生命周期经历哪几个阶段

Initialization:在这个阶段,组件准备设置初始化状态和默认属性。

Mounting:react 组件已经准备好挂载到浏览器 DOM 中。这个阶段包括componentWillMount和componentDidMount生命周期方法。

Updating:在这个阶段,组件以两种方式更新,发送新的 props 和 state 状态。此阶段包括shouldComponentUpdate、componentWillUpdate和componentDidUpdate生命周期方法。

Unmounting:在这个阶段,组件已经不再被需要了,它从浏览器 DOM 中卸载下来。这个阶段包含 componentWillUnmount 生命周期方法。
除以上四个常用生命周期外,还有一个错误处理的阶段:
Error Handling:在这个阶段,不论在渲染的过程中,还是在生命周期方法中或是在任何子组件的构造函数中发生错误,该组件都会被调用。这个阶段包含了 componentDidCatch 生命周期方法

3、具体生命周期方法

constructor:构造函数里,可以做状态的初始化,接收props的传值

componentWillMount() 在渲染之前执行,在客户端和服务器端都会执行,相当于vue中的beforeMount

componentDidMount():渲染完毕,仅在第一次渲染后在客户端执行。

​ shouldComponentUpdate():根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false,可用于性能优化。默认情况下,它返回 true。

​ componentWillUpdate():即将更新。

​ componentWillReceiveProps(nextProps): 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。nextProps 是props的新值,而 this.props是旧值。

​ componentDidUpdate():在组件完成更新后立即调用。在初始化时不会被调用。 相当于vue中的updated

componentWillUnmount(): 即将卸载,可以做一些组件相关的清理工作,例如取消计时器、网络请求等

React和vue

相同点:

        1、都使用虚拟dom实现快速渲染

        2、都有属于自己的状态管理器(redux、vuex)

        3、都是轻量级框架

不同点:

        1、数据流:React属于单向数据流(MVC),vue属于双向数据绑定(MVVM)

        2、兼容性:React兼容性比vue好,vue不兼容IE8

        3、语法模版:React采用JSX,vue采用html模版语法(vue也可以引用JSX模版)

        4、css作用域:vue的css可以有组件的私有作用域,React则没有

        5、大型项目推荐使用React,小型则用vue

虚拟DOM的定义

虚拟DOM(Virtual DOM)是指一个虚拟的、内存中的DOM节点树,它是通过JavaScript对象来模拟真实的DOM结构,而不是直接操作真实的DOM。虚拟DOM通常会在每次页面渲染时被创建,通过对虚拟DOM的修改来实现对页面的更新。

什么是JSX

JSX是javascript的语法扩展。它就像一个拥有javascript全部功能的模板语言。它生成React元素,这些元素将在DOM中呈现。React建议在组件使用JSX。在JSX中,我们结合了javascript和HTML,并生成了可以在DOM中呈现的react元素。

JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS

class MyComponent extends React.Component {
  render() {
    let props = this.props;  
    return (
      <div className="my-component">
      <a href={props.url}>{props.name}</a>
      </div>
    );
  }
}

避免组件重新渲染的方法

        React.memo(): 这可以防止不必要地重新渲染函数组件

        PureComponent: 这可以防止不必要地重新渲染类组件

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

通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。

为什么不能直接更新state

如果试图直接更新 state ,则不会重新渲染组件。

 // 错误
 This.state.message = 'Hello world';

需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应

// 正确做法
This.setState({message: ‘Hello World’});

setState()是同步还是异步

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

state和props的区别


1.state 是组件自己管理数据,控制自己的状态,可变;
2.props 是外部传入的数据参数,不可变;
3.没有state的叫做无状态组件(函数组件),有state的叫做有状态组件;
4.多用 props,少用 state,也就是多写无状态组件(因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。)。

条件StateProps
1. 从父组件中接收初始值YesYes
2. 父组件可以改变值NoYes
3. 在组件中设置默认值YesYes
4. 在组件的内部变化YesNo
5. 设置子组件的初始值YesYes
6. 在子组件的内部更改NoYes

React 中的refs 

refs 提供了一种访问在render方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props 是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 refs。
咱们可以在组件添加一个 ref 属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。

class UnControlledForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          ref={(input) => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

高阶组件(HOC)

高阶组件(HOC)是接受一个组件并返回一个新组件的函数。高阶组件不是组件,是增强函数,可以输入一个元组件,返回出一个新的增强组件

const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOC 可以用于:
1.代码重用、逻辑和引导抽象
2.渲染劫持
3.state 抽象和操作
4.props 处理

类组件和函数组件的区别

类组件:可以使用其他特性,如状态 state 和生命周期钩子

函数组件:当组件只是接收 props 渲染到页面时,就是无状态组件,也被称为哑组件或展示组件。

区别:
1.类组件有 this,函数组件没有
2.类组件有生命周期,函数组件没有
3.类组件有状态 state,函数组件没有

函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

React如何进行组件/逻辑复用

高阶组件: 属性代理、反向继承

渲染属性

react-hooks

React 合成事件

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

在 React 中,所有事件都是合成的,不是原生 DOM 事件,但可以通过 e.nativeEvent 属性获取 DOM 事件。

React所有的事件绑定在container上(react17以后),而不是绑定在DOM元素上(作用:减少内存开销,所有的事件处理都在container上,其他节点没有绑定事件)
React自身实现了一套冒泡机制,不能通过return false阻止冒泡
React通过SytheticEvent实现了事件合成

为什么使用合成事件?

  1. 进行浏览器兼容,实现更好的跨平台:React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React 提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。
  2. 避免垃圾回收:事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。
  3. 方便事件统一管理和事务机制

合成事件原理

React合成事件的工作原理大致可以分为两个阶段:

        1、事件绑定

        2、事件触发

在React17之前,React是把事件委托在document上的,React17及以后版本不再把事件委托在document上,而是委托在挂载的容器上了。当真实的dom触发事件时,此时构造React合成事件对象,按照冒泡或者捕获的路径去收集真正的事件处理函数,在此过程中会先处理原生事件,然后当冒泡到document对象后,再处理React事件。

React17中,会先执行所有捕获事件后,再执行所有冒泡事件。

事件插件(EventPlugin)

React合成事件和原生事件执行的过程,两者其实是通过一个叫事件插件(EventPlugin)的模块产生关联的,每个插件只处理对应的合成事件,比如onClick事件对应SimpleEventPlugin插件,这样React在一开始会把这些插件加载进来,通过插件初始化一些全局对象,比如其中有一个对象是registrationNameDependencies,它定义了合成事件与原生事件的对应关系如下:

{
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    ...
}

registrationNameModule对象指定了React事件到对应插件plugin的映射:

{
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    ...
}

plugins对象就是上述插件的列表。在某个节点渲染过程中,合成事件比如onClick是作为它的prop的,如果判断该prop为事件类型,根据合成事件类型找到对应依赖的原生事件注册绑定到顶层document上,dispatchEvent为统一的事件处理函数。

事件触发

当任意事件触发都会执行dispatchEvent函数,比如上述事例中,当用户点击Child的div时会遍历这个元素的所有父元素,依次对每一级元素进行事件的收集处理,构造合成事件对象(SyntheticEvent–也就是通常我们说的React中自定义函数的默认参数event,原生的事件对象对应它的一个属性),然后由此形成了一条「链」,这条链会将合成事件依次存入eventQueue中,而后会遍历eventQueue模拟一遍捕获和冒泡阶段,然后通过runEventsInBatch方法依次触发调用每一项的监听事件,在此过程中会根据事件类型判断属于冒泡阶段还是捕获阶段触发,比如onClick是在冒泡阶段触发,onClickCapture是在捕获阶段触发,在事件处理完成后进行释放。

更改事件委托(React v17.0)

自React发布以来, 一直自动进行事件委托。当 document 上触发 DOM 事件时,React 会找出调用的组件,然后 React 事件会在组件中向上 “冒泡”。但实际上,原生事件已经冒泡出了 document 级别,React 在其中安装了事件处理器。

但是,这就是逐步升级的困难所在。在 React 17 中,React 将不再向 document 附加事件处理器。而会将事件处理器附加到渲染 React 树的根 DOM 容器中。

在 React 16 或更早版本中,React 会对大多数事件执行 document.addEventListener()。React 17 将会在底层调用 rootNode.addEventListener()。

由于事件对象可能会频繁创建和回收在React16.x中,合成事件SyntheticEvent采用了事件池,合成事件会被放进事件池中统一管理,这样能够减少内存开销。React通过合成事件,模拟捕获和冒泡阶段,从而达到不同浏览器兼容的目的。另外,React不建议将原生事件和合成事件一起使用,这样很容易造成使用混乱。

由于17版本事件委托的更改,现在可以更加安全地进行新旧版本 React 树的嵌套。请注意,要使其正常工作,两个版本都必须为 17 或更高版本,这就是为什么强烈建议升级到 React 17 的根本原因。

React事件处理为什么要手动绑定this

react组件会被编译为React.createElement,在createElement中,它的this丢失了,并不是由组件实例调用的,因此需要手动绑定this。

为什么不能通过return false阻止事件的默认行为

因为React基于浏览器的事件机制实现了一套自己的事件机制,和原生DOM事件不同,它采用了事件委托的思想,通过dispatch统一分发事件处理函数。

React怎么阻止事件冒泡

1、阻止合成事件的冒泡用e.stopPropagation()

2、阻止合成事件和最外层document事件冒泡,使用e.nativeEvent.stoplmmediatePropogation()

3、阻止合成事件和除了最外层document事件冒泡,通过判断e.target避免

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

1.将事件处理程序定义为内联箭头函数

 <button onClick={() => { this.setState({ xx: xx }); }}>Submit</button>

2.使用箭头函数来定义方法

handleSubmit = () => { this.setState({ isFormSubmitted: true }); }

3.使用带有 Hooks 的函数组件

React组件之间通信

父组件向子组件通讯:父组件可以向子组件通过传 props 的方式,向子组件进行通讯

子组件向父组件通讯:props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中

兄弟组件通信:找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信

跨层级通信:Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过

发布订阅模式:发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入event模块进行通信

全局状态管理工具:借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态

React Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

React Hooks 如何模拟组件的生命周期?

模拟 componentDidMount,useEffect 依赖 []
模拟 componentDidUpdate,useEffect 无依赖,或者依赖 [a, b]
模拟 componentWillUnMount,useEffect 返回一个函

componentDidMount:useEffect(()=>{console.log('第一次渲染时调用')},[]) 

componentDidUpdate:useEffect(()=>{console.log('任意状态改变')})
                    useEffect(()=>{console.log('指定状态改变')},[a,b])  

componentWillUnmount:useEffect(()=>{...return()=>{ //组件卸载前}}) 

React Hooks中的常用钩子

userState():用于函数组引入状态。

useReducer():是另一种让函数组件保存状态的方式。

useContext() :在跨组件层级获取数据时简化获取数据的代码。

userEffects():让函数型组件拥有处理副作用的能力,类似生命周期函数。

useMemo(): 缓存计算结果,如果检测值没有发生改变,即使组件重新进行渲染,也不会重新计算。可以用于hooks性能优化。

useCallback():性能优化,缓存函数,是组件重新渲染事得到相同的函数实例。

useRef() :获取DOM元素。

setState和useState的区别

setState( updater, [callback] )

        updater:object/function - 用于更新数据

        callback:function - 用于获取更新后最新的 state 值

        构造函数是唯一建议给 this.state 赋值的地方

        不建议直接修改 state 的值,因为这样不会重新渲染组件

        自动进行浅合并(只会合并第1层)

        由于 setState() 异步更新的缘故,依赖 state 旧值更新 state 的时候建议采用传入函数的方式

useState(initState)

        const [ state , setState ] = useState(initState)

        state:状态

        setState(updater) :修改状态的方法

        updater:object/function - 用于更新数据

        initState:状态的初始值

React Hooks的优缺点

优点:

1、简洁: React Hooks解决了HOC和Render Props的嵌套问题,更加简洁

2、解耦: React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦

3、组合: Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千

4、函数友好: React Hooks为函数组件而生,从而解决了类组件的几大问题:

        1、this 指向容易错误

        2、分割在不同声明周期中的逻辑使得代码难以理解和维护

        3、代码复用成本高(高阶组件容易使代码量剧增)

缺陷:

1、额外的学习成本(Functional Component 与 Class Component 之间的困惑)

2、写法上有限制(不能出现在条件、循环中),并且写法限制增加了重构成本

3、破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)

4、在闭包场景可能会引用到旧的state、props值

5、内部实现上不直观(依赖一份可变的全局状态,不再那么“纯”)

6、React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)

React Redux

Redux 是一个独立的 JavaScript 状态管理库

为什么需要Redux

React作为一个组件化开发框架,组件之间存在大量通信,有时这些通信跨越多个组件,或者多个组件之间共享一套数据,简单的父子组件间传值不能满足我们的需求,自然而然地,我们需要有一个地方存取和操作这些公共状态。而redux就为我们提供了一种管理公共状态的方案。我们希望公共状态既能够被全局访问到,又是私有的不能被直接修改。

redux的三个API:

        1)getState: 返回当前状态

        2)dispatch: 有条件地、具名地修改store的数据(修改的规则从dispatch中抽离出来,就成为了Reducer)

        3)subscribe: 订阅store更新(观察者模式)

redux三大原则:

        1)单一数据源:整个应用的state都储存在reducer中,

        2)state是只读的

        3)使用纯函数来修改

Redux的实现流程

用户页面行为触发一个Action,然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。Reducer 会返回新的 State 。每当state更新之后,view会根据state触发重新渲染。

Redux的实现原理

Redux作为一个通用模块,主要还是用来处理应用中state的变更,通过react-redux做链接,可以在React+Redux的项目中将两者结合的更好。

react-redux是一个轻量级的封装库,它主要通过两个核心方法实现:

1、Provider:从最外部封装了整个应用,并向connect模块传递store。

2、Connect:

        1、包装原组件,将state和action通过props的方式传入到原组件内部。

        2、监听store tree变化,使其包装的原组件可以响应state变化

redux和vuex比较


相同点:

1、数据驱动视图,提供响应式的视图组件

2、都有virtual DOM, 组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范

3、都支持服务端渲染  

4、都有native解决方案,reactnative(facebook团队) vs weex(阿里团队)

不同点:

1、vuex是一个针对vue优化的状态管理系统,而redux仅是一个常规的状态管理系统(Redux)与React框架的结合版本。

2、开发模式:React本身,是严格的view层,MVC模式;Vue则是MVVM模式的一种方式实现

3、数据绑定:Vue借鉴了angular,采取双向数据绑定的方式;React,则采取单向数据流的方式

4、数据更新:Vue采取依赖追踪,默认是优化状态:按需更新;

     React在则有两种选择:
    1)手动添加shouldComponentUpdate,来避免冗余的vdom,re-render的情况
    2)Components 尽可能都用 pureRenderMixin,然后采用 redux 结构 + Immutable.js

5.社区:react相比来讲还是要大于vue,毕竟背后支撑团队不同。facebook vs 个人!当然目前vue的增长速度是高于react的增速,不知道未来的发展趋势是如何。

React Router

React Router 是 React 官方提供的 React 应用程序的导航解决方案。它是一个独立的第三方库,它可以让你构建不同的 URL,让你的应用程序具有类似于多页面的表现形式,并利用 URL 实现导航和状态管理。

优点:

1、风格:与React融为一体,专为React量身打造,编码风格与React保持一致,例如路由的配置可以通过component来实现

2、简单:不需要手工维护路由state,使代码变得简单

3、强大:强大的路由管理机制

4、路由配置:可以通过组件、配置对象来进行路由的配置

5、路由切换:可以通过Redirect进行路由的切换

6、路由加载:可以同步记载,也可以异步加载,这样就可以实现按需加载

7、使用方式:不仅可以在浏览器端使用,也可以在服务器使用

缺点:

API不太稳定,再升级版本的时候需要进行代码变动

React Context

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

React native

React native 基于 JavaScript 开发的一个 可以开发原生app的这么一个集成框架,它兼容开发 iOS 和 Android能够实现一套代码,两个系统都能使用,方便维护,相比于web前端的 react ,react-native更好的提供了一些调用手机硬件的API,可以更好的开发移动端,现在react-native它的生态环境也是越来越好,基本上可以完全实现原生开发,

但是现在好多的应用还是用它来套壳 (原生 + web前端),用它来做有些路由,和框架的搭建,然后里面内容来使用前端的react来实现,这样一来让维护更方便,开发更快捷

diff算法

1、把树形结构按照层级分解,只比较同级元素。

2、通过给列表结构的每个单元添加的唯一key,进行区分同层次的子节点的比较。

3、React只会匹配相同class的component(这里的class是指组件的名字)。

4、合并操作,调用component的setState方法的时候,React将其标记为dirty,到每一个事件循环结束,React检查所有标记dirty的component重新绘制。

5、选择性渲染。可以重写shouldComponentUpdate提高diff的性能。

什么是Fiber

简单来说,dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行。

React 会把 vdom 树转成 fiber 链表,因为 vdom 里只有 children,没有 parent、sibling 信息,而 fiber 节点里有,这样就算打断了也可以找到下一个节点继续处理。fiber 结构就是为实现并发而准备的。
按照 child、sibling、sibling、return、sibling、return 之类的遍历顺序,可以把整个 vdom 树变成线性的链表结构,一个循环就可以处理完。

vue不需要fiber是因为他使用nextTick来异步决定什么时候执行render。

复杂来说:

React 是靠数据驱动视图改变的一种框架,它的核心驱动方法就是用其提供的 setState 方法设置 state 中的数据从而驱动存放在内存中的虚拟 DOM 树的更新。

更新方法就是通过 React 的 Diff 算法比较旧虚拟 DOM 树和新虚拟 DOM 树之间的 Change ,然后批处理这些改变。

在 Fiber 诞生之前,React 处理一次 setState()(首次渲染)时会有两个阶段:

  • 调度阶段(Reconciler):这个阶段React用新数据生成新的 Virtual DOM,遍历 Virtual DOM,然后通过 Diff 算法,快速找出需要更新的元素,放到更新队列中去。
  • 渲染阶段(Renderer):这个阶段 React 根据所在的渲染环境,遍历更新队列,将对应元素更新。在浏览器中,就是更新对应的 DOM 元素。

表面上看,这种设计也是挺合理的,因为更新过程不会有任何 I/O 操作,完全是 CPU 计算,所以无需异步操作,执行到结束即可。

这个策略像函数调用栈一样,会深度优先遍历所有的 Virtual DOM 节点,进行 Diff 。它一定要等整棵 Virtual DOM 计算完成之后,才将任务出栈释放主线程。对于复杂组件,需要大量的 diff 计算,会严重影响到页面的交互性。

为了解决这个问题,react推出了Fiber,它能够将渲染工作分割成块并将其分散到多个帧中。同时加入了在新更新进入时暂停,中止或重复工作的能力和为不同类型的更新分配优先级的能力。

至于上面提到的为什么会影响到用户体验,这里需要简单介绍一下浏览器的工作模式:

因为浏览器的页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿,转换成时间就是16ms内如果当前帧内执行的任务没有完成,就会造成卡顿。

一帧中执行的工作主要以下图所示的任务执行顺序单线程依次执行。

如果其中一项任务执行的过久,导致总时长超过了16ms,用户就会感觉到卡顿了

上面提到的调和阶段,就属于js的执行阶段。如果调和时间过长导致了这一阶段执行时间过长,那么就有可能在用户有交互的时候,本来应该是渲染下一帧了,但是在当前一帧里还在执行 JS,就导致用户交互不能马上得到反馈,从而产生卡顿感。

React 为了解决这个问题,根据浏览器的每一帧执行的特性,构思出了 Fiber 来将一次任务拆解成单元,以划分时间片的方式,按照Fiber的自己的调度方法,根据任务单元优先级,分批处理或吊起任务,将一次更新分散在多次时间片中,另外, 在浏览器空闲的时候, 也可以继续去执行未完成的任务, 充分利用浏览器每一帧的工作特性。

这样 React 更新任务就只能在规定时间内占用浏览器线程了, 如果说在这个时候用户有和浏览器的页面交互,浏览器也是可以及时获取到交互内容。

React性能优化

1、前端按需加载:懒加载(从16.6.0后React提供lazy和Suspense来实现懒加载)、懒渲染、虚拟列表

2、批量更新:在React18.0中的并发模式,可以批量更新setState,之前的版本需要自己实现

3、按优先级更新,及时相应用户

4、利用debounce、throttle避免重复回调

5、缓存优化

6、避免不必要的组件更新:

        1)PureComponet、React.memo

        2)shouldComponetUpDate

        3)useMemo、useCallback

        4)使用发布-订阅者模式跳过中间组件的Render

        5)状态下放,缩小影响范围:如果一个状态只是在部分子树中应用,那就将子树提取出          来做成组件,把该状态放到提取的组件里

        6)列表项实用key属性

        7)Hooks按需更新

7、提交阶段性能优化:避免在componentDidMount()、componentDidUpdate()中更新组件state

        

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值