React 高频面试题-大厂必问

React

对于react的理解

一.是什么

React,用于构建用户界面的JavaScript库,只提供UI层面的解决方案

遵循组件设计模式,声明式编程范式和函数式编程概念,以使前端应用程序更高效

使虚拟 DOM 来有效的操作 DOM ,遵循从高阶到低阶组件的单向数据流;

帮助我们将界面分成各个独立的小块,每一个块就是组件,这些组件之间可以组合,嵌套,构成整体页面,

react类组件使用一个名为render() 的方法或者函数组件 return,接收输入的数据并返回需要展示的内容.

二.特性

  • JSX语法

  • 单向数据绑定

  • 虚拟DOM

  • 声明式编程

  • Component

声明式编程

声明式编程是一种编程范式,他关注的是你要做什么,而不是如何做

他表达逻辑而不显示的定义步骤,这意味着我们需要根据逻辑的计算来声明要显示的组件

实现一个标记的地图

通过命令是创建地图,创建标记,以及在地图上添加的标记的步骤如下

//创建地图
const map =new Map.map(document.getElementById('map')),{
    zoom:4,
    center:{lat,lng}
}
​
//创建标记
const marker = new Map.marker({
    position:{lat,lng},
    title:'Hello Marker'
})
​
//地图上添加标记
marker.setMap(map)

而用react 实现上述功能规则如下:


    

Component

react 中,一切皆为组件.通常将应用程序的整个逻辑分解为小的单个部分.我们将每个单独的部分称为组件

组件可以是一个函数或者是一个类,接收数据输入,处理它并返回在 UI 中呈现的 React 元素

一个组件该有的特点如下:

  • 可组合:每个组件易于和其他组件一起使用,或者嵌套在另一个组件内部

  • 可重用:每个组件都具有独立功能的,它可以被使用在多个UI场景

  • 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护

三.优势

  • 高效灵活

  • 声明式的设计,简单使用

  • 组件式开发,提高代码复用率

  • 单向响应的数据流会比双向绑定的更安全,速度更快

Real DOM 和 Virtual DOM 的区别? 优缺点?

一.是什么

Real DOM ,真实DOM,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个节点都是一个真实 DOM 结构

Virttual DOM, 本质上是以 JavaScript对象形式存在的对 DOM 的描述(本质上是JS对象,是真实DOM的抽象)

创建虚拟 DOM 目的就是为了更好将虚拟的节点渲染到页面视图中,虚拟 DOM 对象的节点与真实 DOM 的属性一一照应

在 react中, JSX 是其一大特征,可以让你在 JS 中通过使用 XML 的方式去直接声明界面的 DOM 结构

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
  ,
  document.getElementById('root')
);

ReactDOM.render() 用于将你创建好的虚拟DOM 节点插入到某个真实节点上,并渲染到页面上

JSX 实际是一个语法糖,在使用过程中会被 Babel 进行编译转化成 JS 代码, JSX 就是为了简化直接调用 ReactDOM.render() 方法:

  • 第一个参数是标签名

  • 第二个参数是个对象,里面存着标签的一些属性

  • 第三个参数是节点中的文本

JSX 通过 Babel 的方式转化成 ReactDOM.render() 执行,返回值是一个对象,也就是虚拟 DOM

二.区别

两者的区别如下:

  • 虚拟DOM 不会进行重排与重绘操作,而 真实DOM 会频繁重排与重绘

  • 虚拟DOM 的总损耗是''虚拟DOM 增删改查+真实DOM 差异增删改+重排与重绘'',真实DOM 的总耗损是''真实DOM 完全增删改+重排与重绘''

三.优缺点

真实DOM 的优势:

  • 易用

缺点

  • 效率低,解析速度慢,内存占用量过高

  • 性能差:频繁操作真实DOM ,易于导致重排与重绘

虚拟DOM 的优势:

  • 简单方便:如果使用手动操作真实DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难

  • 性能方面:使用虚拟DOM,能够有效避免真实DOM 树 频繁更新,减少多次引起重绘与回流,提高性能

  • 跨平台:React 借助虚拟DOM,带来了跨平台的能力,一套代码多段运行

缺点:

  • 在一些性能要求极高的应用中虚拟DOM 无法进行针对性的机制优化

  • 首次渲染大量DOM时,由于多了一层虚拟DOM 的计算,速度比正常稍慢

state和props的区别

一.state

一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态即是 state ,一般在 constructor 中初始化

当需要修改里面的值的状态需要通过调用 setState 来改变,从而达到更新组件内部数据的作用,并且重新调用组件 render 方法,

setState 还可以接收第二个参数,他是一个函数,会在 setState 调用完成并且组件开始重新渲染时 被调用,可以用来监听渲染是否完成

二.props

react 的核心思想就是组件思想,页面会被切分成一些独立的,可复用的组件,组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是 props,所以可以把props 理解成从外部传入组件内部的数据

react 具有单项数据流的特性,所以他的主要作用是从父组件向子组件中传递数据

props 除了可以传字符串,数组,还可以传递对象,数组甚至是回调函数

在子组件中, props在内部不可变的,如果想要改变它看,只能通过外部组件传入新的 props来重新渲染子组件,否则子组件的 props 和展示形式不会改变

三.区别

相同点:

  • 两者都是JS对象

  • 两者都是用于保存信息

  • props和state都能触发渲染更新

区别:

  • props 是外部传递给组件的,而state是在组件内组件自己管理的,一般在constructor中初始化

  • props在组件内部是不可修改的,不可变,而state在组件内部可以进行修改,可变

  • 没有state的叫做无状态组件,有state的叫做有状态组件

  • 多用props,少用state,也就是多写无状态组件

