React面试题

说说你对react的理解?有哪些特性?

React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟 DOM 来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流
帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
react 类组件使用一个名为 render() 的方法或者函数组件return,接收输入的数据并返回需要展示的内容

特性:

JSX 语法
单向数据绑定
虚拟 DOM
声明式编程
Component

优势:

高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快

说说对Diff算法理解

Diff算法作用:

由于渲染真实DOM的开销很大,有时候我们修改了某个数据,直接渲染到真实dom上会引起整个dom树的重绘和重排。我们希望只更新我们修改的那一小块dom,而不是整个dom,diff算法就帮我们实现了这点

Diff算法的本质:找出两对象(虚拟dom树)之间的差异,目的是尽可能地做到节点可复用

Diff策略:

tree diff:Web UI中DOM节点跨层级地移动操作特别少,可以忽略不计

component diff:拥有相同类地两个组件生成相似的树形结构,拥有不同类地两个组件生成不同的树形结构

element diff:对于同一层级地一组子节点,通过唯一id区分

Diff开发建议:
基于tree diff:

开发组件时,注意保持DOM结构的稳定;即,尽可能少地动态操作DOM结构,尤其是移动操作。
当节点数过大或者页面更新次数过多时,页面卡顿的现象会比较明显。
这时可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
基于component diff:

注意使用 shouldComponentUpdate() 来减少组件不必要的更新。
对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。
基于element diff:

对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

一共有三个阶段,分别为

挂载阶段(Mounting):已插入真实的Dom阶段

更新阶段(Updating):正在被重新渲染的阶段

卸载阶段(Unmounting):已移出真是dom阶段

挂载阶段:

  • constructor() 在 React 组件挂载之前,会调用它的构造函数。
  • componentWillMount: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用

更新运行阶段:

  • componentWillReceiveProps: 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低
  • shouldComponentUpdate():用于控制组件重新渲染的生命周期,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • *componentWillUpdate(): shouldComponentUpdate返回true以后,组件进入重新渲染完成之前进入这个函数。
  • **componentDidUpdate(): 每次state改变并重新渲染页面后都会进入这个生命周期
    卸载或销毁阶段
    componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。

说说你对React中虚拟dom的理解?

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

使用虚拟 DOM 的优势如下:
简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难
性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行
缺点:
在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢

说说你对react hook的理解

React中的Hook方法:

UseState():

useState()用于为函数组件引入状态。在useState()中,数组第一项为一个变量,指向状态的当前值。类似this.state,第二项是一个函数,用来更新状态,类似setState

UseEffect():

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()就会执行

useRef():

相当于class组件中的createRef的作用,ref.current获取绑定的对象

UseContext():

接收context状态树传递过来的数据

UseReducer():

接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数

UseMemo()和useCallback():

接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据
共同作用:仅仅依赖数据发生变化, 才会调用,也就是起到缓存的作用。useCallback缓存函数,useMemo 缓存返回值。

React组件之间如何通信

父传子:在父组件的子组件标签上绑定自定义属性,挂载传递的数据,然后子组件通过props接收传递的数据

子传父:在父组件中的组件间标签上绑定一个数据,传递一个方法给子组件,子组件通过props接收这个方法,直接调用即可

非父子组件通信:

可以使用context状态树进行组件间的通信

还可以使用状态提升就是将多个组件需要共享的数据提升到他们最近的负组件中,父组件改变这个状态通过props分发给子组件

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

受控组件:

由React控制输入表单的元素而改变其值的方式

例如:给表单input绑定一个onChange事件,当input状态发生变化就会触发这个事件,从而更新state的变化

非受控组件:

它的表单数据由dom本身处理,不受setState控制,和传统的html相似,输入什么就会显示最新的值

在非受控组件中可以使用ref来从dom获取表单数据

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

Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理
前面我们了解到了Redux整个工作流程,当action发出之后,reducer立即算出state,整个过程是一个同步的操作
那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件,其本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能

常用的redux中间件,如:
redux-thunk:用于异步操作
redux-logger:用于日志记录

中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,依次执行然后作为第二个参数传入到createStore中

说说react 中jsx语法糖的本质?

jsx本质就是下面这个函数React.createElement的语法糖,所有的jsx语法都会最终经过babel.js转化成为React.createElement这个函数的函数调用

React.createElement(component,props,…children)函数

说说你对webSocket的理解?

什么是websocket?**

WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。

WebSocket原理

客户端向 WebSocket 服务器通知一个带有所有接收者ID的事件,服务器接收后立即通知所有活跃的客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。

WebSocket特点

