前言
这一篇文章是博主最近在学习react的知识 会把自己学到的一些原理性的知识分享出来 如果感兴趣 不妨点个关注 大家一起学习
React生命周期中有哪些坑?如何避免踩坑?
getDerivedStateFromProps
getDerivedStateFromProps():会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用,并且在初始挂载及后续更新时都会被调用。
state 的值在任何时候都取决于 props。
getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state。
该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容。
getDerivedStateFromProps 容易编写反模式代码,使受控组件与非受控组件区分模糊。
componentWillMount
在 React中已被标记弃用,不推荐使用,主要原因是新的异步渲染架构会导致它被多次调用。 所以网络请求及事件绑定代码应移至 componentDidMount 中。
componentWillReceiveProps
同样被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
shouldComponentUpdate
shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
shouldComponentUpdate() 的返回值用于判断 React 组件的输出是否受当前 state 或 props 更改的影响,当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
通过返回true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。 componentWillUpdate
componentWillUpdate
同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合getSnapshotBeforeUpdate 与componentDidUpdate 改造使用。
使用生命周期注意事项
如果在 componentWillUnmount函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug
如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加
componentWillUnmount()在卸载和销毁组件之前立即调用。在此方法中执行任何必要的清理,例如使计时器无效,取消网络请求或清除在其中创建的任何订阅componentDidMount()。不应该调用setState(),componentWillUnmount()因为组件永远不会被重新呈现。卸载组件实例后,将永远不会再次安装它。
说说Real diff算法是怎么运作的?
众所周知 在我们开发的过程中 渲染真实dom的开销是很大的,有时候我们修改了某个数据 直接渲染到真实的dom上会引起整个dom树的重绘和重排。我们希望更新我们修改的那一小块的dom,而不是整个dom,diff算法就帮我们实现了这个需求
diff算法的本质就是:找出两个对象之间的差异,目的是尽可能的做到节点复用。
react的diff算法
react中diff算法主要遵循三个层级的策略:
- tree层级
DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较 conponent 层级 - conponent 层级
如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的 - element 层级
对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识
提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)
- Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。】
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
调和阶段setState干了什么?
当调用 setState 时,React会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态
这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最有效的方式,根据这个新的状态来更新UI。 为此,React将构建一个新的 React 元素树(您可以将其视为 UI 的对象表示)
一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React 会将这个新树与上一个元素树相比较( diff )
通过这样做, React 将会知道发生的确切变化,并且通过了解发生什么变化,只需在绝对必要的情况下进行更新即可最小化 UI 的占用空间
说说redux的实现原理是什么,写出核心代码?
原理
将应用的状态统一放到 state 中,由 store 来管理 state 。
reducer 的作用是 返回一个新的 state 去更新 store 中对用的 state。
按 redux 的原则,U层每一次状态的改变都应通过 action 去触发,ation 传入对应的 reducer 中,reducer 返回一个新的 state更
新 store 中存放的 state ,这样就完成了一次状态的更新4.subscribe 是为 store 订阅监听函数,这些订阅后的监听函数是在每一次 dipatch 发起后依次执行
可以添加中间件对提交的 dispatch 进行重写
核心API
createstore 创建仓库,接受 reducer 作为参数
bindActionCreator 绑定 store.dispatch 和 action 的关系
combineReducers 合并多个 reducers
applyMiddleware 洋葱模型的中间件,于 dispatch 和 action 之间,重写 dispatch
compose 整合多个中间件
React合成事件的原理?
原理
React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。
合成事件总结:
合成事件的监听器是统一注册在document上的,且仅有冒泡阶段。所以原生事件的监听器响应总是比合成事件的监听器早
阻止原生事件的冒泡后,会阻止合成事件的监听器执行
React组件之间如何通信?
- 父组件向子组件通讯:
父组件可以向子组件传入props的方式,向子组件进行通讯。 - 子组件向父组件通讯:
props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中。 - 兄弟组件通信:
兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递
例:组件A – 传值 --> 父组件 – 传值 --> 组件B - 跨层级通讯:
Context设计⽬的是为了共享那些对于⼀个
组件树⽽⾔是“全局”的数据,
使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据
例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过Context通信再适合不过。 - 发布订阅者模式:
发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event模块进⾏通信。 - 全局状态管理工具:
借助Redux或者Mobx等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态。
为什么react元素有一个$$type属性?
目的是为了防止 XSS 攻击。因为 Synbol 无法被序列化,所以 React 可以通过有没有 $$typeof 属性来断出当前的 element 对象是从数据库来的还是自己生成的。如果没有 $$typeof 这个属性,react 会拒绝处理该元素。
connect组件的原理是什么?
connect是一个高阶函数,它真正连接 Redux 和 React,包在我们的容器组件的外一层,接收上面Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
原理:
首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有:
(1)通过props.store获取祖先Component的store
(2)props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
(3)componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
(4)shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
(5)componentWillUnmount时移除注册的事件this.handleChange
首先connect之所以会成功,是因为Provider组件:
- 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件,接收Redux的store作为props,通过context对象传递给子孙组件上的connect,它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
说说你对fider架构的理解?解决了什么问题?
解决的问题:
JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待;如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿
理解:
React Fiber 是对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现
主要做了:
- 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
- 增加了异步任务,调用
requestIdleCallback api,浏览器空闲的时候执行 dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
说说你对redux中间件的理解?常用的中间件?实现原理?
理解:
中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能
常用中间件:
redux-thunk:用于异步操作
redux-logger:用于日志记录
实现原理:
所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法内部会将dispatch进行一个判断,然后执行对应操作
react性能优化的方法有那些?
1.使用纯组件
2.使用React.memo进行组件记忆(React.memo是一个高阶组件),对于相同的输入 不重复执行
3.如果是类组件,使用shouldComponentUpdate(重新渲染组件之前触发的其中的一个生命周期事件)生命周期事件可以利用此事件来决定何时需要重新渲染组件
4.路由懒加载
5.使用React Fragments 避免额外标记;
6.不要使用内联函数定义(如果我们使用内联函数 则每次调用"render"函数时都会创建一个新的函数实例);
7.避免在willxxx系列的生命周期中进行异步请求 操作dom等
8. 如果是使用类组件 事件函数constructor 中绑定bind改变this指向;
9. 避免使用内联样式属性;
10.不要再render方法中导出数据;
11.不要再render方法中导出数据
12.列表渲染的时候加key;
13.在函数组件中使用useCallback和useMemo来进行组件优化 依赖没有变化的话 不重复执行
14.类组件中使用immutable对象
事件循环event loop的理解?
任务在主线程不断进栈出栈的一个循环过程。任务会在将要执行时进入主线程,在执行完毕后会退出主线程。
JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,
但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
异步任务还可以细分为微任务与宏任务
微任务一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
前端跨域解决方案?
9种跨域解决方案
1、JSONP跨域
2、跨域资源共享(CORS)
3、nginx代理跨域
4、nodejs中间件代理跨域
5、document.domain+ iframe跨域
6、location.hash + iframe跨域
7、window.name + iframe跨域
8、postMessage跨域
9、WebSocket协议跨域
数组常用方法及作用?
- Array.length:返回或设置一个数组中的元素个数
- Array.from() :对伪数组或可迭代对象(包括arguments,Array,Map,Set,String…)转换成数组对象
- Array.isArray():用于确定传递的值是否是一个 Array
- concat():方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
- every(callback):方法测试数组的所有元素是否都通过了指定函数的测试
- filter():创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
- find():返回数组中满足提供的测试函数的第一个元素的值
- forEach():方法对数组的每个元素执行一次提供的函数
- includes():用来判断一个数组是否包含一个指定的值,如果是,酌情返回 true或 false
- indexOf():返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1
- join():将数组(或一个类数组对象)的所有元素连接到一个字符串中
- lastIndexOf():返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果 不存在则返回 -1。从数组的后面向前查找
- map():创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果
- pop():从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度
- push():将一个或多个元素添加到数组的末尾
- reduce():累加器和数组中的每个元素(从左到右)应用一个函数
- shift():从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度
- slice():返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象
- some():测试数组中的某些元素是否通过由提供的函数实现的测试。
- sort():当的位置对数组的元素进行排序,并返回数组。
- splice():通过删除现有元素和/或添加新元素来更改一个数组的内容
- toString():返回一个字符串,表示指定的数组及其元素
- unshift():将一个或多个元素添加到数组的开头,并返回新数组的长度
- toLocaleString():返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 “,”)隔开
React render方法的原理,在什么时候会触发?
原理:
在类组件中render函数指的就是render方法;而在函数组件中,指的就是整个函数组件
render函数中的jsx语句会被编译成我们熟悉的js代码,在render过程中,react将新调用的render函数返回的树与旧版本的树进行比较,这一步是决定如何更新 DOM 的必要步骤,然后进行 diff 比较,更新dom树
触发机制:
类组件调用 setState 修改状态
函数组件通过useState hook修改状态
一旦执行了setState就会执行render方法,useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染
你对vue的mixin的理解有哪些?
mixin是一种类,在vue中就是js文件,主要的作用是作为功能模块引用。因为在项目中,可能不同组件会有相同的功能,比如控制元素的显示和隐藏,如果他们的变量和规则也完全相同的话,就可以把这个功能单独提取出来,放在mixin.js中,再引入,就可以实现一样的功能了。引入的方法也分为全局混入和局部混入,局部混入就是在每个组件中引入,全局混入就是在main.js中通过Vue.mixin()引入。
for…in 循环和for…of循环的区别
for...in 循环:只能获得对象的键名,不能获得键值
for…in循环有几个缺点
①数组的键名是数字,但是for…in循环是以字符串作为键名“0”、“1”、“2”等等。
②for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
③某些情况下,for…in循环会以任意顺序遍历键名。
for…in循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of 循环:允许遍历获得键值
for…of循环
①有着同for…in一样的简洁语法,但是没有for…in那些缺点。
②不同于forEach方法,它可以与break、continue和return配合使用。
③提供了遍历所有数据结构的统一操作接口
js数据类型判断都有哪几种方式?它们的区别是什么?
1. typeof判断
typeof返回的类型都是字符串形式
2. Constructor
实例constructor属性指向构造函数本身
constructor 判断方法跟instanceof相似,但是constructor检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型
3. Instanceof
instanceof可以判类型是否是实例的构造函数
instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
4. Object.prototype.toString.call()
判断类型的原型对象是否在某个对象的原型链上
5. 通过object原型上的方法判断
比如array.isArray()来判断是不是一个数组
6. ===(严格运算符)
通常出现在我们的条件判断中,用来判断数据类型的话就会非常的有局限性,比如判断一个变量是否为空,变量是否为数据等
说说你对Object.defineProperty()的理解?
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
该方法接受三个参数
- 第一个参数是
obj:要定义属性的对象, - 第二个参数是
prop:要定义或修改的属性的名称或Symbol, - 第三个参数是
descriptor:要定义或修改的属性描述符
函数的第三个参数descriptor所表示的属性描述符有两种形式:数据描述符和存取描述符。 - 数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
- 存取描述符是由
getter函数和setter函数所描述的属性。
一个描述符只能是这两者其中之一;不能同时是两者。
这两种同时拥有下列两种键值:
configurable: 是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。
enumerable: 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。
说说你对webSocket的理解?
理解
WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
Websocket是一个持久化的协议
原理
websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"
说说你对useEffect的理解?
一. 作用
useEffect相当于 componentDidMount(组件挂载),componentDidUpdate(组件更新) 和 componentWillUnmount(组件将要销毁) 这三个生命周期函数的组合。
二.用法
useEffect有两个参数,第一个参数是一个回调函数,第二个参数是一个数组,这个数组中的元素都是依赖,每当依赖发生改变,就会触发第一个函数的执行。
三.重要理解
1.第二个参数存放变量,当数组存放变量发生改变时,第一个参数,逻辑处理函数将会被执行
2.第二个参数如果不传,会导致逻辑处理函数无线循环调用
3.第二个参数如果传递为一个空数组,那么逻辑处理函数就只能执行一次,相当于componentDidMount(组件挂载)
4.如果第二个参数不是空数组,那么在数组中的依赖变化的时候就会触发逻辑函数的调用,就像componentDidMount和componentDidUpdate组合的生命周期函数一样
5.useEffect在组件被销毁去除之前也会调用逻辑执行函数,会消除副作用,类似componentWillUnmount生命周期函数
说说Real DOM和Virtual DOM的区别?优缺点?
两者的区别如下:
虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”
真实 DOM 的优势:
易用
缺点:
效率低,解析速度慢,内存占用量过高
性能差:频繁操作真实 DOM,易于导致重绘与回流
使用虚拟 DOM 的优势如下:
简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难
性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行
缺点:
在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢
说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
挂载阶段:
- constructor() 在 React 组件挂载之前,会调用它的构造函数。
- componentWillMount: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
- componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用
更新运行阶段:
- componentWillReceiveProps: 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低
- shouldComponentUpdate():用于控制组件重新渲染的生命周期,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
- render(): render() 方法是 class 组件中唯一必须实现的方法。
- componentWillUpdate(): shouldComponentUpdate返回true以后,组件进入重新渲染完成之前进入这个函数。
- componentDidUpdate(): 每次state改变并重新渲染页面后都会进入这个生命周期
卸载或销毁阶段
componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。
说说React中setState和replaceState的区别?
在React中,setState和replaceState都是用于更新组件状态的方法,它们的区别在于更新状态的方式和效果。
setState的作用是将新的状态合并到原有的状态中,仅更新需要改变的状态属性,同时保留原有的状态。setState方法接受一个对象或函数作为参数,对象中包含需要更新的状态属性和对应的值,函数的返回值也是一个状态对象。setState方法会在更新状态后重新渲染组件。
replaceState的作用是用新的状态替换原有的状态,新的状态会完全覆盖原有的状态。replaceState方法接受一个对象作为参数,对象中包含所有需要更新的状态属性和对应的值。replaceState方法会在更新状态后重新渲染组件。
因此,setState和replaceState的区别可以总结为:
setState是将新状态合并到原有状态中,而replaceState是用新状态替换原有状态。
setState会保留原有状态,只更新需要改变的状态属性,而replaceState会完全覆盖原有状态。
setState接受一个对象或函数作为参数,而replaceState只接受一个对象作为参数。
在React中,建议使用setState方法来更新组件状态。因为它不会完全替换原有状态,可以避免一些状态丢失的问题。同时,使用函数作为参数可以避免状态更新时的竞态问题。replaceState已经被标记为过时的方法,不建议使用。
说说react中onClick绑定后的工作原理?
React 中的 onClick 事件绑定是一种用于处理用户交互的常见方法。它的工作原理如下:
首先,在 React 组件中,开发人员定义一个 onClick 事件处理函数,该函数将在用户单击元素时被调用。
然后,使用 JSX 语法将该函数与 DOM 元素绑定。例如,可以在一个按钮上添加 onClick 属性并将其设置为处理函数的名称。
当用户单击该按钮时,浏览器会触发一个 click 事件。
React 将该事件传递给 onClick 处理函数,并调用该函数。
处理函数可以执行一些操作,例如更新组件的状态或调用其他函数。
总的来说,onClick 事件绑定的工作原理是通过将事件处理函数绑定到 DOM 元素上来实现的。当用户与该元素交互时,浏览器会触发事件并将其传递给 React,最终调用处理函数并执行相关操作。