super()和super(props)的区别

React 中,类组件基于 ES6,所以在constructor中必须使用super

在调用 super 过程,无论是否传入props, React 内部都会将props赋值给组件实例 props属性值

如果只调用super(), 那么 this.props 在super()和构造函数结束之间仍是undefined

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

在调用super() 方法之前,子类构造函数无法使用this引用,ES6子类也是如此.

将props作为参数传递给super() 调用的主要援用是在子构造函数能够this.props来获取传入的props

React中的setState执行机制

一.是什么

一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state

当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用

执行setState方法更新state状态,然后执行render 函数,从而导致页面的视图更新

注意: 直接修改state,不会重新渲染组件

二.语法

setState 有两个参数,第一个参数可以是对象或者一个函数,第二个参数是一个回调函数,用于可以事实的获取到更新之后的数据.

setState在react中是异步的,为了自身性能

  1. 当调用setState(),react并不会马上修改state,

  2. 而是把这个对象放在一个更新队列里面

  3. 稍后才会从队列当中把新的状态提取出来合并到state当中,然后再触发组件更新

    注意: 连续调用多次setState 修改同一数据,但是最终之一触发最后一次

setState的第二个参数,可以传递一个回调函数,这个回调函数会在数据更新完,DOM渲染完执行

setState()伪异步 是react刻意为之,为了节省性能
1.修改数据
2.更新视图
react的控制范围内,比如:事件处理函数,生命周期函数...都是异步的
​
setState如果在react的生命周期中或者是事件处理函数中,表现出来的是异步的
setState如果是在setTimeout/setInterval或者原生事件中,表示出现是同步的

总结:setState 是同步的方法,但是react为了性能优化,所以setState在react的事件中表现出来的是异步的

React的事件机制

一:是什么

React 基于浏览器的事件机制自身实现了一套事件机制,包括事件注册,事件的合成,事件冒泡,事件派发等.在 React中这套事件机制被称之为合成事件

合成事件(SyntheticEvent)

合成事件是React 模拟原生DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器

根据 W3C 规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口

如果想要获取原生DOM 事件,可以通过 e.nativeEvent属性

react事件和原生事件的区别

  • 事件名称命名方式不同

  • 事件处理函数书写不同

    虽然 onClick 看似绑定到DOM 元素上,但实际并不会把事件处理代理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件去监听

    这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数,但组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象

    当事件发生时,首先被这个统一的事件监听器处理,然后再映射里找到真正的事件处理函数并调用,这样做简化了事件处理和回收机制,效果也有很大提升

二.执行顺序

  1. 原生事件:子元素DOM 事件监听

  2. 原生事件:父元素DOM事件监听

  3. React事件:子元素事件监听

  4. React事件:父元素事件监听

  5. 原生事件:document DOM 事件监听

结论:

  • React 所有事件都挂载在document对象上

  • 当真实DOM 元素触发事件,会冒泡到document对象后,在处理React事件

  • 所有会先执行原生事件,然后处理React事件

  • 最后真正执行document上挂载的事件

所以想要组织不同时间段的冒泡行为,对应使用不同的方法,对应如下:

  • 阻止合成事件间的冒泡,用e.stopPropagetion

  • 阻止合成事件与外层docunment上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation()

  • 阻止合成事件与最外层document上的原生事件上的冒泡,通过判断e.target来避免

document.body.addEventListener('click', e => {   
    if (e.target && e.target.matches('div.code')) {  
        return;    
    }    
    this.setState({   active: false,    });   }); 
}

三.总结

React 事件机制结论

  • React上注册的事件最终会绑定到document这个DOM上,而不是Reat组件对应的DOM(减少内存开销就是因为所有的事件都绑定在document上,其他节点上没有绑定事件)

  • React自身实现了一套事件冒泡机制,所有者也就是为什么我们 event.stopPropagetion()无效的原因

  • React通过队列的形式,从出发的组件想组件回溯,然后调用他们JSX中定义的callback

  • React有一套自己的合成事件SyntheicEvent

React事件绑定的方式有哪些?区别?

一.是什么

事件名首字母大写,{} 包裹

二.如何绑定

为了解决输出this的问题,常见的绑定方式:

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

  2. 使用箭头函数来定义事件方法(优,避免多次渲染)

  3. 使用bind绑定this指向

  4. 使用带有Hooks的函数组件(优)

React构建组件的方法有哪些

一.是什么

组件就是把图形,非图形的各种逻辑抽象为一个统一的概念(组件)来实现开发的模式

在react中,一个类,一个函数都可以视为一个组件

组件的优势:

  • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历,事件,范围等组件作具体实现

  • 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移出组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所有逻辑会比分析这个系统要简单

  • 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

二.如何构建

React 目前来讲,组件的创建主要分成三种方式

  • 函数式创建

  • 通过React.createClass方法创建

  • 继承React.Component创建

三.区别

由于 React.createClass 创建的方式过于冗杂,并不建议使用

而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态组件:

  • 对于一些无状态的组件创建,建议使用函数式创建的方法

  • 由于 React hook 的出现,函数式组件创建的组件通过使用hook方法也能使之称为有状态组件,再加上目前推崇函数式编程,所有这里建议都使用函数式的方式来创建组件

在考虑组件的选择原则上,能用无状态组件则用无状态组件

React中组件之间如何通信

一.是什么

组件间通信即指组件通过某种方式来传递信息,访问组件之间的数据

二.如何通信

  • 父传子

  • 子传父

  • 兄弟组件通信(将共享状态提升到最近的公共组件中,由公共父组件管理这个状态)

  • 跨组件通信(createContext, Provider提供状态,Consumer接收状态)

  • 非关系组件(Redux)

