lao3d wordpress 插件,网站怎么做关键词优化,未备案网站如何加cdn,服装网站建设规定谈一谈你对 React 的理解 对待这类概念题#xff0c;讲究一个四字口诀“概用思优”#xff0c;即“讲概念#xff0c;说用途#xff0c;理思路#xff0c;优缺点#xff0c;列一遍” 。 React 是一个网页 UI 框架#xff0c;通过组件化的方式解决视图层开发复用的问题讲究一个四字口诀“概用思优”即“讲概念说用途理思路优缺点列一遍” 。 React 是一个网页 UI 框架通过组件化的方式解决视图层开发复用的问题本质是一个组件化框架。
它的核心设计思路有三点分别是声明式、组件化与 通用性。
声明式的优势在于直观与组合。组件化的优势在于视图的拆分与模块复用可以更容易做到高内聚低耦合。通用性在于一次学习随处编写。比如 React NativeReact 360 等 这里主要靠虚拟 DOM 来保证实现。这使得 React 的适用范围变得足够广无论是 Web、Native、VR甚至 Shell 应用都可以进行开发。这也是 React 的优势。但作为一个视图层的框架React 的劣势也十分明显。它并没有提供完整的一揽子解决方案在开发大型前端应用时需要向社区寻找并整合解决方案。虽然一定程度上促进了社区的繁荣但也为开发者在技术选型和学习适用上造成了一定的成本。
为什么 React 要用 JSX 为什么采用该技术方案”这一类问题其实是考察你的技术广度和技术方案能力使用“一句话解释核心概念方案对比”的解题思路 在回答问题之前我首先解释下什么是 JSX 吧。JSX 是一个 JavaScript 的语法扩展结构类似 XML。所以从这里可以看出React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。
接下来与 JSX 以外的三种技术方案进行对比。
首先是模板React 团队认为模板不应该是开发过程中的关注点因为引入了模板语法、模板指令等概念是一种不佳的实现方案。其次是模板字符串模板字符串编写的结构会造成多次内部嵌套使整个结构变得复杂并且优化代码提示也会变得困难重重。最后是 JXON同样因为代码提示困难的原因而被放弃。
所以 React 最后选用了 JSX因为 JSX 与其设计思想贴合不需要引入过多新的概念对编辑器的代码提示也极为友好。
JSX 主要用于声明 React 元素但 React 中并不强制使用 JSX。即使使用了 JSX也会在构建过程中通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
Babel 插件如何实现 JSX 到 JS 的编译
它的实现原理是这样的。Babel 读取代码并解析生成 AST再将 AST 传入插件层进行转换在转换时就可以将 JSX 的结构转换为 React.createElement 的函数。
如何避免生命周期中的坑 “如何避免坑”换种思维思考也就是“为什么会有坑”在代码编写中遇到的坑往往会有两种 在不恰当的时机调用了不合适的代码 在需要调用时却忘记了调用。 通过梳理生命周期明确周期函数职责确认什么时候该做什么事儿以此来避免坑。
当我们在讨论 React 组件生命周期的时候一定是在讨论类组件Class Component
类组件与函数组件有什么区别呢 作为组件而言类组件与函数组件在使用与呈现上没有任何不同性能上在现代浏览器中也不会有明显差异。
它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的它主打的是继承、生命周期等核心概念而函数组件内核是函数式编程主打的是 immutable、没有副作用、引用透明等特点。
之前在使用场景上如果存在需要使用生命周期的组件那么主推类组件设计模式上如果需要使用继承那么主推类组件。
但现在由于 React Hooks 的推出生命周期概念的淡出函数组件可以完全取代类组件。
其次继承并不是组件最佳的设计模式官方更推崇“组合优于继承”的设计概念所以类组件在这方面的优势也在淡出。
性能优化上类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能而函数组件依靠React.memo 缓存渲染结果来提升性能。
从上手程度而言类组件更容易上手从未来趋势上看由于React Hooks 的推出函数组件成了社区未来主推的方案。
类组件在未来时间切片与并发模式中由于生命周期带来的复杂度并不易于优化。而函数组件本身轻量简单且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用更能适应 React 的未来发展。
如何设计 React 组件
React 组件应从设计与工程实践两个方向进行探讨。
从设计上而言社区主流分类的方案是展示组件与灵巧组件。
展示组件内部没有状态管理仅仅用于最简单的展示表达。展示组件中最基础的一类组件称作代理组件。代理组件常用于封装常用属性、减少重复代码。很经典的场景就是引入 Antd 的 Button 时你再自己封一层。如果未来需要替换掉 Antd 或者需要在所有的 Button 上添加一个属性都会非常方便。基于代理组件的思想还可以继续分类分为样式组件与布局组件两种分别是将样式与布局内聚在自己组件内部。
灵巧组件由于面向业务其功能更为丰富复杂性更高复用度低于展示组件。最经典的灵巧组件是容器组件。在开发中我们经常会将网络请求与事件处理放在容器组件中进行。容器组件也为组合其他组件预留了一个恰当的空间。还有一类灵巧组件是高阶组件。高阶组件被 React 官方称为 React 中复用组件逻辑的高级技术它常用于抽取公共业务逻辑或者提供某些公用能力。常用的场景包括检查登录态或者为埋点提供封装减少样板代码量。高阶组件可以组合完成链式调用如果基于装饰器使用就更为方便了。高阶组件中还有一个经典用法就是反向劫持通过重写渲染函数的方式实现某些功能比如场景的页面加载圈等。但高阶组件也有两个缺陷第一个是静态方法不能被外部直接调用需要通过向上层组件复制的方式调用社区有提供解决方案使用 hoist-non-react-statics 可以解决第二个是 refs 不能透传使用 React.forwardRef API 可以解决。
从工程实践而言通过文件夹划分的方式切分代码。我初步常用的分割方式是将页面单独建立一个目录将复用性略高的 components 建立一个目录在下面分别建立 basic、container 和 hoc 三类。这样可以保证无法复用的业务逻辑代码尽量留在 Page 中而可以抽象复用的部分放入 components 中。其中 basic 文件夹放展示组件由于展示组件本身与业务关联性较低所以可以使用 Storybook 进行组件的开发管理提升项目的工程化管理能力。
setState 是同步更新还是异步更新 setState 并非真异步只是看上去像异步。在源码中通过 isBatchingUpdates 来判断 setState 是先存进 state 队列还是直接更新如果值为 true 则执行异步操作为 false 则直接更新。
那么什么情况下 isBatchingUpdates 会为 true 呢在 React 可以控制的地方就为 true比如在 React 生命周期事件和合成事件中都会走合并操作延迟更新的策略。
但在 React 无法控制的地方比如原生事件具体就是在 addEventListener 、setTimeout、setInterval 等事件中就只能同步更新。
一般认为做异步设计是为了性能优化、减少渲染次数React 团队还补充了两点。
保持内部一致性。如果将 state 改为同步更新那尽管 state 的更新是同步的但是 props不是。启用并发更新完成异步渲染。 如何面向组件跨层级通信
在跨层级通信中主要分为一层或多层的情况。
如果只有一层那么按照 React 的树形结构进行分类的话主要有以下三种情况父组件向子组件通信子组件向父组件通信以及平级的兄弟组件间互相通信。
在父与子的情况下因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展示组件之间通过 Props 传递 state让展示组件受控。在子与父的情况下有两种方式分别是回调函数与实例函数。回调函数比如输入框向父级组件返回输入内容按钮向父级组件传递点击事件等。实例函数的情况有些特别主要是在父组件中通过 React 的 ref API 获取子组件的实例然后是通过实例调用子组件的实例函数。这种方式在过去常见于 Modal 框的显示与隐藏。这样的代码风格有着明显的 jQuery 时代特征在现在的 React 社区中已经很少见了因为流行的做法是希望组件的所有能力都可以通过 Props 控制。
多层级间的数据通信有两种情况。第一种是一个容器中包含了多层子组件需要最底部的子组件与顶部组件进行通信。在这种情况下如果不断透传 Props 或回调函数不仅代码层级太深后续也很不好维护。第二种是两个组件不相关在整个 React 的组件树的两侧完全不相交。那么基于多层级间的通信一般有三个方案。
第一个是使用 React 的 Context API最常见的用途是做语言包国际化。第二个是使用全局变量与事件。全局变量通过在 Windows 上挂载新对象的方式实现这种方式一般用于临时存储值这种值用于计算或者上报缺点是渲染显示时容易引发错误。全局事件就是使用 document 的自定义事件因为绑定事件的操作一般会放在组件的 componentDidMount 中所以一般要求两个组件都已经在页面中加载显示这就导致了一定的时序依赖。如果加载时机存在差异那么很有可能导致两者都没能对应响应事件。第三个是使用状态管理框架比如 Flux、Redux 及 Mobx。优点是由于引入了状态管理使得项目的开发模式与代码结构得以约束缺点是学习成本相对较高。
列举一种你了解的 React 状态管理框架
首先介绍 FluxFlux 是一种使用单向数据流的形式来组合 React 组件的应用架构。
Flux 包含了 4 个部分分别是 Dispatcher、 Store、View、Action。Store 存储了视图层所有的数据当 Store 变化后会引起 View 层的更新。如果在视图层触发一个 Action就会使当前的页面数据值发生变化。Action 会被 Dispatcher 进行统一的收发处理传递给 Store 层Store 层已经注册过相关 Action 的处理逻辑处理对应的内部状态变化后触发 View 层更新。
Flux 的优点是单向数据流解决了 MVC 中数据流向不清的问题使开发者可以快速了解应用行为。从项目结构上简化了视图层设计明确了分工数据与业务逻辑也统一存放管理使在大型架构的项目中更容易管理、维护代码。
其次是 ReduxRedux 本身是一个 JavaScript 状态容器提供可预测化状态的管理。社区通常认为 Redux 是 Flux 的一个简化设计版本但它吸收了 Elm 的架构思想更像一个混合产物。它提供的状态管理简化了一些高级特性的实现成本比如撤销、重做、实时编辑、时间旅行、服务端同构等。
Redux 的核心设计包含了三大原则单一数据源、纯函数 Reducer、State 是只读的。
Redux 中整个数据流的方案与 Flux 大同小异。
Redux 中的另一大核心点是处理“副作用”AJAX 请求等异步工作或不是纯函数产生的第三方的交互都被认为是 “副作用”。这就造成在纯函数设计的 Redux 中处理副作用变成了一件至关重要的事情。社区通常有两种解决方案
第一类是在 Dispatch 的时候会有一个 middleware 中间件层拦截分发的 Action 并添加额外的复杂行为还可以添加副作用。第一类方案的流行框架有 Redux-thunk、Redux-Promise、Redux-Observable、Redux-Saga 等。
第二类是允许 Reducer 层中直接处理副作用采取该方案的有 React LoopReact Loop 在实现中采用了 Elm 中分形的思想使代码具备更强的组合能力。
除此以外社区还提供了更为工程化的方案比如 rematch 或 dva提供了更详细的模块架构能力提供了拓展插件以支持更多功能。
Redux 的优点很多结果可预测代码结构严格易维护模块分离清晰且小函数结构容易编写单元测试Action 触发的方式可以在调试器中使用时间回溯定位问题更简单快捷单一数据源使服务端同构变得更为容易社区方案多生态也更为繁荣。
最后是 MobxMobx 通过监听数据的属性变化可以直接在数据上更改触发UI 的渲染。在使用上更接近 Vue比起 Flux 与 Redux 的手动挡的体验更像开自动挡的汽车。Mobx 的响应式实现原理与 Vue 相同以 Mobx 5 为分界点5 以前采用 Object.defineProperty 的方案5 及以后使用 Proxy 的方案。它的优点是样板代码少、简单粗暴、用户学习快、响应式自动更新数据让开发者的心智负担更低。
Virtual DOM 的工作原理是什么
虚拟 DOM 的工作原理是通过 JS 对象模拟 DOM 的节点。在 Facebook 构建 React 初期时考虑到要提升代码抽象能力、避免人为的 DOM 操作、降低代码整体风险等因素所以引入了虚拟 DOM。
虚拟 DOM 在实现上通常是 Plain Object以 React 为例在 render 函数中写的 JSX 会在 Babel 插件的作用下编译为 React.createElement 执行 JSX 中的属性参数。
React.createElement 执行后会返回一个 Plain Object它会描述自己的 tag 类型、props 属性以及 children 情况等。这些 Plain Object 通过树形结构组成一棵虚拟 DOM 树。当状态发生变更时将变更前后的虚拟 DOM 树进行差异比较这个过程称为 diff生成的结果称为 patch。计算之后会渲染 Patch 完成对真实 DOM 的操作。
虚拟 DOM 的优点主要有三点改善大规模 DOM 操作的性能、规避 XSS 风险、能以较低的成本实现跨平台开发。
虚拟 DOM 的缺点在社区中主要有两点。
内存占用较高因为需要模拟整个网页的真实 DOM。
高性能应用场景存在难以优化的情况类似像 Google Earth 一类的高性能前端应用在技术选型上往往不会选择 React。
与其他框架相比React 的 diff 算法有何不同
在回答有何不同之前首先需要说明下什么是 diff 算法。
diff 算法是指生成更新补丁的方式主要应用于虚拟 DOM 树变化后更新真实 DOM。所以 diff 算法一定存在这样一个过程触发更新 → 生成补丁 → 应用补丁。
React 的 diff 算法触发更新的时机主要在 state 变化与 hooks 调用之后。此时触发虚拟 DOM 树变更遍历采用了深度优先遍历算法。但传统的遍历方式效率较低。为了优化效率使用了分治的方式。将单一节点比对转化为了 3 种类型节点的比对分别是树、组件及元素以此提升效率。
树比对由于网页视图中较少有跨层级节点移动两株虚拟 DOM 树只对同一层次的节点进行比较。
组件比对如果组件是同一类型则进行树比对如果不是则直接放入到补丁中。
元素比对主要发生在同层级中通过标记节点操作生成补丁节点操作对应真实的 DOM 剪裁操作。
以上是经典的 React diff 算法内容。自 React 16 起引入了 Fiber 架构。为了使整个更新过程可随时暂停恢复节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构可以直接找到兄弟节点与子节点。
整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后再通过修改 current 相关指针指向新节点。
然后拿 Vue 和 Preact 与 React 的 diff 算法进行对比。
Preact 的 Diff 算法相较于 React整体设计思路相似但最底层的元素采用了真实 DOM 对比操作也没有采用 Fiber 设计。Vue 的 Diff 算法整体也与 React 相似同样未实现 Fiber 设计。
然后进行横向比较React 拥有完整的 Diff 算法策略且拥有随时中断更新的时间切片能力在大批量节点更新的极端情况下拥有更友好的交互体验。
Preact 可以在一些对性能要求不高仅需要渲染框架的简单场景下应用。
Vue 的整体 diff 策略与 React 对齐虽然缺乏时间切片能力但这并不意味着 Vue 的性能更差因为在 Vue 3 初期引入过后期因为收益不高移除掉了。除了高帧率动画在 Vue 中其他的场景几乎都可以使用防抖和节流去提高响应性能。
如何解释 React 的渲染流程
React 的渲染过程大致一致但协调并不相同以 React 16 为分界线分为 Stack Reconciler 和 Fiber Reconciler。这里的协调从狭义上来讲特指 React 的 diff 算法广义上来讲有时候也指 React 的 reconciler 模块它通常包含了 diff 算法和一些公共逻辑。
回到 Stack Reconciler 中Stack Reconciler 的核心调度方式是递归。调度的基本处理单位是事务它的事务基类是 Transaction这里的事务是 React 团队从后端开发中加入的概念。在 React 16 以前挂载主要通过 ReactMount 模块完成更新通过 ReactUpdate 模块完成模块之间相互分离落脚执行点也是事务。
在 React 16 及以后协调改为了 Fiber Reconciler。它的调度方式主要有两个特点第一个是协作式多任务模式在这个模式下线程会定时放弃自己的运行权利交还给主线程通过requestIdleCallback 实现。第二个特点是策略优先级调度任务通过标记 tag 的方式分优先级执行比如动画或者标记为 high 的任务可以优先执行。Fiber Reconciler的基本单位是 FiberFiber 基于过去的 React Element 提供了二次封装提供了指向父、子、兄弟节点的引用为 diff 工作的双链表实现提供了基础。
在新的架构下整个生命周期被划分为 Render 和 Commit 两个阶段。Render 阶段的执行特点是可中断、可停止、无副作用主要是通过构造 workInProgress 树计算出 diff。以 current 树为基础将每个 Fiber 作为一个基本单位自下而上逐个节点检查并构造 workInProgress 树。这个过程不再是递归而是基于循环来完成。
在执行上通过 requestIdleCallback 来调度执行每组任务每组中的每个计算任务被称为 work每个 work 完成后确认是否有优先级更高的 work 需要插入如果有就让位没有就继续。优先级通常是标记为动画或者 high 的会先处理。每完成一组后将调度权交回主线程直到下一次 requestIdleCallback 调用再继续构建 workInProgress 树。
在 commit 阶段需要处理 effect 列表这里的 effect 列表包含了根据 diff 更新 DOM 树、回调生命周期、响应 ref 等。
但一定要注意这个阶段是同步执行的不可中断暂停所以不要在 componentDidMount、componentDidUpdate、componentWiilUnmount 中去执行重度消耗算力的任务。
如果只是一般的应用场景比如管理后台、H5 展示页等两者性能差距并不大但在动画、画布及手势等场景下Stack Reconciler 的设计会占用占主线程造成卡顿而 fiber reconciler 的设计则能带来高性能的表现。
React 的渲染异常会造成什么后果
React 渲染异常的时候在没有做任何拦截的情况下会出现整个页面白屏的现象。它的成型原因是在渲染层出现了 JavaScript 的错误导致整个应用崩溃。这种错误通常是在 render 中没有控制好空安全使值取到了空值。
所以在治理上我的方案是这样的从预防与兜底两个角度去处理。
在预防策略上引入空安全相关的方案在做技术选型时我主要考虑了三个方案第一个是引入外部函数比如 Facebook 的 idx 或者 Lodash.get第二个是引入 Babel 插件使用 ES 2020 的标准——可选链操作符第三个是 TypeScript它在 3.7 版本以后可以直接使用可选链操作符。最后我选择了引入 Babel 插件的方案因为这个方案外部依赖少侵入性小而且团队内没有 TS 的项目。
在兜底策略上因为考虑到团队内部和我存在一样的问题就抽取了兜底的公共高阶组件封装成了 NPM 包供团队内部使用。
从最终的数据来看预防与治理方案覆盖了团队内 100% 的 React 项目头三个月兜底组件统计到了日均 10 次的报警信息其中有 10% 是公司关键业务。那么经过分析与统计首先是为关键的 UI 组件添加兜底组件进行拦截然后就是做内部培训对易错点的代码进行指导加强 Code Review。后续到现在线上只收到过 1 次报警。 如何分析和调优性能瓶颈
我负责的业务是 CRM 管理后台用户付费进入操作使用有一套非常标准的业务流程。在我做完性能优化后整个付费率一下提升了 17%效果还可以。
前期管理后台的基础性能数据是没有的我接手后接入了一套 APM 工具才有了基础的性能数据。然后我对指标观察了一周多思考了业务形态发现其实用户对后台系统的加载速度要求并不高但对系统的稳定性要求比较高。我也发现静态资源的加载成功率并不高TP99 的成功率大约在 91%这是因为静态资源直接从服务器拉取服务器带宽形成了瓶颈导致加载失败。我对 Webpack 的构建工作流做了改造支持发布到 CDN改造后 TP99 提升到了 99.9%。
如何避免重复渲染
如何避免重复渲染分为三个步骤选择优化时机、定位重复渲染的问题、引入解决方案。
优化时机需要根据当前业务标准与页面性能数据分析来决定是否有必要。如果卡顿的情况在业务要求范围外那确实没有必要做如果有需要那就进入下一步——定位。
定位问题首先需要复现问题通常采用还原用户使用环境的方式进行复现然后使用 Performance 与 React Profiler 工具进行分析对照卡顿点与组件重复渲染次数及耗时排查性能问题。
通常的解决方案是加 PureComponent 或者使用 React.memo 等组件缓存 API减少重新渲染。但错误的使用方式会使其完全无效比如在 JSX 的属性中使用箭头函数或者每次都生成新的对象那基本就破防了。
针对这样的情况有三个解决方案
缓存通常使用 reselect 缓存函数执行结果来避免产生新的对象不可变数据使用数据 ImmutableJS 或者 immerjs 转换数据结构手动控制自己实现 shouldComponentUpdate 函数但这类方案一般不推荐因为容易带来意想不到的 Bug可以作为保底手段使用。
通过以上的手段就可以避免无效渲染带来的性能问题了
如何提升 React 代码可维护性
如何提升 React 代码的可维护性究其根本是考虑如何提升 React 项目的可维护性。从软件工程的角度出发可维护性包含了可分析性、可改变性、稳定性、易测试性与可维护性的依从性接下来我从这五个方面对相关工作进行梳理。 可分析性的目标在于快速定位线上问题可以从预防与兜底两个维度展开工作预防主要依靠 Lint 工具与团队内部的 Code Review。Lint 工具重在执行代码规划力图减少不合规的代码而 Code Review 的重心在于增强团队内部的透明度做好业务逻辑层的潜在风险排查。兜底主要是在流水线中加入 sourcemap能够通过线上报错快速定位源码。 可改变性的目标在于使代码易于拓展业务易于迭代。工作主要从设计模式与架构设计展开。设计模式主要指组件设计模式通过容器组件与展示组件划分模块边界隔绝业务逻辑。整体架构设计采用了 rematch 方案rematch 中可以设计的 model 概念可以很好地收敛 action、reducer 及副作用同时支持动态引入 model保障业务横向拓展的能力。Rematch 的插件机制非常利于做性能优化这方面后续可以展开聊一下。 接下来是稳定性目标在于避免修改代码引起不必要的线上问题。在这方面主要通过提升核心业务代码的测试覆盖率来完成。因为业务发展速度快、UI 变化大所以基于 UI 的测试整体很不划算但背后沉淀的业务逻辑比如购物车计算价格等需要长期复用不时修改那么就得加测试。举个个人案例在我自己的项目中核心业务测试覆盖率核算是 91%虽然没完全覆盖但基本解决了团队内部恐惧线上出错的心理障碍。 然后是易测试性目标在于发现代码中的潜在问题。在我个人负责的项目中采用了 Rematch 的架构完成模块分离整体业务逻辑挪到了 model 中且 model 自身是一个 Pure Object附加了多个纯函数。纯函数只要管理好输入与输出在测试上就很容易。 最后是可维护性的依从性目标在于建立团队规范遵循代码约定提升代码可读性。这方面的工作就是引入工具减少人为犯错的概率。其中主要有检查 JavaScript 的 ESLint检查样式的 stylelint检查提交内容的 commitlint配置编辑器的 editorconfig配置样式的 prettier。总体而言工具的效果优于文档团队内的项目整体可保持一致的风格阅读代码时的切入成本相对较低。
React Hook 的使用限制有哪些
React Hooks 的限制主要有两条 不要在循环、条件或嵌套函数中调用 Hook 在 React 的函数组件中调用 Hook。
那为什么会有这样的限制呢就得从 Hooks 的设计说起。Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。 组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。 复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深导致关联部分难以拆分。 人和机器都很容易混淆类。常见的有 this 的问题但在 React 团队中还有类难以优化的问题他们希望在编译优化层面做出一些改进。
这三个问题在一定程度上阻碍了 React 的后续发展所以为了解决这三个问题Hooks 基于函数组件开始设计。然而第三个问题决定了 Hooks 只支持函数组件。
那为什么不要在循环、条件或嵌套函数中调用 Hook 呢因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中如果使用循环、条件或嵌套函数很有可能导致数组取值错位执行错误的 Hook。当然实质上 React 的源码里不是数组是链表。
这些限制会在编码上造成一定程度的心智负担新手可能会写错为了避免这样的情况可以引入 ESLint 的 Hooks 检查插件进行预防。
useEffect 与 useLayoutEffect 区别在哪里
useEffect 与 useLayoutEffect 的区别在哪里这个问题可以分为两部分来回答共同点与不同点。
它们的共同点很简单底层的函数签名是完全一致的都是调用的 mountEffectImpl在使用上也没什么差异基本可以直接替换也都是用于处理副作用。
那不同点就很大了useEffect 在 React 的渲染过程中是被异步调用的用于绝大多数场景而 LayoutEffect 会在所有的 DOM 变更之后同步调用主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理所以需要避免在 LayoutEffect 做计算量较大的耗时任务从而造成阻塞。
在未来的趋势上两个 API 是会长期共存的暂时没有删减合并的计划需要开发者根据场景去自行选择。React 团队的建议非常实用如果实在分不清先用 useEffect一般问题不大如果页面有异常再直接替换为 useLayoutEffect 即可。
谈谈 React Hook 的设计模式
React Hooks 并没有权威的设计模式很多工作还在建设中在这里我谈一下自己的一些看法。
首先用 Hooks 开发需要抛弃生命周期的思考模式以 effects 的角度重新思考。过去类组件的开发模式中在 componentDidMount 中放置一个监听事件还需要考虑在 componentWillUnmount 中取消监听甚至可能由于部分值变化还需要在其他生命周期函数中对监听事件做特殊处理。在 Hooks 的设计思路中可以将这一系列监听与取消监听放置在一个 useEffect 中useEffect 可以不关心组件的生命周期只需要关心外部依赖的变化即可对于开发心智而言是极大的减负。这是 Hooks 的设计根本。
在这样一个认知基础上我总结了一些在团队内部开发实践的心得做成了开发规范进行推广。
第一点就是 React.useMemo 取代 React.memo因为 React.memo 并不能控制组件内部共享状态的变化而 React.useMemo 更适合于 Hooks 的场景。
第二点就是常量在类组件中我们很习惯将常量写在类中但在组件函数中这意味着每次渲染都会重新声明常量这是完全无意义的操作。其次就是组件内的函数每次会被重新创建如果这个函数需要使用函数组件内部的变量那么可以用 useCallback 包裹下这个函数。
第三点就是 useEffect 的第二个参数容易被错误使用。很多同学习惯在第二个参数放置引用类型的变量通常的情况下引用类型的变量很容易被篡改难以判断开发者的真实意图所以更推荐使用值类型的变量。当然有个小技巧是 JSON 序列化引用类型的变量也就是通过 JSON.stringify 将引用类型变量转换为字符串来解决。但不推荐这个操作方式比较消耗性能。
这是开发实践上的一些操作。那么就设计模式而言还需要顾及 Hooks 的组合问题。在这里我的实践经验是采用外观模式将业务逻辑封装到各自的自定义 Hook 中。比如用户信息等操作就把获取用户、增加用户、删除用户等操作封装到一个 Hook 中。而组件内部是抽空的不放任何具体的业务逻辑它只需要去调用单个自定义 Hook 暴露的接口就行了这样也非常利于测试关键路径下的业务逻辑。
以上就是我在设计上的一些思考。
React-Router 的实现原理及工作方式分别是什么
React Router 路由的基础实现原理分为两种如果是切换 Hash 的方式那么依靠浏览器 Hash 变化即可如果是切换网址中的 Path就要用到 HTML5 History API 中的 pushState、replaceState 等。在使用这个方式时还需要在服务端完成 historyApiFallback 配置。
在 React Router 内部主要依靠 history 库完成这是由 React Router 自己封装的库为了实现跨平台运行的特性内部提供两套基础 history一套是直接使用浏览器的 History API用于支持 react-router-dom另一套是基于内存实现的版本这是自己做的一个数组用于支持 react-router-native。
React Router 的工作方式可以分为设计模式与关键模块两个部分。从设计模式的角度出发在架构上通过 Monorepo 进行库的管理。Monorepo 具有团队间透明、迭代便利的优点。其次在整体的数据通信上使用了 Context API 完成上下文传递。
在关键模块上主要分为三类组件第一类是 Context 容器比如 Router 与 MemoryRouter第二类是消费者组件用以匹配路由主要有 Route、Redirect、Switch 等第三类是与平台关联的功能组件比如 Link、NavLink、DeepLinking 等。
React 中你常用的工具库有哪些
常用的工具库都融入了前端开发工作流中所以接下来我以初始化、开发、构建、检查及发布的顺序进行描述。
首先是初始化。初始化工程项目一般用官方维护的 create-react-app这个工具使用起来简单便捷但 create-react-app 的配置隐藏比较深修改配置时搭配 react-app-rewired 更为合适。国内的话通常还会用 dva 或者 umi 初始化项目它们提供了一站式解决方案。dva 更关心数据流领域的问题而 umi 更关心前端工程化。其次是初始化库一般会用到 create-react-library也基本是零配置开始开发底层用 rollup 进行构建。如果是维护大规模组件的话通常会使用 StoryBook它的交互式开发体验可以降低组件库的维护成本。
再者是开发开发通常会有路由、样式、基础组件、功能组件、状态管理等五个方面需要处理。路由方面使用 React Router 解决它底层封装了 HTML5 的 history API 实现前端路由也支持内存路由。样式方面主要有两个解决方案分别是 CSS 模块化和 CSS in JS。CSS 模块化主要由 css-loader 完成而 CSS in JS 比较流行的方案有 emotion 和 styled-components。emotion 提供 props 接口消灭内联样式styled-components 通过模板字符串提供基础的样式组件。基础组件库方面一般管理后台使用 Antd因为用户基数庞大稳定性好面向 C 端的话主要靠团队内部封装组件。功能组件就比较杂了比如用于实现拖拽的有 react-dnd 和 react-draggablereact-dnd 相对于 react-draggable在拖放能力的抽象与封装上做得更好下层差异屏蔽更完善更适合做跨平台适配PDF 预览用过 react-pdf-viewer视频播放用过 Video-React长列表用过 react-window 与 react-virtualized两者的作者是同一个人react-window 相对于 react-virtualized 体积更小也被作者推荐。最后是状态管理主要是 Redux 与 Mobx这两者的区别就很大了Redux 主要基于全局单一状态的思路Mobx 主要是基于响应式的思路更像 Vue。
然后是构建构建主要是 webpack、Rollup 与 esBuild。webpack 久经考验更适合做大型项目的交付Rollup 常用于打包小型的库更干净便捷esBuild 作为新起之秀性能十分优异与传统构建器相比性能最大可以跑出 100 倍的差距值得长期关注尤其是与 webpack 结合使用这点便于优化 webpack 构建性能。
其次是检查。检查主要是代码规范与代码测试编写。代码规范检查一般是 ESLint再装插件属于常规操作。编写代码测试会用到 jest、enzyme、react-testing-library、react-hooks-testing-libraryjest 是 Facebook 大力推广的测试框架enzyme 是 Aribnb 大力推广的测试工具库基本完整包含了大部分测试场景react-testing-library 与 react-hooks-testing-library 是由社区主推的测试框架功能上与 enzyme 部分有所重合。
最后是发布我所管理的工程静态资源主要托管在 CDN 上所以需要在 webpack 中引入上传插件。这里我使用的是 s3-plugin-webpack主要是识别构建后的静态文件进行上传。