当前位置: 首页 > Web前端 > HTML

Web前端实训React基础知识点

时间:2023-03-28 01:19:16 HTML

以下文章来自前端早茶,作者:广东良子文react的生命周期函数有哪些设置props时调用shouldComponentUpdate判断组件是否存在updatedhtmlcomponentWillupdate组件即将更新时调用componentDidupdatehtml组件更新后立即调用componentWillUnmount调用组件移除前的React生命周期组件和非受控组件之间的区别是模糊的。componentWillMount在React中已经被标记为deprecated,不推荐使用,主要是因为新的异步渲染架构导致它会被多次调用。所以网络请求和事件绑定的代码应该移到componentDidMount。componentWillReceiveProps也被标记为deprecated,被getDerivedStateFromProps取代,主要是性能问题(componentWillReceiveProps是同步更新状态,getDerivedStateFromProps的设计更适合异步场景。)。shouldComponentUpdate判断是否需要通过返回true或false来触发新的渲染。主要用于性能优化。由于新的异步渲染机制,componentWillUpdate也被标记为过时,不推荐使用。原来的逻辑可以结合getSnapshotBeforeUpdate和componentDidUpdate进行改造。如果忘记在componentWillUnmount函数中取消绑定事件,取消定时器等清理操作,很容易导致bug。如果不添加错误边界处理,当渲染出现异常时,用户会看到无法操作的白屏,所以一定要添加。React请求应该放在哪里,为什么?对于异步请求,应该放在componentDidMount中进行操作。从时间顺序来看,除了componentDidMount之外,还可以有以下选项:constructor:可以放,但从设计的角度不推荐。构造函数主要用于初始化状态和函数绑定,不承载业务逻辑。而且随着类属性的普及,构造函数也很少用到。componentWillMount:它已被标记为过时。在新的异步渲染架构下,会触发多次渲染,容易产生bug,不利于以后React升级后的代码维护。所以将React的请求放在componentDidMount中是最好的选择。使用挂钩需要注意什么?useState--set依赖前一个state的状态,不使用函数//initialStateconst[visible,setVisible]=useState(false)//修改state//badsetVisible(!visible)//goodsetVisible(visible=>!可见)useState——它的setter方法其实是异步的。有时,组件在使用钩子时会无缘无故地卡住。这时候可以考虑使用useMemo4。区分useCallback和useMemo。一种是“缓存函数”,一种是在组件内部缓存“函数的返回值”的方法,会成为其他useEffects的依赖。建议用useCallback包裹起来,或者直接写在引用它的useEffect中。如果函数将作为props传递给子组件,请务必使用useCallback使用高阶函数包装场景。使用useMemo类组件和函数组件有什么区别?共同点:它们的实际用途是一样的,无论是高阶组件,还是异步加载,都可以作为基础组件来展示UI。因为组件是React最小的编码单元,无论是函数组件还是类组件,在使用和最终渲染_前端训练上都是完全一致的。区别:基础知识类组件和功能组件本质上代表了两种不同的设计思想和心智模型。类组件的基础是OOP(面向对象编程),因此具有继承性、属性和内部状态管理。功能组件的基础是FP,即函数式编程。属于“结构化编程”的一种,类似于数学函数的思想。即假设输入和输出之间存在特定的映射关系,那么当输入一定时,输出也必然一定。功能组件更纯粹、更简单、更容易测试。唯一性类组件将业务逻辑贯穿整个生命周期,这是类组件所特有的。使用场景如果你需要在不使用Recompose或Hooks的情况下使用生命周期,那么使用类组件。限定场景非常固定;但是有了recompose或者Hooks的加持,这样的界限就模糊了,类组件和函数组件的能力边界完全一致,都可以使用生命周期等能力。设计模式在设计模式中,由于类本身,类组件可以实现继承,而功能组件则缺乏继承能力。当然,不建议继承React中已有的组件,因为继承不够灵活,细节屏蔽太多,所以有组合优于继承这样的铁律。性能优化组件的优化主要依赖shouldComponentUpdate函数来阻塞渲染。函数组件一般通过React.memo进行优化。为什么在hooks中不能使用ifelse逻辑判断?确保在每次渲染时以相同的顺序调用Hooks。这允许React在多个useState和useEffect调用之间保持钩子状态正确。在hooks/src/index.js下,找到useState源码。底层调用useReducer通过全局索引获取Hook状态。你通常如何设计React组件?组件分为:显示组件和智能组件显示组件内部没有状态管理,仅用于最简单的显示表达。表示组件中最基本的一类组件称为代理组件。代理组件常用于封装公共属性,减少重复代码。一个很经典的场景就是你引入了Antd的Button,然后自己封了起来。如果以后需要更换Antd或者需要在所有Buttons上加一个属性,会很方便。基于代理组件的思想,又可以进一步分为样式组件和布局组件两类,分别将样式和布局集成到自己的组件中。由于智能组件是面向业务的,其功能更丰富、更复杂,复用性低于展示组件。最经典的智能组件就是容器组件。在开发中,我们经常将网络请求和事件处理放在容器组件中。容器组件也预留了适当的空间来组合其他组件。另一类智能组件是高阶组件。High-levelcomponents被React官方称为React中复用组件逻辑的高级技术。它通常用于提取公共业务逻辑或提供一些通用功能。常见的场景包括检查登录状态,或者为埋点提供封装以减少样板代码量。可以组合高阶组件完成链式调用,基于装饰器使用更方便。高层组件中另一个经典的用法是反向劫持,通过重写渲染函数来实现某些功能,比如场景的页面加载圈。但是,高层组件也有两个缺陷。首先是静态方法不能被外部直接调用,需要拷贝到上层组件中调用。社区已经提供了解决方案,可以使用hoist-non-react-statics解决;第二个是refs不能透传,可以使用React.forwardRefAPI解决。从工程实践的角度来看,代码是分文件夹的。我初步常用的切分方法是为页面创建一个单独的目录,为复用性稍高的组件创建一个目录,创建以下三个类别:basic、container、hoc。这样可以保证不能复用的业务逻辑代码尽量放在Page中,可以抽象复用的部分放到组件中。其中,basic文件夹包含显示组件。由于展示组件本身与业务无关,可以使用Storybook对组件进行开发和管理,提高项目的工程管理能力。setState是同步更新还是异步更新?setState并不是真正的异步,它只是看起来是异步的。源码中使用isBatchingUpdates判断setState是先存入状态队列还是直接更新。如果值为true,则执行异步操作,如果为false,则直接更新。那么什么情况下isBatchingUpdates会为真呢?React可以控制的地方,这是事实。例如,在React生命周期事件和合成事件中,会遵循合并操作和延迟更新的策略。但是在React无法控制的地方,比如原生事件,具体来说就是在addEventListener、setTimeout、setInterval等事件中,只能同步更新。一般认为做异步设计是为了性能优化,减少渲染次数。React团队还补充了两点。保持内部一致性。如果状态是同步更新的,那么虽然状态的更新是同步的,但是道具不是。启用并发更新以完成异步渲染。setState是同步还是异步的关键点:更新队列。组件如何跨层通信?在父子情况下,因为React旨在实际传递props。然后在容器组件和显示组件之间映射场景,通过Props传递状态,从而控制显示组件。在child和parent的情况下,有两种方式,分别是回调函数和实例函数。回调函数,比如输入框向父组件返回输入内容,按钮向父组件传递点击事件等。实例函数的情况有点特殊,主要是在父组件中获取实例通过React的refAPI调用子组件,然后通过实例调用子组件的实例函数。这种方式以前在Modal框的显示和隐藏中比较常用。这种代码风格具有明显的jQuery时代特征,在当前的React社区中并不多见,因为流行的做法是希望通过Props来控制组件的所有能力。多级之间的数据通信有两种情况。第一种是包含多层子组件的容器,需要最底层的子组件与最顶层的组件通信。在这种情况下,如果不断地传递props或者回调函数,不仅代码层次太深,而且以后也很难维护。二是这两个组件是不相关的,在整个React组件树的两边是完全不相交的。那么基于多层次通信_web前端训练一般有三种解决方案。第一种是使用React的ContextAPI,最常见的用途是做语言包国际化。第二种是使用全局变量和事件。全局变量是通过在Windows上挂载新对象来实现的。此方法通常用于值的临时存储。该值用于计算或报告。缺点是渲染显示时容易出错。全局事件是使用document的自定义事件,因为绑定事件的操作一般都放在组件的componentDidMount中,所以一般会要求两个组件都已经加载并显示在页面上,这就导致了一定的时序依赖。如果加载时机有差异,很可能两者都无法响应相应的事件。第三种是使用状态管理框架,比如Flux、Redux、Mobx。好处是由于引入了状态管理,可以约束项目的开发模型和代码结构。缺点是学习成本比较高。VirtualDOM的原理是什么?虚拟DOM的工作原理是通过JS对象模拟DOM的节点。Facebook早期在构建React时,考虑到提高代码抽象能力、避免人工DOM操作、降低代码整体风险等因素,引入了虚拟DOM。VirtualDOM通常被实现为一个PlainObject。以React为例,render函数中编写的JSX在Babel插件的作用下会被编译成React.createElement来执行JSX中的属性参数。React.createElement执行后会返回一个PlainObject,其中会描述自己的标签类型、props属性、children等,这些PlainObject通过树状结构组成一棵虚拟的DOM树。当状态发生变化时,比较变化前后的虚拟DOM树。这个过程称为diff,生成的结果称为patch。经过计算,Patch将被渲染,完成对真实DOM的操作。虚拟DOM的优势主要有3个:提高大规模DOM操作的性能,规避XSS风险,以更低的成本实现跨平台开发。virtualDOM的缺点在社区主要有两点。内存占用高,因为需要模拟整个网页的真实DOM。高性能应用场景难以优化。像GoogleEarth这样的高性能前端应用,在技术选型上往往不会选择React。React的diff算法有何不同?diff算法是指生成更??新补丁的方式,主要用于虚拟DOM树发生变化后更新真实DOM。因此,diff算法必然有这样一个过程:触发更新→生成补丁→应用补丁。在React的diff算法中,触发更新的时机主要是在状态改变和hooks被调用之后。此时触发虚拟DOM树变化遍历,采用深度优先遍历算法。但是传统的遍历方法效率较低。为了优化效率,使用了分而治之的方法。将单节点比较转化为树、组件、元素三种节点的比较,提高效率。树比较:由于webview中很少有跨层级的节点移动,所以两棵虚拟DOM树只比较同一层级的节点。组件比较:如果组件是同一类型,则进行树比较,如果不是,则直接放入补丁中。元素比较:主要发生在同层,通过标记节点操作生成patch,节点操作对应真实的DOM裁剪操作。以上就是经典的Reactdiff算法的内容。从React16开始,引入了Fiber架构。为了让整个更新过程可以随时暂停和恢复,分别使用FiberNode和FiberTree重构节点和树。fiberNode采用双链表结构,可以直接查找兄弟节点和子节点。整个更新过程是通过current树和workInProgress树的双缓冲来完成的。workInProgress更新后,修改当前相关指针指向新节点。然后用React的diff算法对比Vue和Preact。与React相比,Preact的Diff算法整体设计思路相似,但底层元素使用真正的DOM比较操作,而不是Fiber设计。Vue的Diff算法整体上和React类似,同样没有实现Fiber设计。再横向对比一下,React有完整的Diff算法策略,并且有随时中断更新的时间分片能力,在节点大量更新的极端情况下有更友好的交互体验。Preact可以应用在一些对性能要求不高,只需要渲染框架的简单场景。Vue的整体diff策略与React保持一致。虽然它缺少时间切片能力,但这并不意味着Vue的性能更差,因为它在Vue3的早期引入,后来因为回报率低而被移除。除了高帧率动画,Vue中几乎所有其他场景都可以使用防抖和节流来提高响应速度。react更新的过程是怎样的?当React更改props或状态时,它会调用render()方法来创建不同的树。比较同一层的节点不会导致节点折叠。比较不同类型的节点会产生不同的树结构。使用键指定哪些节点在不同的渲染下是稳定的。Diffing算法(reconciliationalgorithm):1.比较不同类型的元素如果节点不同,则拆解原来的树,建立新的树。2.比较相同类型的元素。3.针对子节点递归详细描述React的渲染过程。不一样的,以React16为分界线,分为StackReconciler和FiberReconciler。狭义上,这里的协调特指React的diff算法,广义上有时指的是React的reconciler模块,通常包括diff算法和一些通用逻辑。回到StackReconciler,StackReconciler最核心的调度方式就是递归。调度的基本处理单元是事务,它的事务基类是Transaction。这里的事务是React团队从后端开发中加入的概念。在React16之前,挂载主要是通过ReactMount模块完成的,更新是通过ReactUpdate模块完成的。模块相互分离,执行点也是一个事务。在React16及之后的版本中,reconcile被更改为FiberReconciler。它的调度方法有两个主要特点。首先是协作式多任务模式。这种模式下,线程会周期性的放弃运行权,交还给主线程,通过requestIdleCallback实现。第二个特点是政策优先。调度任务通过标记标签来确定优先级,比如动画,或者标记高的任务可以先执行。FiberReconciler的基本单元是Fiber,它在过去的ReactElement的基础上进行二次封装,提供父子兄弟节点的引用,为diff工作的双链表实现提供基础。在新架构下,整个生命周期分为两个阶段:Render和Commit。Render阶段的执行特点是可中断、可停止、无副作用,主要通过构造workInProgress树来计算diff。以当前树为基础,以每一个Fiber为基本单元,从下到上逐个节点检查构建workInProgress树。这个过程不再是递归的,而是基于循环的。在执行方面,requestIdleCallback用于调度执行每组任务。每个组中的每个计算任务称为工作。每个工作完成后,检查是否有更高优先级的工作需要插入。继续。优先级通常标记为动画或高将首先处理。每组完成后,调度权交还给主线程,直到下一次requestIdleCallback调用,然后继续构建workInProgress树。在commit阶段,需要处理effect列表。这里的效果列表包括根据diff更新DOM树,callback生命周期,responseref等。但是必须注意这个阶段是同步执行的,不能被打断和暂停,所以不要执行消耗大算力的任务在componentDidMount、componentDidUpdate和componentWiilUnmount中。ReactHooks的局限性是什么?主要有两个:1、不要在循环、条件或嵌套函数中调用Hook;2、在React的函数组件中调用Hook。Hooks旨在改进React组件的开发模式。旧的开发模式会遇到三个问题。难以在组件之间重用状态逻辑。过去常见的解决方案是高阶组件、渲染道具和状态管理框架。复杂的组件变得难以理解。生命周期功能与业务逻辑耦合太深,关联部分难以分离。人和机器都容易混淆类。普遍的问题是这个,但是在React团队中也存在class难以优化的问题,他们希望在编译优化层面做一些改进。这三个问题在一定程度上阻碍了React的后续发展,所以为了解决这三个问题,Hooks是基于函数组件设计的。但是第三个问题决定了Hooks只支持函数组件。那么为什么不在循环、条件或嵌套函数中调用Hooks呢?因为Hooks的设计是基于数组实现的。调用时按顺序添加到数组中。如果使用循环、条件或者嵌套函数,很可能会导致数组的值错位,执行错误的Hook。当然,本质上React的源码并不是数组,而是链表。