三.总结

由于 React 是单向数据流,主要思想是组件不会改变接收的数据,只会监听数据的变化,当数据发生改变时他们使用接收到的新值,而不是去修改已有的值

React中的key

一.是什么

渲染列表的时候每一个族元素都需要一个唯一的key值

二.作用

React和vue一样,也存在Diff算法,而元素key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的元素渲染

因此 key 的值需要为每一个元素赋予一个确定的标识

  1. key的作用主要是为了高效的更新虚拟DOM,提高渲染性能

  2. key属性可以避免数据混乱的情况出现

三.原理

  1. React实现一套虚拟DOM,使我们不直接操作DOM元素只操作数据,就可以重新渲染页面,而隐藏在背后的原理就是高效的diff算法

  2. 当页面数据发生改变时候,Diff算法只会比较同一层级的节点,

  3. 如果节点类型不同,直接干掉前面的节点,在创建并插入新的节点,不会再比较这个节点后面的子节点

    如果该节点类型相同,则会重新设置该节点属性,从而实现节点更新

  4. 使用key给每个节点做一个唯一标识,DIff算法就可以正确识别此节点,''就地更新''找到正确的位置插入新的节点

React 中refs的理解?应用场景?

一.是什么?

Refs 在计算机中被称为弹性文件系统

React 中的Refs 提供了一种方式,允许我们访问DOM节点或者在render方法中创建的React元素(组件)

本质为 ReactDOM.render() 返回的组件实例,如果是渲染组件则返回如果渲染dom则返回的是具体的dom节点

二.如何使用

创建ref的形式

  1. 传入字符串,使用时通过this.refs.传入的字符串的格式获取对应的元素

  2. 传入对象,对象是通过React.createRef() 方式创建出来,使用获取到创建的对象中存在current属性就是对应的元素

  3. 传入函数,该函数会在DOM 被挂载时进行回调,这个函数会传入一个元素对象,可以自己保存,使用时直接拿到之前保存的元素对象即可

  4. 传入hook,hook是通过useRef()方式创建,使用时通过生成hook对象的current属性就是对应的元素

三.应用场景

在某些情况下,我们会通过使用refs来更新组件,但这种方式并不推荐,更多情况我们是通过props与state的方式方式进行去重新渲染子元素

过多使用refs, 会使组件的实例或者是DOM 结构暴露,违反组件封装的原则

使用场景:

  • 对DOM 元素的焦点控制,内容选择,控制

  • 对DOM元素的内容设置及媒体播放

  • 对DOM 元素的操作和对组件实例的操作

  • 继承第三方DOM 库

对于React中类组件和函数组件的理解?有什么区别?

一.类组件

通过使用ES6 类的编写形式去编写组件,该类必须继承React.Component

如果想要访问组件传递过来的参数,可通过this.props 的方式去访问

在组件中必须实现render 方法,在return中返回React对象

二.函数组件

通过函数编写的形式去实现一个React组件,是React中定义组件最简单的方式

函数第一个参数为props 用于接收父组件传递过来的参数

三.区别

针对两种React 组件,其区别主要分别以下几大方向

  • 编写方式

  • 状态管理

    类组件通过setState来管理state

    函数组件(hooks) 使用useState 生成state 以及管理state的方法

  • 生命周期

    类组件有生命周期,

    函数组件使用useEffect代替生命周期的作用

  • 调用方式

    类组件需要进行实例化,然后调用实例对象的render方法

    函数组件直接执行的函数返回结果,性能高

  • 获取渲染的值

    props在react中是不可变的所以他永远不会改变,但是this是可变的

    类组件通过this.props 获取的是最新的数据

    函数组件没有this,所有props中的内容还是之前的数据

总结:

函数组件相比如类组件没有this指向以及生命周期的问题

类组件使用的时候需要实例化,函数组件直接执行函数返回的结果,对比函数组件性能高

函数组件语法更短,更简单,这使得它更容易开发,理解,测试

对受控组件和非受控组件的理解?应用场景

一.受控组件

受控组件就是react组件的状态来控制表单元素的值

需要提供两个属性:

  1. 绑定value

  2. onChange事件

二.非受控组件

非受控组件是通过手动操作DOM的方式来控制

三.应用场景

大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由 React 组件赋值处理

如果选择非受控组件的话,控制能力较弱,表单数据就由DOM 本身处理,但更加方便快捷,代码量少

对高阶组件的理解?应用场景?

一.是什么

高阶组件,至少满足下列一个条件的函数

  • 接受一个或多个函数作为输入

  • 输出一个函数

React 中,高阶组件即接受一个或多个组件作为参数并且返回一个组件,本质也就是一个函数,并不是一个组件

高阶组件的这种实现方法,本质是一个装饰者设计模式

二.如何编写

通过对传入的原始组件 做一些你想要的操作(比如操作props,提取state,给原始组件包裹其他元素等),从而加工出想要的组件

把通用的逻辑放在高阶组件中,对组件实现一致的结果,从而实现代码的复用

所以,高阶组件的主要功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用

但在使用高阶组件的同时,一般遵循一些约定,如下:

  • props保持一致

  • 你不能在函数式(无状态)组件上使用ref属性,因为它没有实例

  • 不要以任何方式改变原始组件 WrappedComponent

  • 透传不相关props属性给被包裹的组件WrappedComponet

  • 不要再 render( ) 方法中使用高阶组件

  • 使用compose组合高阶组件

这里需要注意的是,高阶组件可以传递所有的props,但是不能传递ref