支持双向通信,实时性更强
可以发送文本,也可以发送二进制数据‘’
建立在TCP协议之上,服务端的实现比较容易
数据格式比较轻量,性能开销小,通信高效
没有同源限制,客户端可以与任意服务器通信
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

应用场景

基于websocket的事实通信的特点,其存在的应用场景大概有:
弹幕
媒体聊天
协同编辑
基于位置的应用
体育实况更新
股票基金报价实时更新

说说如何借助webpack来优化前端性能?

随着前端项目的逐渐变得越来越来大那么随之而来得一个问题就是前端的一个性能问题。而我们在项目完成之后需要使用webpack对我们的项目进行一个打包,而在打包的过程中webpack就会对我们的性能进行一个优化,优化的手段有:js代码压缩、代码分离、文件大小压缩、css和html代码压缩等这些方式来优化我们的性能。

说说你对koa中洋葱模型的理解?

什么是koa?

Koa是一个精简的node框架,被认为是第二代Node框架,其最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型,它的核心工作包括下面两个方面:

将node原生的req和res封装成为一个context对象。
基于async/await的中间件洋葱模型机制。

什么是洋葱模型。

Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑,这里的request的逻辑,我们可以理解为是next之前的内容,response的逻辑是next函数之后的内容,也可以说每一个中间件都有两次处理时机。洋葱模型的核心原理主要是借助compose方法

为什么需要洋葱模型?

因为很多时候,在一个app里面有很多中间件,有些中间件需要依赖其他中间件的结果,用葱模型可以保证执行顺序,如果没有洋葱模型,执行顺序可能出乎我们的预期
如下是洋葱代码的案例:

const Koa = require('koa');
 
//Applications
const app = new Koa();
 
// 中间件1
app.use((ctx, next) => {
  console.log(1);
  next();
  console.log(2);
});
 
// 中间件 2 
app.use((ctx, next) => {
  console.log(3);
  next();
  console.log(4);
});
 
app.listen(7000, '0.0.0.0', () => {
    console.log(`Server is starting`);
});
 
// 中间件的打印顺序是1 -> 3 -> 4 -> 2

如何通过原生js实现一个节流函数和防抖函数?

防抖函数(debounce)

功能:当事件被触发N秒之后再执行回调,如果在N秒内被触发,则重新计时。
比如一个输入框,当应用实现防抖函数之后,用户不断输入内容时,函数会一直被触发,则不会发送请求。只有当用户在一段规定时间N内未进行输入操作,才会发送一次请求。如果在N秒内间断的输入内容,且间断的时间小于规定时间N时,则会重新计时且不会发送请求。
这样降低了发送请求的次数,提高性能的同时也提升了用户体验。

手写实现防抖函数

// func是用户传入需要防抖的函数
// wait是等待时间,若不传参,默认50ms
// 因为闭包,timer将一直在内存中
const debounce = (func, wait = 50) => {
    // 缓存一个定时器
    let timer = null;
    // 返回的函数是每次用户实际调用的防抖函数
    return (...args) => {
        // 如果已经设定过定时器了就清空上一次的定时器
        if (timer) clearTimeout(timer);
        // 开始一个新的定时器,延迟执行用户传入的方法
        timer = setTimeout(() => {
            func.apply(this, args);
        }, wait);
    };
};

节流函数(throttle)

功能:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

实现节流函数

// func是用户传入需要防抖的函数
// wait是等待时间,若不传参,默认50ms
// 因为闭包,falg将一直在内存中
const throttle = (func, wait = 50) => {
    // 定义falg,初试为true
    let flag = true;
    // 返回的函数是每次用户实际调用的节流函数
    return (...args) => {
        if (flag) {
            // 如果flag为true,则执行定时器
            setTimeout(() => {
                func.apply(this, args);
                // 函数执行完毕后,将flag改回true
                // 以便下次再执行
                flag = true;
            }, wait);
        }
        // 因为定时器是异步任务,定时器执行后,立刻将flag关闭
        // 在等待延时时间时,阀门始终关闭,不会一直执行函数
        flag = false;
    };
};

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

React凭借着虚拟DOM和diff算法有着更高的一个性能然而在一些特殊的情况下还是需要我们对React的性能进行一个优化,

优化的手段有以下几种:

服务器端渲染、懒加载组件、避免使用内联函数、使用一个插件避免额外标记、还有一个事件绑定方式等去进行react的性能优化。

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

原理

在类组件和函数组件中,render函数的形式是不同的。
在类组件中render函数指的就是render方法;而在函数组件中,指的就是整个函数组件

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

触发时机

类组件调用 setState 修改状态:
函数组件通过useState hook修改状态:
函数组件通过useState这种形式更新数据,当数组的值不发生改变了,就不会触发render

总结

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

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

