1. setState是同步还是异步的
setState并非是真异步,只是看上去像异步,在源码中通过isbatchingUpdates来判断setState是是先存进队列还是直接更新,如果值为true则执行异步操作,同步则直接更新,那什么情况下isbatchingUpdates的值为true呢,在react可以控制的地方就为true,比如在react的生命周期事件和合成事件中,都会走合并操作延迟更新的策略,但在react无法控制的地方,比如原生事件,具体就是在addEventListener setTimeout setInterval等事件中就只能同步更新,一般认为做异步设计是为了性能优化,减少渲染次数,但是react团队还补充了两点,第一点是保持内部的一致性,如果将state改为同步更新,那尽管state的更新是同步了,但是props不是。第二点,是为了启用并发更新,完成内部渲染。
React setState 调用之后发生了什么?是同步还是异步?
在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。
在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。
React中setState的第二个参数作用是什么?
setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值:
this.setState({
key1: newState1,
key2: newState2,
...
}, callback) // 第二个参数是 state 更新完成后的回调函数
2. 如何面向组件跨层级通信
在跨层级通信中,主要分为一层或多层的情况。如果只有一层,那么按照react树形结构进行分类的话,主要有以下三种情况:
1.父组件向子组件通信2.子组件向父组件通信3.平级的兄弟组件之间互相通信。
在父与子的情况下,因为react的设计实际上就是传递props,那么场景主要体现在容器组件与展示组件之间,通过props传递state,让展示组件受控,在子与父的情况下,有两种方式:分别是回调函数与实例函数。回调函数,比如输入框向父级组件返回输入内容,按钮向父级组件传递点击事件等。实例函数的情况有些特别,主要是在父级组件中通过react reference API 获取子组件的实例,然后通过实例调用子组件的实例函数,这种方式在过去通常见于model框的显示与隐藏,这样的代码风格有着明显的Jquery时代特征,在现在的react社区中已经非常少见了,因为流行的做法是希望组件的所有能力都可以通过props来控制。
多层级的数据通信有两种情况:第一种是一个容器中包含了多层子组件,需要从最底部的子组件与顶部的组件进行通信,在这种情况下,如果不断的透传props或回调函数,不仅代码层级太深,而且后续也不太好维护。
第二种是两个组件完全不相关,在整个react组件树的两侧完全不相交,那么基于多层级的通信方案一般有三个,第一个是使用react的context api,最常见的用途是做国际化语言包,第二个是使用全局变量与事件,全局变量通过在windows上挂载新对象的方式实现,这种方式一般用于存储临时的数据,这些数据用于计算或者上报,缺点是渲染时容易引发错误,全局事件就是使用document的自定义事件,因为绑定的事件操作一般都会放在组件的component amount,所以一般会要求两个组件都已经在页面中加载显示,这就导致一定的时序依赖,如果加载时机存在差异,那么很有可能导致两者都没能对应响应事件。 第三个是使用状态管理框架,比如Flux,Redux,Mobx,优点是由于引入了状态管理,使得项目的开发模式与代码结构得以约束,但缺点也很明显,就是学习成本相对较高。
3. 虚拟dom的工作原理
答题思路: 讲概念,说用途,理思路,列优缺点,核心问题,什么原因引发流行
虚拟DOM的工作原理是通过js对象模拟DOM节点,在facebook初期构建react的时候,考虑到要提升代码抽象能力,避免人为的DOM操作,降低代码整体风险等因素,所以引入了虚拟DOM。以react为例,在render函数中写的jsx,通常会在babel插件的作用下编译为react.createElement执行jsx中的属性参数,react.createELement执行后会返回一个PlanObject,他会描述自己的tag类型,props属性以及children情况等。这些planobject通过树形结构组成一棵虚拟dom树,当状态发生变更时,将变更前后的虚拟dom树进行差异比较,这个过程称为diff,生成的结果称为patch,计算之后会渲染patch,完成对真实dom操作。
虚拟DOM的优点主要有三点:1.改善大规模DOM操作的性能 2.规避xss的风险 3.较低的成本实现跨平台开发
虚拟DOM的缺点在社区中主要有两点:1.内存占用较高,因为需要模拟整个网页的真实DOM,高性能应用场景存在难以优化的情况,类似像goole Earth一样的高性能前端应用,在技术选型上往往不会选择react。2.无法进行极致优化,虽然虚拟DOM足以应对绝大部分应用的性能要求,但在一些性能要求高的应用中,虚拟DOM无法进行针对性的极致优化。 扩展:除渲染页面,虚拟DOM还有哪些应用场景?比如,只要记录真实DOM变更,它甚至可应用于埋点统计与数据记录等。具体案例可参考rrweb。
4. 与其他框架相比,React的diff算法有何不同
答题思路:原理题: 讲概念,说用途,理思路,优缺点,列一遍,面试官考核的是你在使用中有没有思考,对diff算法有没有特殊的理。答题的时候务必遵循先分类,后讲述的方式,切忌语无伦次,没有区分度。而且你的分类方式还向面试官透露了对知识点的理解度
diff算法是指生成更新补丁的方式,主要应用于虚拟DOM树变化,更新真实DOM,所以diff算法一定存在这样一个过程,触发更新-生成补丁-应用补丁。react的diff算法触发更新的时机主要在state变化与hooks调用之后。此时触发虚拟DOM树变更遍历,采用了深度优先遍历算法,(从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止,然后回溯前一个节点,进行右子树节点遍历,直到遍历完所有可达节点)但传统的遍历方式效率较低,为了优化效率,采用了分治的方式,将单一节点比对转化为了三种类型节点的比对。分别是树,组件和元素,以此来提升效率。
树比对:由于网页视图中较少有跨层级的节点移动,两株虚拟DOM树只对同一层级节点进行比较。
组件比对:如果组件是同一类型,则进行树比对,如果不是,就放入补丁中。
元素比对:主要发生在同层级中,通过标记节点操作生成补丁,节点操作对应真实的DOM节点进行操作。以上是经典的react diff算法内容,自react 16起引入了fiber架构,为了使整个过程可随时暂停恢复,节点与树分别采用了fiberNode与fiberTree进行重构发布,fiberNode使用了双链表的结构,可以直接找到兄弟节点与子节点,整个更新过程由current与workInProgress两株树双缓冲完成。workInProgress更新完成后,再通过修改current的相关指针去指向新的节点,直接抛弃老树。
然后拿Vue和Preact与react的diff算法进行对比:在React-like框架中,Preact适用范围最广,生命力最强,仅以3kb小巧特点应用于对体积追求极致的场景。也正因为体积受限,Preact在diff算法上做了裁剪。pReact的diff算法相较于react整体设计思路相似,最底层的元素采用了真实DOM对比操作,并没有采用fiber的设计。
Vue的diff算法整体也与react相似,同样未采用fiber设计
然后从横向比较:react拥有完整的diff算法策略,且拥有随时中断更新的时间切片能力,在大批量节点更新的极端情况下,拥有更友好的交互体验。Preact可以在一些对性能要求不高仅需要渲染框架的简单场景下应用。vue的diff算法整体与react对齐,虽然缺乏时间切片能力,但这并不意味着vue的性能更差,因为在vue3初期引用过,后期因为收益不高移除掉了。除了高帧率动画,在vue中,其他的场景几乎都可以通过防抖和节流去提高响应性能。
扩展:如何根据React diff算法原理优化代码呢?
- 根据diff算法的设计原则,应尽量避免跨层级节点移动。
- 通过设置唯一key进行优化,尽量减少组件层级深度。因为过深的层级会加深遍历深度,带来性能问题。
- 设置shouldComponentUpdate或者React.pureComponet减少diff次数
5.类组件与函数组件有什么区别?
答题思路: 对组件的两种编写模式是否了解,是否具备在合适的场景下选用合适技术栈的能力。需要找差异点中的共性作为主线。
相同点: 在使用方式和表达效果上并没有什么不同,性能在现代浏览器中也不会有明显的差异。
不同点:
- 本质上代表两种不同设计思想与心智模式,类组件的根基是面向对象编程的OOP,(它主打的是继承生命周期的核心概念)所以它有继承,有属性,有内部状态的管理。而函数组件的根基是FP,也就是函数式编程。相较于类组件,函数组件更纯粹简单,易测试。
- 由于根本思考方式不同,类组件通过生命周期包装业务逻辑。在不使用recompose或者Hooks的情况下,如需使用生命周期,就用类组件,限定场景是固定的。 在recompose或Hooks的加持下,类组件与函数组件的能力边界完全相同,都可以使用类似生命周期等能力。
- 在设计模式上,因为类本身的原因,类组件时可以实现继承的,而函数组件缺少继承能力。当然在react中也是不推荐继承已有的组件的,因为继承的灵活性更差,细节屏蔽过多,所以有这样一条铁律:组合优于继承。
- 性能优化:类组件的优化主要依靠shouldComponentUpdate函数去阻断渲染,而函数组件一般靠React.Memo,缓存渲染结果来提升性能。
- 未来趋势,函数组件成为了社区未来主推的方案,React团队从facebook实际业务出发,通过探索时间切片与并发模式,以及考虑性能的进一步优化与组件间更合理的代码拆分结构后,认为类组件的模式并不能很好的适应未来的趋势。原因:1.this的模糊性2.业务逻辑散落在生命周期中3.React的组件代码缺乏标准的拆分方式。 但是使用Hooks组件可以提供比原先更细粒度的逻辑组织与服用,而且能更好的适用于时间切片与并发模式。
6.如何提升React代码的可维护性?
究其根本是考虑如何提升react项目的可维护性,从软件工程的角度来讲,可维护性包含了可分析性,可改变性,稳定性,易测试性,可维护性的依从性。
可分析性的目标在于快速定位线上问题,可以从预防和兜底两个维度展开工作。预防主要依靠lint工具与团队内部的codeReview。lint工具重在执行代码规划,力图减少不合规的代码,而codeReview的重心在于增强团队内部的透明度,做好业务逻辑层的潜在风险排查。兜底主要在流水线中加入sourceMap能够通过线上报错快速定位源码。
可改编性的目标主要在于使代码易于拓展,业务易于迭代,工作主要从设计模式与架构设计展开。设计模式主要指组件设计模式,通过容器组件与展示组件的划分,模块边界隔绝业务逻辑。整体架构设计采用了rematch方案。
稳定性的目标在于避免修改代码引起不必要的线上问题。主要通过提升核心业务代码的测试覆盖率来完成。在我自己的项目中,核心业务测试覆盖率核算是91%,虽然没有完全覆盖,但基本解决了团队内部恐惧线上出错的心理障碍。
易测试性,目标是易于发现代码潜在问题。
可维护性的依从性,目标是遵循约定,提升代码可读性。减少人为因素。方法是加强工具干预。ESLint,StyleLint,CommitLint,Editorconfig,Prettier。
7.react事件机制相关
问法1:react合成事件是什么,跟原生事件有什么区别?
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。
JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
这样的方式不仅仅减少了内存的消耗,还能在组件挂载销毁时统一订阅和移除事件。 实现合成事件的目的如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
问法2:react事件机制是怎么样的
问法3:React 组件中怎么做事件代理?它的原理是什么?
React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。
在React底层,主要对合成事件做了两件事:
- 事件委派: React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。
- 自动绑定: React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。
react中怎么处理异常
ErrorBoundary只能捕获到生命周期渲染周期内的错误,比如这个子组件开始渲染了,到渲染结束,这个错误就能被捕获到。但是用户子组件渲染完成之后,他点了按钮,触发了事件,调了接口,接口返回了错误,这个错误是不能被捕获到的.
React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代
这三者是目前react解决代码复用的主要方式:
-
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。
-
HOC的优缺点∶
-
优点∶ 逻辑复用、不影响被包裹组件的内部逻辑。
-
缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖
-
-
render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,"render"的命名可以是任何其他有效的标识符。
-
由此可以看到,render props的优缺点也很明显∶
-
优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
-
缺点:无法在 return 语句外访问数据、嵌套写法不够优雅
-
-
通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderltem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。
-
以上可以看出,hook解决了hoc的prop覆盖的问题,同时使用的方式解决了render props的嵌套地狱的问题。hook的优点如下∶
-
使用直观;
-
解决hoc的prop 重名问题;
-
解决render props 因共享数据 而出现嵌套地狱的问题;
-
能在return之外使用数据的问题。
需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。
-
总结∶
Hoc、render props和hook都是为了解决代码复用的问题,但是hoc和render props都有特定的使用场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复用更简洁明了,同时也解决了hoc和render props的一些缺点。
fiber相关
问法1:对React-Fiber的理解,它解决了什么问题?
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。
所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
- 分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
- 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。
核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
问法2:详细解释一下动态优先级: 权重值原理
问法3:怎么实现渲染可中断,任务可中断执行
问法4:fiber 分批处理任务,如何让出浏览器的控制权,好让浏览器可以去执行其他操作
window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。
MessageChannel: segmentfault.com/a/119000004…
问法5:为啥不能用setTimeout 因为会延迟掉帧
问法6:为啥使用链表的数据结构 因为链表比较方便的插入删除元素
问法7:fiber架构实现动画 requestAnimationFrame() 方法
哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?
(1)哪些方法会触发 react 重新渲染?
- **setState()方法被调用
setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。 - 父组件重新渲染
只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render
(2)重新渲染 render 会做些什么?
-
会对新旧 VNode 进行对比,也就是我们所说的Diff算法。
-
对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
-
遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.
在React中如何避免不必要的render?
React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能,其很重要的一个方向,就是避免不必要的渲染(Render)。这里提下优化的点:
- shouldComponentUpdate 和 PureComponent
在 React 类组件中,可以利用 shouldComponentUpdate或者 PureComponent 来减少因父组件更新而触发子组件的 render,从而达到目的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。
- 利用高阶组件
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能
- 使用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo只能用于函数组件。
useMemo,usecallback
对React中Fragment的理解,它的使用场景是什么?
在React中,组件返回的元素只能有一个根元素。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹所有的元素,Fragment标签不会渲染出任何元素。React官方对Fragment的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
React如何获取组件对应的DOM元素?
可以用ref来获取某个子节点的实例,然后通过当前class组件实例的一些特定属性来直接获取子节点实例。ref有三种实现方法:
- 字符串格式:字符串格式,这是React16版本之前用得最多的,例如:
<p ref="info">span</p> - 函数格式:ref对应一个方法,该方法有一个参数,也就是对应的节点实例,例如:
<p ref={ele => this.info = ele}></p> - createRef方法:React 16提供的一个API,使用React.createRef()来实现
对 React context 的理解
在React中,数据传递一般使用props传递数据,维持单向数据流,这样可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不适用。单纯一对的父子组件传递并无问题,但要是组件之间层层依赖深入,props就需要层层传递显然,这样做太繁琐了。
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
可以把context当做是特定一个组件树内共享的store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。
JS的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时JS代码块执行期间所能访问的活动对象,包括变量和函数,JS程序通过作用域链访问到代码块内部或者外部的变量和函数。
假如以JS的作用域链作为类比,React组件提供的Context对象其实就好比一个提供给子组件访问的作用域,而 Context对象的属性可以看成作用域上的活动对象。由于组件 的 Context 由其父节点链上所有组件通 过 getChildContext()返回的Context对象组合而成,所以,组件通过Context是可以访问到其父组件链上所有节点组件提供的Context的属性。
React中怎么检验props?验证props的目的是什么?
React为我们提供了PropTypes以供验证使用。当我们向Props传入的数据无效(向Props传入的数据类型和验证的数据类型不符)就会在控制台发出警告信息。它可以避免随着应用越来越复杂从而出现的问题。并且,它还可以让程序变得更易读。
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
当然,如果项目中使用了TypeScript,那么就可以不用PropTypes来校验,而使用TypeScript定义接口来校验props。
React-Router的实现原理是什么?
客户端路由实现的思想:
-
基于 hash 的路由:通过监听
hashchange事件,感知 hash 的变化- 改变 hash 可以直接通过 location.hash=xxx
-
基于 H5 history 路由:
-
改变 url 可以通过 history.pushState 和 resplaceState 等,会将URL压入堆栈,同时能够应用
history.go()等 API -
监听 url 的变化可以通过自定义事件触发实现
-
react-router 实现的思想:
- 基于
history库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知 - 通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render
Redux 原理及工作流程
(1)原理
Redux源码主要分为以下几个模块文件
- compose.js 提供从右到左进行函数式编程
- createStore.js 提供作为生成唯一store的函数
- combineReducers.js 提供合并多个reducer的函数,保证store的唯一性
- bindActionCreators.js 可以让开发者在不直接接触dispacth的前提下进行更改state的操作
- applyMiddleware.js 这个方法通过中间件来增强dispatch的功能
(2)工作流程
-
const store= createStore(fn)生成数据;
-
action: {type: Symble('action01), payload:'payload' }定义行为;
-
dispatch发起action:store.dispatch(doSomething('action001'));
-
reducer:处理action,返回新的state;
通俗点解释:
-
首先,用户(通过View)发出Action,发出方式就用到了dispatch方法
-
然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
-
State—旦有变化,Store就会调用监听函数,来更新View
以 store 为核心,可以把它看成数据存储中心,但是他要更改数据的时候不能直接修改,数据修改更新的角色由Reducers来担任,store只做存储,中间人,当Reducers的更新完成以后会通过store的订阅来通知react component,组件把新的状态重新获取渲染,组件中也能主动发送action,创建action后这个动作是不会执行的,所以要dispatch这个action,让store通过reducers去做更新React Component 就是react的每个组件。
Redux 和 Vuex 有什么区别,它们的共同思想
(1)Redux 和 Vuex区别
-
Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
-
Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
-
Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)
通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;
(2)共同思想
-
单—的数据源
-
变化可以预测
本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。
redux 异步请求
redux-saga react-thunk
mobox 和 redux 有什么区别?
(1)共同点
- 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;
- 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
- 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
- 支持将store与React组件连接,如react-redux,mobx- react;
(2)区别
Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶
- Action∶ 一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性∶
o type∶ action 类型;
o payload∶ 负载数据;
- Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;
- Store∶ 管理action和reducer及其关系的对象,主要提供以下功能∶
o 维护应用状态并支持访问状态(getState());
o 支持监听action的分发,更新状态(dispatch(action));
o 支持订阅store的变更(subscribe(listener));
-
异步流∶ 由于Redux所有对store状态的变更,都应该通过action触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入React组件中,就需要使用其他框架配合管理异步任务流程,如redux-thunk,redux-saga等;
Mobx是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶
-
Action∶定义改变状态的动作函数,包括如何变更状态;
-
Store∶ 集中管理模块状态(State)和动作(action)
-
Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据
对比总结:
- redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中
- redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作
- redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改
- mobx相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而redux提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
react hooks 解决了哪些问题
(1)在组件之间复用状态逻辑很难
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。
可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
(2)复杂组件变得难以理解
在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
(3)难以理解的 class
除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术
React Hook 的使用限制有哪些?
React Hooks 的限制主要有两条:
-
不要在循环、条件或嵌套函数中调用 Hook;
-
在 React 的函数组件中调用 Hook。
那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。
-
组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。
-
复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。
-
人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,希望在编译优化层面做出一些改进。
这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks 基于函数组件开始设计。然而第三个问题决定了 Hooks 只支持函数组件。
那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。
这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。
useEffect 与 useLayoutEffect 的区别
(1)共同点
-
运用效果: useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。
-
使用方式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl方法,在使用上也没什么差异,基本可以直接替换。
(2)不同点
-
使用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。
-
使用效果: useEffect是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变DOM后渲染),不会产生闪烁。useLayoutEffect总是比useEffect先执行。
在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清,先用 useEffect,一般问题不大;如果页面有异常,再直接替换为 useLayoutEffect 即可。
lazy
key的作用
React 中的高阶组件运用了什么设计模式
使用了装饰模式,高阶组件的运用:
function withWindowWidth(BaseComponent) {
class DerivedClass extends React.Component {
state = {
windowWidth: window.innerWidth,
}
onResize = () => {
this.setState({
windowWidth: window.innerWidth,
})
}
componentDidMount() {
window.addEventListener('resize', this.onResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
render() {
return <BaseComponent {...this.props} {...this.state}/>
}
}
return DerivedClass;
}
const MyComponent = (props) => {
return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);
装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:
@testable
class MyTestableClass {
}