如果向一个高阶组件添加refs引用,那么ref指向的是最外层容器组件实例的,而不是被包裹的组件,如果需要传递refs的话,则使用React.forwardRef

三.应用场景

高阶组件能够提高代码的复用性和灵活性,在实际应用中,常常用于与核心业务无关但又在多个模块使用的功能,如权限控制,日志记录,数据校验,异常处理,统计上报等

对React Hooks的理解?解决了什么问题?

一.是什么

Hooks 是React 16.8的新增特性. 它可以让你在不编写class的情况下使用 state 以及其他的React特性

为什么引入Hook,官方给出的动机是解决长时间使用和维护 React 过程中常遇到的问题,例如:

  • 难以重用和共享组件中的与状态相关的逻辑

  • 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的local state时,每个生命周期函数中可能会包含各种互不相关的逻辑在里面

  • 类组件中的this增加了学习成本,类组件在基于现有工具的优化上存在许多问题

  • 由于业务变动,函数组件不得不该为类组件等等

在此之前函数组件也被称之为无状态组件,只负责渲染的一些工作

因此,现在的函数组件也可以是有状态的组件,内部也可以维护自身的状态以及做一些逻辑方面的处理

二.有哪些

Hook 让我们的函数组件拥有了类组件的特性,例如组件内的状态,生命周期

最常见的hooks:

  • useState

  • useEffect

  • useRef

  • useContext

使用规则

  • React Hooks 只能出现在函数组件中和自定义hooks中使用

  • React Hooks 不能嵌套在if/for/其他函数中

  • React 是按照Hooks的调用顺序来识别每一个Hooks,如果每次调用的顺序不同,导致React 无法知道是哪一个Hooks

useState

函数组件中通过useState 实现函数内部维护 state,参数为state默认的值,返回值是一个数组,第一个值是当前的state,第二个值为更新state的函数

函数组件和类组件状态的区别

  • state声明方式:在函数组件中通过useState直接获取,类组件通过constructor构造函数中设置

  • state读取方式:在函数组件中直接使用变量,类组件通过this.state.count的方式

  • state更新方式:在函数组件中通过setCount更新,类组件通过this.setState()

总来说useState使用起来更为简洁,减少了this指向不明确的情况

useState-回调参数

useState的参数可以有两种形式

  1. useState(普通的数据)=>useState(0)

  2. useState(回调函数)=>useState(()=>{return初始化})

    1. 回调函数的返回值就是状态的初始值

    2. 该回调函数只会触发一次

useEffect

useEffect 可以让我们在函数组件中进行一些带有副作用的操作

主作用:根据数据(state/props)渲染UI

副作用: ajax请求,手动修改DOM,localStorage操作等

useEffect-一个参数

执行时机:该effect会在组件首次挂载完,以及组件更新完后执行

相当于:componentDidMount+componentDidUpdata

useEffect-两个参数,第二个参数为[]

执行时机:只在组件第一次渲染后执行

相当于:componentDidMount

使用场景:

  1. 事件绑定

  2. 发送ajax请求

useEffect-两个参数,第二个参数为[依赖项]

执行时机:在组件首次挂载完以及依赖项发生更新时候,都会执行

相当于:componentDidMount+依赖项改变时候的componentDidUpdata

推荐: 一个useEffect只处理一个功能,有多个功能时候,使用多次useEffect

注意: 不要对依赖项撒谎

useEffect- 清理副作用

需要清理的副作用:

  1. 开启定时器,延时器

  2. 注册事件

  3. 本地存储

执行时机:

  • 清除函数会在组件卸载时以及下一次副作用回调函数调用的时候执行,用于清除上一次的副作用

  • 如果依赖项为空数组,那么 会在组件卸载时会执行,相当于组件的componentWillUnmount

useEffect(()=>{
    
    //useEffect 内部代码,再一次执行前,会调研+组件销毁时会调用
    return ()=>{}
},[])

useRef

是什么?

  • 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。

  • 返回的 ref 对象在组件的整个生命周期内保持不变

    一般用于在react中需要定义一个全局的变量,例如定时器的timer

  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方

  • 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里 useRef 类似于类组件的 this

使用场景:在React中进行DOM 操作时,用来获取DOM

//1.导入
import { useRef } from 'react'
​
//2.创建
const aref=useRef(null)
​
//3.绑定
ref={aref}
​
//4.获取
aref.current

重点

需求:由于每次执行,都会重新初始化很多变量,有时,我们需要一个能够贯穿始终的一个变量

效果:一经修改,所有重新执行函数时,拿到的这个值,都是修改后最新的值

useRef=>可以得到一个可变的对象,利用current属性,存值=>获取dom
useRef=>特质=>在整个函数渲染更新过程中,贯穿始终

三.解决什么

hooks能够更容易解决状态相关的重用的问题

  • 每调用useHook 一次都会生成一份独立的状态

  • 通过自定义hooks 能够更好的封装我们的功能

编写hooks为函数式编程,每个功能都包裹在函数中,整体风格更清爽,更优雅

React 中引入css的方法

一.是什么

组件式开发选择合适的css解决方案尤为重要

通常会遵循一下规则:

  • 可以编写局部css,不会随意污染其他组件内的原生

  • 可以编写动态的css,可以获取当前组件的一些状态,根据状态的变化生成不同的css样式

  • 支持所有的css特性:伪类,动画,媒体查询等

  • 编写起来简洁方便,最好符合一贯的css风格特点

二.方式

常见的css引入方式有以下:

  • 在组件内直接使用

  • 组件中引入.css文件

  • 组件中引入.module.css文件

  • CSS in JS