组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state

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

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

说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?

react-redux

react-redux 是的官方 React UI 绑定层,允许您的 React 组件从 Redux 存储中读取数据,并将操作分派到存储以更新状态。

@reduxjs/toolkit

@reduxjs/toolkit 是对 Redux 的二次封装,开箱即用可的一个高效的 Redux 开发工具集,使得创建store、更新store

说说React jsx转换成真实DOM的过程?1编译JSX

首先,JSX本质其实是javascript的语法扩展,和模板语言非常接近,但是其充分具备javascript的能力。但是其要在javascript生效的话,需使用到 Babel 进行编译,JSX在被编译后,会变成一个针对 React.createElement 的调用。

2React.createElement 内部流程

首先,React.createElement 会接收三个参数:
第一个参数为 type,字符串类型,用于标识节点的类型,比如,div、p等,也可以是React组件类型或 React fragment类型。
第二个参数为 config,这是一个对象类型的参数,组件的所有属性都会键值对的形式存储在config中。
第三个参数为 children,这也是一个对象类型的参数,它记录的是组件标签之间嵌套的内容,也就是所谓的子节点或子元素。

整体来说,createElement的大致流程为:
1.二次处理key、ref、self、source四个属性值;
2.遍历config,筛选可以提到props中的属性;
3.将children中的子元素推入childArray数组;
4.格式化defaultProps
5.将以上数据作为入参,发起ReactElement的调用,最终由ReactElement返回虚拟Dom对象

3最终将虚拟Dom传入ReactDom.render函数中,将其转变为真实Dom

流程

书写JSX代码 => Babel编译JSX => 编译后的JSX执行React.createElement的调用 => 传入到ReactElement方法中生成虚拟Dom => 最终返回给ReactDom.render生成真实Dom

说说package.json中版本号的规则?

版本的格式
major.minor.patch

主版本号.次版本号.修补版本号

patch:修复bug,兼容老版本

minor:新增功能,兼容老版本

major:新的架构调整,不兼容老版本

依赖版本号规则
version

必须匹配某个版本

如:1.1.2,表示必须依赖1.1.2版

\>version

必须大于某个版本

如:>1.1.2,表示必须大于1.1.2版

\>=version

可大于或等于某个版本

如:>=1.1.2,表示可以等于1.1.2,也可以大于1.1.2版本

<version

必须小于某个版本

如:<1.1.2,表示必须小于1.1.2版本

<=version

可以小于或等于某个版本

如:<=1.1.2,表示可以等于1.1.2,也可以小于1.1.2版本

~version

大概匹配某个版本

如果minor版本号指定了,那么minor版本号不变,而patch版本号任意

如果minor和patch版本号未指定,那么minor和patch版本号任意

如:~1.1.2,表示>=1.1.2 <1.2.0,可以是1.1.2,1.1.3,1.1.4,.....,1.1.n

如:~1.1,表示>=1.1.0 <1.2.0,可以是同上

如:~1,表示>=1.0.0 <2.0.0,可以是1.0.0,1.0.1,1.0.2,.....,1.0.n,1.1.n,1.2.n,.....,1.n.n

^version

兼容某个版本

版本号中最左边的非0数字的右侧可以任意

如果缺少某个版本号,则这个版本号的位置可以任意

如:^1.1.2 ,表示>=1.1.2 <2.0.0,可以是1.1.2,1.1.3,.....,1.1.n,1.2.n,.....,1.n.n

如:^0.2.3 ,表示>=0.2.3 <0.3.0,可以是0.2.3,0.2.4,.....,0.2.n

如:^0.0,表示 >=0.0.0 <0.1.0,可以是0.0.0,0.0.1,.....,0.0.n

x-range

x的位置表示任意版本

如:1.2.x,表示可以1.2.0,1.2.1,.....,1.2.n

*-range

任意版本,""也表示任意版本

如:*,表示>=0.0.0的任意版本

version1 - version2

大于等于version1,小于等于version2

如:1.1.2 - 1.3.1,表示包括1.1.2和1.3.1以及他们件的任意版本

range1 || range2

满足range1或者满足range2,可以多个范围

如:<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0,表示满足这3个范围的版本都可以

说说AMD、CMD、commonJS模块化规范的区别?

CommonJS规范的特点:

对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
当使用require命令加载某个模块时,就会运行整个模块的代码。
当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

CommonJS与AMD:

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
AMD规范则是非同步加载模块,允许指定回调函数。
由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

CMD和AMD:

1.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

2.AMD推崇依赖前置(在定义模块的时候就要声明其依赖的模块),CMD推崇依赖就近(只有在用到某个模块的时候再去require——按需加载)。

3.AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值