三.区别

  • 在组件内直接使用css 该方式编写方便,容易能够根据状态修改属性,但是大量的演示编写容易导致代码混乱

  • 组件中引入.css文件符合我们日常的编写习惯,但是作用域是全局的,样式之间会层叠

  • 引入.module.scc文件能够解决局部作用域问题,但是不方便动态修改样式,需要使用内联的方式进行样式的编写

  • 通过css in js 这种方法,可以满足大部分场景的应用,可以类似于预处理器一样样式浅谈,定义,修改状态等

在React中组件间过渡动画如何实现?

一.是什么

当一个组件在显示与消失过程中存在过渡动画,可以很好的增加用户体验

在react 中实现过渡动画效果会有很多种选择,如react-transition-group, react-motion,Animated.以及原生的css都能完成切换动画

二.如何实现

主要提供三个重要的组件:

  • CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果

  • SwitchTransition:两个组件显示和隐藏切换时,使用该组件

  • TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画

对Redux的理解?其工作原理?

一.是什么

redux就是一个实现组件内状态的集中管理的容器,遵循三大基本原则:

  • 单一数据源

  • state是只读的

  • 使用纯函数来执行修改

纯函数的特点:

  • 只要有固定的输入,必然有固定的输出(避开 new Date(),Math.random())

  • 纯函数不允许修改参数(引用类型数据,也不能直接修改)

  • 不允许影响到外部作用域的内容 (不允许直接操作全局变量)

  • 没有副作用 (操作dom,发送请求,localStrong)

二.工作原理

redux要求我们把数据都放在store公共存储空间

组件需要获取一些数据,先告知store 需要获取数据,也就是action,然后store接收到之后去reducer中查,reducer 告诉store应该给组件对应的数据

三.基本使用

三个核心

Action,Reducer,Store

  • action(动作):是一个JS对象,必须要有type属性,用于组件 dispatch帮忙分发action,action会传递给store

  • reducer(方案):是一个函数,有两个参数prevState,action

  • store(仓库):createStore可以帮助创建store,有三个参数: reducer,初始值(可省略),中间件

  • store.getState这个方法可以帮助获取store里面所有的数据内容

  • store.subscrible方法订阅store变化,只要store发生改变store.subscrible 这个函数接收的这个回调函数就会被执行

  • Provider 可以更好的监视store的变化

对React中间件的理解?常用的中间件有哪些?实现原理?

一.是什么

中间件是介于应用系统和系统软件之间的一类软件 Redux整个工作流程,当action发出之后,reducer立即算出state,整个过程是一个同步操作

那么如果需要支持异步操作,支持错误处理,日志监控,这个过程就可以用上中间件

redux中,中间件就是放在dispatch过程,在分发action进行拦截处理

中间件的本质是一个函数,对store.dispatch方法进行了改造,在发出action和执行reducer这两部之间,添加了其他功能

二.常用的中间件

  • redux-thunk:用于异步操作

  • redux-logger:用于日志记录

中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,一次执行

在React项目中如何使用Redux的?项目结构是如何划分?

一.背景

react-redux将组件分成:

  • 容器组件:存在逻辑处理

  • UI组件:只负责显示和交互,内部不处理逻辑,状态由外部控制

通过redux将整个应用状态存储到store中,组件可以派发dispatch action给store

其他组件通过Provider上传递store 包裹住整个根组件,获取state

二.如何做

使用react-redux分成两大核心

  • Provider

  • connection

Provider

connection(旧)

connect 方法将store上的getState和dispatch包装成组件的props

useSlecter,useDispatch(新)

useSlecter,useDispatch,获store上的getState和dispatch

对React Router的理解?常用的Router组件有哪些?

一.是什么

路由:组件和路径的映射关系

React-router主要分成几个不同的包

  • react-router:实现路由的核心功能

  • react-router-dom:基于react-router,加入了在浏览器运行环境下的一些功能

  • react-router-native:基于react-router,加入了react-native运行环境下的一些功能

  • react-router-config:用于配置静态路由的工具库

二.有哪些

常用的Api

  • BrowserRouter、HashRouter

  • Route

  • Link、NavLink

  • Switch

  • Redirect

BrowserRouter、HashRouter

HashRouter :利用hash值 地址栏有#

BroserRouter : 利用html5浏览器端的history API实现=>推荐使用(React中路由传参只能在BroserRouter)

Route

组件出口+规则

老版本

​
#新版本  React-Router v6版本中
 } />

Link、NavLink

相同点:

  • 本质是a标签

  • to属性,将来会渲染成a标签的href属性

不同点:

  • NavLink组件,一个更特殊的Link组件,可以用用于指定当前导航高亮

  • NavLink组件中有个activeClassName:用于指定高亮的类名,默认active

  • activeStyle:活跃时(匹配时)的样式

  • exact:精确匹配

Switch

  • 要将所有的Route 组件包裹

  • 不管有多少个路由规则匹配到,都只会渲染第一个匹配的组件

新版本: switch==>Routes

Redirect

用于路由的重定向,当这个组件出现时,就会执行跳转到对应的路径中,且redirect组件只能在switch包裹里才能生效,否则需要通过render 去渲染redirect

react-router还提供一些hooks

  • useHistory

  • useParams

  • useLocation

useHistory

可以让函数组件内部直接访问history,无需通过props获取

import { useHistory } from "react-router-dom";
const history =useHistory()

useParams

返回URL参数的键值对的对象,用它来访问match.params当前

import { useParams } from "react-router-dom";
const { name } = useParams();

useLocation

返回的location表示当前URL的对象,您可以将其视为每当URL,更改时useState返回一个新的

import { useLocation } from "react-router-dom";
  const { pathname } = useLocation();

三.参数传递

路由传递参数的三种形式

  • 动态路由的方式

  • search传递参数

  • to传入对象

动态路由

path='/detail/:id'

获取传参的方法

const {id}=useParmas()

详情
    
    

search 传递参数

在跳转的路径中添加了一些query参数

详情2
  

push/replace/redirect组件to传参的三种方式

  1. search:显示在地址栏,刷新不丢失

  2. query:不显示在地址栏,刷新会丢失

  3. state:不显示在地址栏,刷新不丢失(用的最多)=>这个state参数,只有BrowaweRouter支持

React Router有几种模式?实现原理?

一.是什么

在单页应用中,一个web项目只有一个html页面,一旦页面加载完成之后,就不用因为用户的操作而进行页面的重新加载或者该跳转,其特性如下:

  • 改变 url 且不让浏览器向服务器发送请求

  • 在不刷新页面的前提下动态改变浏览器地址栏中的URL地址

其中主要成了两种模式

  • hash模式:在url后面加上#

  • history模式:允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录

二.使用

React Rouer 对应的hash 模式和 history 模式对应的组件为

  • HashRouter

  • BrowserRouter

三.实现原理

路由就是组件和路径(URLUI之间)的映射关系,这种映射是单向的,即URL改变引起UI更新(无需刷新页面)

对于immutable 的理解?如何应用在react项目中

一.是什么

immutable, 不可改变的,在计算机中,即指一旦创建,就不能再被更改的数据

Immutable 对象的任何修改或者添加删除操作都会返回一个新的Immutable 对象

Immutable 实现的原理是 Persistent Data Structure (持久化数据结构)

  • 用一种数据结构来保存数据

  • 当数据被修改时,会返回一个对象,但是新对象会可能利用之前的数据结构而不会对内存造成浪费

也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy 把所有节点都赋值一遍带来的性能损耗

Immutable 使用了 Structural Sharing (结构共享)

如果对象树中一个节点发生改变.只修改这个节点和受他影响的父节点,其他节点则进行共享

二.如何使用

使用 Immutable 对象最主要的库是 immutable.js

immutable.js 是一个完全独立的库,无论基于什么框架都可以用它

其出现场景在于弥补 JavaScript 没有不可变数据结构的问题,通过Structural Sharing (结构共享)来解决的性能问题

  • List: 有序索引集,类似于JS中的Array

  • Map: 无需索引集,类似于JS中的Object

  • Set:没有重复值的集合

主要的方法如下

  • fromJS(): 讲一个js数据转换成 Immutable 类型的数据

    const obj= Immutable.fromJS({})
  • toJS() : 将一个Immutable数据转换成 JS类型的数据

  • is(): 对两个对象进行比较

  • get(key): 对数据或对象取值

  • getIn([]): 将嵌套对象或数组取值,传参为数组,表示位置

    let abs = Immutable.fromJS({a: {b:2}});
    abs.getIn(['a', 'b']) // 2
    abs.getIn(['a', 'c']) // 子级没有值
    ​
    let arr = Immutable.fromJS([1 ,2, 3, {a: 5}]);
    arr.getIn([3, 'a']); // 5
    arr.getIn([3, 'c']); // 子级没有值

三.在React中应用

使用 Immutable 可以给react 应用带来性能的优化,主要体现在减少渲染的次数

在做 react 性能优化的时候,为了避免重复渲染,我们会在 shouldComponentUpdata()中做对比,当返回 true执行render 方法

Immutable 通过is 方法 则可以完全对比,而无需想一样通过深度比较的方式比较

在使用 redux 过程中可以结合 Immutable,不使用 Immutable 前修改一个数据需要做一个深拷贝

import '_' from 'lodash';
​
const Component = React.createClass({
  getInitialState() {
    return {
      data: { times: 0 }
    }
  },
  handleAdd() {
    let data = _.cloneDeep(this.state.data);
    data.times = data.times + 1;
    this.setState({ data: data });
  }
}

使用 Immutable 后:

getInitialState() {
  return {
    data: Map({ times: 0 })
  }
},
  handleAdd() {
    this.setState({ data: this.state.data.update('times', v => v + 1) });
    // 这时的 times 并不会改变
    console.log(this.state.data.get('times'));
  }

同理,在redux中也可以将数据进行fromJS处理

import * as constants from './constants'
import {fromJS} from 'immutable'
const defaultState = fromJS({ //将数据转化成immutable数据
    home:true,
    focused:false,
    mouseIn:false,
    list:[],
    page:1,
    totalPage:1
})
export default(state=defaultState,action)=>{
    switch(action.type){
        case constants.SEARCH_FOCUS:
            return state.set('focused',true) //更改immutable数据
        case constants.CHANGE_HOME_ACTIVE:
            return state.set('home',action.value)
        case constants.SEARCH_BLUR:
            return state.set('focused',false)
        case constants.CHANGE_LIST:
            // return state.set('list',action.data).set('totalPage',action.totalPage)
            //merge效率更高,执行一次改变多个数据
            return state.merge({
                list:action.data,
                totalPage:action.totalPage
            })
        case constants.MOUSE_ENTER:
            return state.set('mouseIn',true)
        case constants.MOUSE_LEAVE:
            return state.set('mouseIn',false)
        case constants.CHANGE_PAGE:
            return state.set('page',action.page)
        default:
            return state
    }
}

React render 方法的原理?在什么时候触发?

一.原理

首先,render函数在react中有两种形式

在类组件中,指的是render方法

在函数组件中,指的是函数组件本身

在render中,我们会编写jsx,jsx通过babel编译后就会转化成我们熟悉的js格式

在render 过程中,react 将新调用的render函数返回的树与旧版本的树进行比较,这一步是决定如何更新DOM 的必要步骤,然后进行diff比较,更新DOM

二.触发时机

render 的执行时机主要分成了两部分

  • 类组件

    • 调用setState 修改状态

    • 类组件props传值改变,重新渲染

  • 函数组件

    • 通过useState hook修改状态,只会首次触发render

三.总结

render 函数里面可以编写JSX ,转化成 createElement这种形式,用于生成虚拟DOM, 最终转化成真实DOM

在 render中,类组件只要执行了 setState方法,就一定会触发render 函数执行,函数组件使用useState 更改状态不一定导致重新render

组件的props改变了,不一定触发render函数的执行,就会导致子组件的重新渲染

在这种情况下,父组件或者祖先组件的state 发生了改变,就会导致组件的重新渲染

所有一旦执行了setState就会执行render方法,useState会判断当前值会有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染

如何提高组件的渲染效率的?在React中如何避免不必要的render?

一.是什么

react 基于虚拟DOM 和高效DIff算法 的完全配合,实现了对DOM 最小粒度的更新,大多数情况下,React对DOM 的渲染效率足以我们的业务日常

复杂业务场景下,性能问题依然会困扰我们,此时需要采取一些措施来提升运行性能,避免不必要的渲染是业务中场景的优化手段之一

二.如何做

具体实现的方法如下:

  • shouldComponentUpdate=>类组件

  • PureComponent+immutable =>类组件

  • React.memo=>函数组件

shouldComponentUpdata

通过shouldComponentUpdata 生命周期函数来比对state 和props,确定是否要重新渲染

默认情况下返回true 表示重新渲染,如果不希望组件重新渲染,返回false即可

PureComponent

跟 shouldComponentUpdate原理基本一致,通过对props和state的浅比较结果来实现shouldComponentUpdate

React.memo

React.memo 用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与PureComponent 十分类似.但不同的是,React.memo 只能用于函数组件

三.总结

在实际开发过程中,前端性能问题是一个必须考虑的问题,随着业务的赋值,遇到性能问题的概率也在增高

除此之外,建议将页面进行更小的颗粒化,如果一个过大,当状态发生改变的时候,就会导致整个大组件的渲染,而对组件进行拆分后,颗粒度变小了,也能减少子组件不必要的渲染

React diff的原理是什么?

一.是什么

跟 vue一致,react通过引入Vritual DOM 的概念,极大地避免无效的DOM操作,是我们的页面的构建效率得到极大的提升,

而 diff算法就是更高效地通过对比新旧 Vritual DOM 来找到真正的DOM 变化之处

二.原理

react 中diff算法主要遵循三个层级的策略

  • tree层级

  • conponent层级

  • element层级

tree 层级

DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较

conponent层级

如果是同一个类的组件,则会继续往下 diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的

element层级

对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识

通过key可以准确地发现新旧集合中的节点都是相同节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置

三.注意事项

对于简单列表渲染而言,不使用key比使用key节省性能

由于dom节点的移动操作开销是比较昂贵的,没有key的情况下要比有key的性能更好

对Fiber架构的理解?解决了什么问题

一.问题

JS引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待

如果JS线程长时间占用了主线程,那么渲染层面的更新就不得不长时间的等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿

而这也正是React 15的Stack Reconciler 所面临的问题,当react在渲染组件时候,从开始到渲染完成整个成功是一气呵成的,无法中断

如果组件较大,那么js线程会一直执行,然后等到整颗VDOM 树计算完成后,才会交给渲染的线程

就会导致一些用户交互,动画等任务无法立即得到处理,导致卡顿的请求

二.是什么

react 16,主要做了以下操作

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务.然后再更新,注意是重新执行优先级低的任务

  • 增加了异步任务,调用requesetldleCallback api ,浏览器空闲的时候执行

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

从架构角度来看,Fiber是对React核心算法(即调和过程)的重写

从编码角度来看,Fiber是react内部所定义的一种数据结构,它是Fiber树结构的节点单位,也就是react 16新架构下的虚拟DOM

一个Fiber就是一个JS对象,包含了元素的信息,该元素更新操作队列,类型,其数据结构如下

type Fiber = {
  // 用于标记fiber的WorkTag类型,主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等
  tag: WorkTag,
  // ReactElement里面的key
  key: null | string,
  // ReactElement.type,调用`createElement`的第一个参数
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // 表示当前代表的节点类型
  type: any,
  // 表示当前FiberNode对应的element组件实例
  stateNode: any,
​
  // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  return: Fiber | null,
  // 指向自己的第一个子节点
  child: Fiber | null,
  // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
  sibling: Fiber | null,
  index: number,
​
  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
​
  // 当前处理过程中的组件props对象
  pendingProps: any,
  // 上一次渲染完成之后的props
  memoizedProps: any,
​
  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  updateQueue: UpdateQueue | null,
​
  // 上一次渲染的时候的state
  memoizedState: any,
​
  // 一个列表,存放这个Fiber依赖的context
  firstContextDependency: ContextDependency | null,
​
  mode: TypeOfMode,
​
  // Effect
  // 用来记录Side Effect
  effectTag: SideEffectTag,
​
  // 单链表用来快速查找下一个side effect
  nextEffect: Fiber | null,
​
  // 子树中第一个side effect
  firstEffect: Fiber | null,
  // 子树中最后一个side effect
  lastEffect: Fiber | null,
​
  // 代表任务在未来的哪个时间点应该被完成,之后版本改名为 lanes
  expirationTime: ExpirationTime,
​
  // 快速确定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,
​
  // fiber的版本池,即记录fiber更新过程,便于恢复
  alternate: Fiber | null,
}

React JSX 转换成真实DOM过程

一.是什么

  • JSX是JavaScript XML 的简写,表示在JS代码中编写HTML代码

  • JSX不是标准的JS语法,是JS的语法拓展

    • jsx本身不能浏览器读取,必须使用Babel和webpack等工具将其转换成传统的JS

    • Babel是一个工具链,主要将ES5+语法编写的代码转化成向下兼容的JS语法

  • JSX本质就是JS对象=>React.createElement

二.过程

在react中,节点大盒子可以分成四大类别:

  • 原生标签节点

  • 文本节点

  • 函数组件

  • 类组件

这些类别最终都会转化成React.createElement这种形式

React.createElement 会根据传入的节点信息进行一个判断:

  • 如果是原生标签节点,type是字符串,如div,span

  • 如果是文本节点,type就没有,这里是TEXT

  • 如果是函数组件,type就是函数名

  • 如果是类组件,type就是类名

虚拟Dom会通过ReactDOM.render进行渲染成真实DOM

三.总结

在react 源码中,虚拟DOM转化成真实DOM 整体流程如下

渲染流程如下:

  • 使用React.createElement或JSX编写react组件,实际上所有的jsx代码最后都会转换成React.createElement(...),Babel帮助我们完成这个转换的过程

  • createElement函数对key 和ref等特殊的props进行处理,并获取defaultProps对默认props进行复制,并且对传入的孩子节点进行处理,最终构成一个虚拟DOM对象

  • ReactDOM.render将生成好的虚拟DOM 渲染到指定容器上,其中采用了批处理,事务等机制并且对特定浏览器进行了性能优化,最终转化成为真实DOM

React 性能优化的手段有哪些?

一.是什么

React 凭借 virtual DOM 和 diff算法拥有高效的性能,但是在某些情况下,性能明显可以进一步提高

二.怎么做

在React中避免不必要的render中有:shouldComponenetUpdate,PureComponent,React.memo

除此之外,常见性能优化常见的手段有如下:

  • 避免使用内联函数

  • 使用React Fragments避免额外标记=>可以使用幽灵组件

  • 使用Immutable

  • 懒加载组件

  • 事件绑定方式

    从性能方面考虑,在render方法中使用bind和render方法中使用箭头函数这两种形式,在每次组件render的时候都会生成新的方法实例=>尽量使用在render外创建一个函数,将事件绑定到函数本身

  • 服务端渲染

    服务端渲染需要起一个node服务,可以使用express,koa等

    调用react的renderToString方法,将根组件渲染成字符串,在输出到响应中

在React项目是如何捕获错误的

为了解决出现的错误导致整个应用崩溃的问题,react16 引用了 错误边界 新的概念

错误边界是一种React组件,这种组件可以捕获发生子组件树任何位置的JS错误,并打印这些错误,同时展示降级UI,而不会渲染哪些发生崩溃的子组件树

错误边界在渲染期间,生命周期方法和整个组件树的构造函数中捕获错误

形成错误边界组件的两个条件

  • 使用了 static getDerivedStateFromError()==>渲染备用 UI

  • 使用了componentDidCatch==>打印错误

下面这些情况无法捕获异常

  • 事件处理

  • 异常代码

  • 服务端渲染

  • 自身抛出来的错误

React 服务端渲染怎么做?原理是什么?

一.是什么

在SSR中,我们了解到Server-Side Rendering,简称SSR,意为服务端渲染

指由服务侧完成页面的HTML结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态和事件,称为完成可交互页面的过程

其解决的问题主要有两个

  • SEO,由于搜索引擎爬虫抓取工具可以直接查看完成渲染的页面

  • 加速首屏加载,解决首屏白屏问题

二.如何做

在react中,实现SSR主要有两种形式

  • 手动搭建一个SSR框架

  • 使用成熟SSR框架,如Next.js

这里主要以手动搭建一个SSR框架进行实现

首先通过express 启动一个app.js,用于监听3000端口的请求,当请求根目录时,返回HTML

重构通俗讲就是一套React代码在服务器上运行一遍,到达浏览器又运行一遍:

  • 服务端渲染完成页面结构

  • 浏览器端渲染完成事件绑定

三.原理

整体 react 服务端渲染并不复杂,具体如下:

node server 接收客户端请求,得到当前的请求url路径,然后再已有的路由表内查找到对应的组件,拿到需要请求的数据,将数据作为props,context或者store形式传入组件

然后基于react 内置的服务器渲染方法 renderToString() 把组件渲染为html字符串在最终的html进行输出前需要将数据注入到浏览器端

浏览器开始进行渲染和节点对比,然后执行完成组件内事件绑定和一些交互,浏览器重用了服务器输出的html节点,整个流程结束

类组件的生命周期

  1. 创建时(挂载阶段)

    • constructor

      1. 初始化state

      2. 创建ref

    • render

      1. 渲染UI(注意:不要在render中调用setState,会死循环)

    • componentDidMount

      1. 发送请求+注册事件

      2. 操作DOM

  2. 更新时(更新阶段)

    • render

    • componentDidUpdate

      1. setState() - 更改state,更新UI

      2. forceUpdate() 强制更新

      3. 组件接收到新的props

  3. 卸载时(销毁阶段)

    • componentWillUnmount

      1. 执行清理工作(比如:清理定时器等)

-------------------------------------------------------------------------------Amy-YAN

你可能感兴趣的:(html5,css,react.js,前端)