柚子快报激活码778899分享:2024React前端面试题

http://yzkb.51969.com/

1.Diff算法

Diff算法比较过程

第一步:

patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部

第二步:

patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,再分成三种情况 - 有新children,没有旧children,创建新的 - 没有新children,有旧children,删除旧的 - 新children、旧children都有,执行`updateChildren`比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text

第三步:

updateChildren函数子节点进行比较

第一步 头头比较。若相似,旧头新头指针后移(即 `oldStartIdx++` && `newStartIdx++`),真实dom不变,进入下一次循环;不相似,进入第二步。第二步 尾尾比较。若相似,旧尾新尾指针前移(即 `oldEndIdx--` && `newEndIdx--`),真实dom不变,进入下一次循环;不相似,进入第三步。第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 `oldStartIdx++` && `newEndIdx--`),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 `oldEndIdx--` && `newStartIdx++`),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 `newStartIdx++`);否则,vnode对应的dom(`vnode[newStartIdx].elm`)插入当前真实dom序列的头部,新头指针后移(即 `newStartIdx++`)。

但结束循环后,有两种情况需要考虑:

新的字节点数组(newCh)被遍历完(`newStartIdx > newEndIdx`)。那就需要把多余的旧dom(`oldStartIdx -> oldEndIdx`)都删除,上述例子中就是`c,d`; - 新的字节点数组(oldCh)被遍历完(`oldStartIdx > oldEndIdx`)。那就需要把多余的新dom(`newStartIdx -> newEndIdx`)都添加。

React中key的作用

React组件在更新的时候,react就会生成新的完整的虚拟DOM树与之前的虚拟dom进行比对,然后再对有更新的节点相关的位置进行更新。

对比之前往往需要进行匹配和比对,为了匹配的更精准,react希望在列表循环等位置去手动为Element添加key属性,这样对比的时候就可以通过查找key来进行精准匹配。

2.React Hook

React Hook定义:

从具象上来说就为函数组件(纯函数)提供副作用能力的 React API,从抽象意义上来说是确定状态源的解决方案。

Hook 是如何运行的:React在处理函数组件时直接将其作为函数调用,Hook 在函数组件「第一次执行」时的表现和「重渲染」时的表现是不一样的。React 为了保证以同样的姿势调用 Hook,开发者却能在「第一次执行」函数组件和无数次的「重渲染」中得到想要的结果,函数组件「第一次执行」和「重渲染」所调用的 Hook 实际上指向的是不同的 Hook。函数组件在「第一次执行」时所使用的 Hook (useXXX) 指向的是对应的 mountXXX,而在更新时,Hook 指向的是对应的 updateXXX。

Hook 是如何存储的:

React 处理 class 组件的方式是将 class 实例化,所以 class 组件将方法、状态、钩子作为成员属性直接使用就可以了。而 React 在处理函数组件时是将其当作函数调用的,没有实例可言,自然也不能将「状态」或是「副作用」挂在原型上。所以只能将 Hook 放在一个和函数组件有关联的地方,即函数组件在渲染过程中对应的 Fiber Node。函数组件在渲染时将 Hook 按照调用顺序以链表的形式挂在 Fiber Node 上面。其中 Fiber Node 的属性 memoizedState 用来指向Hook 链表的第一个节点,Hook 的属性 memoizedState 用来存放 state 值或 effect回调函数。React 在「第一次执行」函数组件时,组件内部每调用一个 Hook,React 就会在组件的 Hook 链表末尾挂载一个 Hook。在「重渲染」,每调用一次 Hook 会先将其从原链表中取出,进行相应「更新」操作后,再将其挂到新的链表上。如果在嵌套的逻辑中(比如 if 或者 switch )调用了 Hook,就会发生错位的问题。

Hook 是如何实现的:

作为函数而被调用的函数组件,每一次渲染都会生成新的作用域。如果想在函数组件中持久化管理、存储状态,还不想造成全局污染等问题,只能使用闭包;在函数组件初始化时,useReducer 做的事情:1、把自己挂到链表上;2、记录「初始值」;3、返回「初始值」和触发更新的 dispatch 函数。函数组件更新时 useReducer 的逻辑:1、从 Hook 链表中读取当前指向的 Hook;2、 顺序执行当前 Hook 的更新动作队列;3、记录下更新后的状态;4、返回更新后的状态和 dispatch 函数。

如果我们将 setState 放在了一个不受限制(比如顶层作用域、没有依赖参数的 useEffect )的地方会导致「无限」重渲染。 原因就在于每一次 setState 都会触发重渲染(如果是同一setState 在同一次渲染被多次调用只会触发一次),而每次重渲染又会执行setState,这就是个死循环;

每次函数组件「重渲染」时所获取的 state,都是上一次渲染时被塞进更新队列中的更新动作执行后的结果,且每次返回的 state 都是一个全新的 state,而不是旧 state 的引用,state 在当前渲染阶段是不可变的。这样就类似于对当前整个组件中的状态形成了一个快照,而不是实时的。

Effect Hook 的实现其实很简单,函数组件「第一次执行」时,Effect Hook 会把开发者传入的回调函数放到当前 Fiber Node 的更新队列 fiber.updateQueue 内,并在执行 DOM 更新之后逐一调用。

在函数组件「重渲染」时 Effect Hook 会将前后的依赖列表进行比对,如果列表为空或者前后依赖列表值的比对一致,此 Effect Hook 会被标记为不需要执行,但它依然存在于 Hook 链表当中,这样做是为了保证不会发生 Hook 错位的问题。如果依赖列表值的比对前后不一致或是没有依赖列表,此 Effect 会被标记为需要执行,回调函数会在执行 DOM 更新之后被调用。 Effect 回调函数的返回值会在组件 unmount 时执行。

react中useEffect使用async await报错

useEffect 钩子函数的一个特性是清理功能,即return函数。如果你从 useEffect 钩子函数返回任何东西,它必须是一个清理函数,此函数将在组件卸载时运行。相当于类组件中的 componentWillUnmount 生命周期方法。

在 JavaScript 中, async...await 会让程序在等待异步任务完成后才会继续执行。 异步函数也总是返回一个 Promise;如果函数还没有返回,则返回值会自动包装在 Promise 中。

useCallback

允许在多次渲染中缓存函数的 React Hook。fn:想要缓存的函数。此函数可以接受任何参数并且返回任何值。dependencies:有关是否更新 fn 的所有响应式值的一个列表。

useContext

可以读取和订阅组件中的 context。向组件树深层传递数据。

useMemo

是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。在组件的顶层调用 useMemo 来缓存每次重新渲染都需要计算的结果。calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。

3.React生命周期

React的生命周期中常用的有:

constructor,负责数据初始化。

render,将jsx转换成真实的dom节点。

componentDidMount,组件第一次渲染完成时触发。

componentDidUpdate,组件更新完成时触发。

componentWillMount 函数的触发时机是在组件将要装载,在组件render之前调用

componentWillUnmount,组件销毁和卸载时触发。

不常用的有:

getDerivedStateFromProps,更新state和处理回调。

shouldComponentUpdate,用于性能优化。

getSnapshotBeforeUpdate,替代了componentWillUpdate。

React的生命周期中有常用的和不常用的。

常用的有:

- constructor(): 完成了数据的初始化。注意:只要使用了constructor()就必须写super(),否则this指向会出错。

- render(): render()函数会将jsx生成的dom插入到目标节点中。在每次组件更新时,react通过diff算法比较更新前和更新之后的dom节点,找到最小的有差异的dom位置并更新,花费最小的开销。

- componentDidMount(): 组件第一次渲染完成,此时dom节点已经生成,在这里调用接口请求,返回数据后使用setState()更新数据后重新渲染。

- componentDidUpdate(prevProps,prevState): 组件更新完成。每次react重新渲染之后都会进入这个生命周期,可以拿到更新之前的props和state。

- componentWillUnmount(): 在这个生命周期完成组件的数据销毁和卸载,移除所有的定时器和监听。

不常用的有:

- getDerivedStateFromProps(nextProps,prevState): 代替老版的componentWillReceiveProps()。官方将更新state与触发回调重新分配到了componentWillReceiveProps()中,让组件整体的更新逻辑更加清晰,并且在当前生命周期中,禁止使用this.props,强制让开发者们通过比较nextProps和PrevState去保证数据的正确行为。

- shouldComponentUpdate(): return true可以渲染,return false不重新渲染。为什么会出现这个SCU生命周期?主要用于性能优化。也是唯一可以控制组件渲染的生命周期,在setState之后state发生改变组件会重新渲染,在当前生命周期内return false会阻止组件的更新。因为react中父组件重新渲染导致子组件也重新渲染,这时在子组件的当前生命周期内做判断是否真的需要重新渲染。

- getSnapshotBeforeUpdate(prevProps,prevState): 代替componentWillUpdate(),核心区别在于getSnapshotBeforeUpdate()中读取到的dom元素状态是可以保证和componentDidUpdate()中的一致。

4.React组件间传值

 React中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。

父子组件常用的传值方法是当父组件给子组件传值时通过props,子组件向父组件传值通过回调函数来传值。

跨级组件常用的传值方法是props一层一层的传,或者使用context对象,将父组件设置为生产者而子组件都设置对应的contextType即可。

非嵌套关系组件传值的方法是使用共同的父级组件进行props传值,或者通过context传值,推荐使用发布/订阅的自定义事件传值。

React中组件间传值方法有很多,按照不同的组件间关系可以把组件间传值的方法分为:

- 父子组件传值:父子组件传值是最常见的应用场景也是非常简单的一种通信方式,父组件通过向子组件传递props,子组件得到props之后做处理就行。而子组件向父组件传值需要通过回调函数触发,具体操作是父组件将一个函数作为props的属性传递给子组件,子组件通过this.props.xxx()调用父组件的函数,参数根据需要自己进行搭配即可。

- 跨级组件传值:跨级的组件之间传值无非就是重复多个父子组件传值的过程。一般跨级的传值方式有两种,分别是:

- 一层一层的传递props:写法很复杂,耦合程度也很高,如果两个组件之间隔了很多层,那么也会影响中间组件的性能,开销大。不过这种方法也是可以的,如果组件之间的层级不是非常多,可以考虑使用这个方法。

- context对象:context相当于一个全局的变量,是一个大的容器,可以把需要传递的数据放在这个容器中,不论嵌套多深都可以轻易的使用。具体操作是创建一个context对象,需要输入默认值。在父组件中使用生产者标签将需要传值的所有子组件包裹起来。子组件通过指定contextType获取到这个context对象,直接调用this.context即可获得值。

- 非嵌套关系组件传值:就是没有任何包含关系的组件之间的传值,包括了兄弟组件。

对于非嵌套关系组件传值一般用两种方法:

- 通过相同的父组件传值:子组件通过回调函数的形式将数据传给父组件,父组件直接通过属性将数据传递给子组件。

- context:利用共同的父组件context对象进行传值

- 通过发布/订阅进行传递:也可以说是自定义事件。重点是在子组件的componentDidMount生命周期通过声明一个自定义事件,然后在componentWillUnmount生命周期组件销毁时移除自定义事件。

5.ReactRouter基本用法

react的路由保证了界面和URL的同步,拥有简单的API和强大的功能。

react中的路由模式有两种,分别是:hash路由和history路由。

- 首先用析构的方法引入需要用到的路由方式,需要注意的是路由所有的配置都必须被包裹在hash路由或者history路由里面。

- 然后在路由标签内再配置Route标签,它的参数有:path,路由匹配的路径。component,路由匹配成功之后渲染的组件。

- react中路由的跳转使用Link标签,它的参数to指向路由匹配的路径,也需要引入。NavLink标签和Link的区别就是渲染成a标签之后会自带一个class属性,对应的是NavLink标签的active属性。

- react路由中有高阶路由组件withRouter,它和普通路由一样需要引入,主要作用是增加了路由跳转的方式,可以调用history方法进行函数中路由的跳转。

- react中路由的动态传值是一个重点,{/:属性名}和{/属性名/值}搭配的方 式进行传值,在需要接收参数的组件通过this.props.match.params来进行接收。react中路由的query传值是通过0问号的方法将参数拼接在url之后,在需要接收参数的组件通过url.parse(this.props.location.search).query获取参数。

- 路由的重定向需要用组件Redirect来完成,参数to是目标组件。

- 路由的懒加载需要从react中引入Suspense和lazy,引入组件时通过lazy(() => import())来引入,使用Suspense标签将Route包裹起来即可。

react中路由模式分为hash路由和history路由。

hash路由的原理是window.onhashchange监听,url前面会有一个#号,这个就是锚点,每个路由前面都会有#锚点,特点是#之后的数据改变不会发起请求,因此改变hash不会重新加载页面。

history路由的原理是window.history API,在HTML5中新增了pushState和replaceState方法,这两个方法是在浏览器的历史记录栈上做文章,提供了对历史记录做修改的功能,虽然更改了url但是不会向服务器发起请求。history模式虽然去掉了hash模式的#锚点,但是它怕刷新,因为刷新时是真实的请求。而hash模式下,修改的是#锚点之后的信息,浏览器不会将#锚点之后的数据发送到服务器,所以没有问题。

6.setState是同步还是异步

setState在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的。

setState本身不分同步或者异步,而是取决于是否处于batch update中。组件中的所有函数在执行时临时设置一个变量isBatchingUpdates = true,当遇到setState时,如果isBatchingUpdates是true,那么setState就是异步的,如果是false,那么setState就是同步的。那么什么时候isBatchingUpdates会被设置成false呢?

- 当函数运行结束时isBatchingUpdates = false

- 当函数遇到setTimeout、setInterval时isBatchingUpdates = false

- 当dom添加自定义事件时isBatchingUpdates = false

最近的18+版本已经修复了这个问题, 所有情况下都是异步的

7.React事件绑定原理

 - React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过event.nativeEvent可以获取到原生事件。

- React17版本开始所有事件都绑定在root根组件上,之前都是绑定在document上。

- React中event和DOM事件不一样,和Vue也不一样。

React并不是将click事件绑在该div的真实DOM上,而是在root处监听所有支持的事件,当事件发生并冒泡至root处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。另外冒泡到 root上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticBaseEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用event.preventDefault。

如何阻止默认事件

return false 或者 event.preventDefault();

8.React中hooks的优缺点

优点:

- 代码的可读性强,在使用hooks之前比如发布/订阅自定义事件被挂载在componentDidMount生命周期中,然后需要在componentWillUnmount生命周期中将它清除,这样就不便于开发者维护和迭代。在使用hooks之后,通过useEffect可以将componentDidMount生命周期、componentDidUpdate生命周期和componentWillUnmount生命周期聚合起来,方便代码的维护。

- 组件层级变得更浅了,在使用hooks之前通常使用高阶组件HOC的方法来复用多个组件公共的状态,增强组件的功能,这样肯定是加大了组件渲染的开销,损失了性能。但是在hooks中可以通过自定义组件useXxx()的方法将多个组件之间的共享逻辑放在自定义hook中,就可以轻松的进行数据互通。

- 不再需要考虑class组件中this指向的问题,hook在函数组件中不需要通过this.state或者this.fn来获取数据或者方法。

缺点:

hooks的useEffect只包括了componentDidMount、componentDidUpdate还有componentWillUnmount这三个生命周期,对于getSnapshotBeforeUpdate和componentDidCatch等其他的生命周期没有支持。

使用useEffect时候里面不能写太多依赖项,将各个不同的功能划分为多个useEffect模块,将各项功能拆开写,这是遵循了软件设计的“单一职责模式”。如果遇到状态不同步的情况,使用手动传递参数的形式。如果业务复杂,就使用Component代替hooks,hooks的出现并不是取代了class组件,而是在函数组件的基础上可以实现一部分的类似class组件功能。

9.redux

redux是一个独立专门用于做状态管理的JS库(不是react插件库),Redux只有唯一的state状态树,Redux就是一个组件之间数据传递分享的状态管理器框架;

redux工作流程主要包含哪些层次,每个层次的作用是什么:

Store仓库层,数据中心

Reducers,数据初始与修改

Component视图层,用以显示数据,动作触发

ActionCreators,动作派发执行

redux的三大基本原则是什么:

单一数据源

State是只读的

使用纯函数来执行修改

redux的结构层次:

1 store:唯一,只有一颗状态树

2 resucers:有state,只读的,通过纯函数修改

3 component:显示与事件的触发

4 action:动作派发执行

10.react组件名称为什么要大写

React中使用JSX语法,但浏览器无法识别JSX语法,需通过babel对JSX语法进行转义;而如果组件的首字母为小写时,其会被认定为原生DOM标签,创建一个不存在的标签是会报错的。

我们在 React 中都是写的 JSX语法,从 JSX语法 到页面上的 真实DOM大概需要经历以下几个阶段:JSX语法 —> 虚拟DOM(JS对象) —> 真实DOM。

babel在进行转义JSX语法时,是调用了 React.createElement() 这个方法,这个方法需要接收三个参数:type, config, children。第一个参数声明了这个元素的类型。

对比上面两张图,图一中,我在创建自定义组件时没有首字母大写。 而 babel 在转义时把它当成了一个字符串 传递进去了;图二中,我把首字母大写了,babel 在转义时传递了一个变量进去。

问题就在这里,如果传递的是一个字符串,那么在创建虚拟DOM对象时,React会认为这是一个简单的HTML标签,但是这显然不是一个简单的HTML标签,因此去创建一个不存在的标签肯定是会报错的。

如果首字母大写,那么就会当成一个变量传递进去,这个时候React会知道这是一个自定义组件,因此他就不会报错了。

11.HashRouter 和 HistoryRouter的区别和原理

HashRouter和 HistoryRouter的区别:

1. history和hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现。

2. history的url没有'#'号,hash反之。

3. 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。

HashRouter的原理:通过`window.onhashchange`方法获取新URL中hash值,再做进一步处理。

HistoryRouter的原理:通过`history.pushState `使用它做页面跳转不会触发页面刷新,使用`window.onpopstate` 监听浏览器的前进和后退,再做其他处理。

hash模式下url会带有#,需要url更优雅时,可以使用history模式。 需要兼容低版本的浏览器时,建议使用hash模式。 需要添加任意类型数据到记录时,可以使用history模式。

单页应用和多页应用

单页应用(single page application,SPA)将所所有内容放在一个页面中,从而使整个页面更加流畅。就用户体验而言,单击导航可以定位锚点,快速定位相应的部分,并轻松上下滚动。单页应用提供的信息和一些主要内容已经过筛选和控制,可以简单单、方便地阅读和浏览。

多页应用(multi-page application,MPA)是指包含多个独立页面的应用,其中的每个页面都必须重复加载 JS、CSS 等相关资源。多页应用在跳转时,需要刷新整页资源。

12.react17和18的区别

setState自动批处理

什么是批处理?

批处理就是多个状态更新合并成一个次更新。(视图层将多次渲染合并成一次渲染)

在React18以前

我们只在React18中进行批处理。默认情况下,在promise、setTimeout、原生事件处理函数中、或任何其它事件内的更新都不会进行批处理。

在React18以后

所有更新都会自动进行批处理。多次更新将会合并成一次更新,从而降低渲染次数提高性能。

react组件返回空值

在react17中,如果你需要返回一个空组件,ract只允许你返回null。如果你显示的返回了undifined控制台则会在运行时抛出一个错误。

在react18中,不在见出啊因返回undifined而导致奔溃。即能返回null,也能返回undifned。

Concurrent Mode(并发模式)

并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,该模式通过使渲染可中断来修复阻塞渲染限制。在 Concurrent 模式中,React 可以同时更新多个状态。

react17 和 react18的区别就是:从同步不可中断更新变成了异步可中断更新。

开启并发模式:

在React18中提供了新的root Api,我们只需要把render改成ReactDOM.createRoot(root).render() 就可以开启并发模式。

开启并发模式就一定开启并发更新吗?

No!在React18中开启并发模式不一定开启并发更新,而是否开启并发更新的依据是是否使用并发特性。

并发特性指的是开启并发模式才能使用的特性

startTranstionuseTranstionuseDeferredValue

并发更新的意义就是交替执行不同的任务,当预留的时间不够用时,React将线程控制交给浏览器,等待下一帧时间的到来,然后继续被中断的工作。

并发模式是实现并发更新的基本前提。时间切片是实现并发更新的基本手段。

13.单向数据流

数据流就是:数据在组件之间的传递。

单向数据流就是:数据在某个节点被改动后,只会影响一个方向上的其他节点。数据只会影响到下一个层级的节点,不会影响上一个层级的节点。规范数据的流向,数据由外层组件向内层组件进行传递和更新。保证数据的可控性。

14.容器组件

容器组件是内置组件的一种,Smart 组件又称为 容器组件,它负责处理应用的数据以及业务逻辑,同时将状态数据与操作函数作为属性传递给子组件,一般而言它仅维护很少的 DOM,其所有的 DOM 也仅是作为布局等作用。

react受控组件和不受控组件的区别:

1、受控组件依赖于状态,而非受控组件不受状态的控制;

2、受控组件只有继承“React.Component”才会有状态,而非受控组件则不是只有继承才有状态;

3、受控组件一般适用于需要动态设置初始值时,非受控组件一般用于无任何动态初始值信息时。

15.state和props有什么区别

React中的state和props主要有以下区别:

数据来源:props是由父组件传递给子组件的数据,而state是组件内部自己管理的数据。数据类型:props可以传递任何JavaScript类型的数据,包括基本数据类型、对象、数组等。而state一般是对象类型。数据修改:props是只读的,子组件不能直接修改props的值。而state可以由组件自己修改,用于控制组件的状态和重新渲染。生命周期:组件的props可以在组件的生命周期中任意时刻被更新,因为它是由父组件传递给子组件的。而state的更新会触发组件的重新渲染。

总的来说,props主要用于组件之间传递参数和获取组件的属性值,而state则主要用于组件内部管理数据和状态,控制组件的渲染和更新。

16.super()和super(props)有什么区别

在React中,super()和super(props)都用于在子类的构造函数中调用父类的构造函数。这两者的主要区别在于是否传递了props给父类的构造函数。

super():当使用super()时,只是简单地调用了父类的构造函数,并没有传递任何参数。这通常在不需要在子类的构造函数中使用this.props时使用。super(props):当使用super(props)时,正在将props作为参数传递给父类的构造函数。这在需要在子类的构造函数中访问this.props时使用。通过这样做,可以确保this.props在子类的构造函数中是可用的。

需要注意的是,无论是否在子类的构造函数中使用this.props,都应该始终在子类的构造函数中调用super()或super(props)。这是因为在ES6类中,子类没有自己的this上下文,它只能继承父类的this上下文。super()的作用就是将父类的this上下文传递给子类,使得子类能够正确地访问和修改自己的状态。

如果不在子类的构造函数中调用super()或super(props),那么在尝试访问this.props或this.state时会报错,因为这些上下文在子类中尚未被初始化。

17.受控组件和非受控组件

在React中,组件可以分为受控组件(Controlled Components)和非受控组件(Uncontrolled Components),这主要是针对表单元素(如输入框、选择框等)而言的。

受控组件:

是一种由React状态控制的组件。在受控组件中,组件的值由React状态管理。这意味着当表单元素的值发生变化时,它会触发一个事件(通常是onChange事件),然后这个事件会更新组件的状态,从而改变表单元素的值。受控组件的值始终由React的state驱动,这使得React的state成为唯一的数据源。因此,官方推荐使用受控组件,因为它们能更好地控制组件的生命周期和状态。

非受控组件:

是一种不由React状态控制的组件。在非受控组件中,组件的值不由React状态管理,而是由DOM节点直接管理。这意味着表单元素的值变化不会触发onChange事件,也不会更新React的状态。非受控组件的值由DOM节点直接管理,因此它们通常用于那些不需要React状态控制的表单元素,或者当你想让浏览器默认行为生效时。

总的来说,选择使用受控组件还是非受控组件取决于具体需求。如果需要更精细地控制表单元素的值和状态,或者需要在表单元素的值变化时执行一些自定义逻辑,那么受控组件可能更适合。相反,如果只是想让表单元素保持其默认行为,或者不关心表单元素的值变化,那么非受控组件可能更适合。

18.react中引入css的方式

在React中,引入CSS样式有多种方式。以下是其中的一些常见方法:

行内样式:直接在组件内部定义样式。例如,可以在一个div元素中使用style属性来定义宽度和高度。这种方式适用于简单的样式,但对于更复杂的样式,可能会使代码变得难以维护。声明样式:在render函数中先声明一个样式对象,然后在组件中使用该对象。这种方式与行内样式类似,但可以将样式代码分离出来,使代码更加清晰。CSS Modules:使用CSS Modules可以将CSS类名本地化为JavaScript对象,从而避免全局样式冲突。在React中,可以使用CSS Modules来引入CSS样式。首先,需要安装css-loader和style-loader,然后在Webpack配置文件中添加相应的规则。接着,在React组件中,可以使用import语句引入CSS文件,并使用对象语法来引用类名。全局样式:有时,可能需要在整个应用程序中使用一些全局样式。在这种情况下,可以将CSS文件放在项目的根目录下,并在Webpack配置文件中添加相应的全局样式加载规则。然后,在React组件中,可以直接使用类名来引用全局样式。

19.react高阶组件

React高阶组件(Higher-Order Components,简称HOC)是React中一种高级的技术,用于复用组件逻辑。高阶组件本质上是接受一个组件并返回一个新组件的函数。

高阶组件在React社区中非常常见,例如react-redux中的connect方法就是一个典型的高阶组件。使用高阶组件可以实现以下功能:

代码复用:高阶组件可以提取多个组件之间的共享逻辑,使得这些组件可以更加简洁和易于维护。条件渲染:高阶组件可以根据传入的props或其他条件来决定如何渲染组件,从而实现条件渲染。操作props:高阶组件可以在返回的新组件中对传入的props进行处理或增强,例如添加新的props或修改原有的props。抽象状态:高阶组件可以将组件的状态逻辑抽象出来,使得组件更加关注于自身的展示逻辑。

高阶组件有两种常见的实现方式:属性代理和反向继承。属性代理是最常用的方式,它通过将传入的组件作为新组件的一个属性(通常是WrappedComponent),并在新组件的render方法中渲染这个传入的组件,从而实现对传入组件的增强。反向继承则相对较少使用,它通过将传入的组件作为基类,然后创建一个新的子类来扩展这个基类,并在子类中实现额外的逻辑。

需要注意的是,高阶组件并不是React API的一部分,而是基于React的组合特性而形成的设计模式。因此,在使用高阶组件时,需要遵循一些原则,例如不要修改原始组件、保持props的一致性、保持可组合性等。同时,也需要注意高阶组件可能会影响组件的性能,因为每次渲染时都会创建一个新的组件实例。因此,在使用高阶组件时,需要权衡其带来的好处和可能带来的性能问题。

20.在react中组件间过渡动画如何实现

在React中实现组件间的过渡动画,可以使用专门的库来处理动画效果。一个流行的选择是使用react-transition-group。这个库提供了一系列组件,可以实现组件的进入、离开和更新的过渡效果。

21.React项目是如何捕获错误

错误边界(Error Boundaries):React 16引入了错误边界的概念,错误边界是一种特殊的React组件,它可以捕获并打印发生在其子组件树任何位置的JavaScript错误,并且,它可以阻止这些错误冒泡至更高的树层级。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。当错误被捕获后,错误边界可以显示一个备用UI,而不是崩溃的组件树。try-catch语句:在JavaScript中,可以使用try-catch语句来捕获运行时错误。然而,在React中,由于组件的生命周期方法和渲染函数都是同步执行的,因此try-catch通常不能在这些地方捕获错误。不过,可以在事件处理函数或其他异步代码中使用try-catch。React DevTools:React开发者工具是一个浏览器扩展,它可以在开发过程中发现和修复错误。这个工具可以高亮显示组件树中出错的组件,并提供有关错误的详细信息。全局错误处理:可以使用window.onerror或window.addEventListener('error', callback)来捕获全局的JavaScript错误。虽然这些方法可以捕获到许多类型的错误,但它们可能不会捕获到由React组件抛出的所有错误。使用日志和监控工具:例如Sentry、LogRocket等

22.React refs的理解

React refs提供了一种方式,允许访问DOM节点或在render方法中创建的React元素。Refs本质上为ReactDOM.render()返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染DOM则返回的是具体的DOM节点。

Refs的主要使用场景包括:

管理焦点、文本选择或媒体播放。触发强制动画。集成第三方DOM库。

创建ref的形式有三种:

传入字符串,使用时通过this.refs.传入的字符串的格式获取对应的元素。传入对象,对象是通过React.createRef()方式创建出来,使用时获取到创建的对象中存在current属性就是对应的元素。传入函数,该函数会在DOM被挂载时进行回调,这个函数会传入一个元素对象,可以自己保存,使用时直接拿到之前保存的元素对象即可。

在原生DOM元素和自定义组件上,ref的使用方式是不同的。在原生DOM元素上,ref获取到的是DOM元素本身,而在自定义组件中,ref获取到的是组件的子组件。

虽然refs在某些情况下非常有用,但应避免在可以通过声明式方式来完成的情况下使用refs。React团队建议尽可能使用props和state来管理组件的状态和行为,而不是依赖refs。

23.render方法的原理

在React中,render方法是组件生命周期中的一个重要部分,特别是当使用类组件时。它的主要任务是返回应该由React渲染的UI。

render方法的基本原理:

声明式渲染:React是一个声明式库。render方法就是一个声明式接口,它返回一个React元素(可以是DOM元素、组件或其他类型的元素),描述了UI如何显示。纯函数:render方法应该是一个纯函数,这意味着给定相同的输入,它总是返回相同的输出,并且不修改任何外部状态或副作用。这使得React能够有效地重新渲染组件,并确定哪些部分需要更新。虚拟DOM:render方法返回的不是实际的DOM节点,而是一个虚拟DOM(也称为React元素)。React使用这个虚拟DOM来创建或更新实际的DOM,这个过程称为调和(reconciliation)。React会比较新旧虚拟DOM之间的差异,并只更新必要的部分,这有助于保持应用的性能。组件状态:在类组件中,render方法通常会根据组件的状态(this.state)和属性(this.props)来返回不同的UI。当状态或属性发生变化时,render方法会被再次调用,导致组件重新渲染。与生命周期方法的关系:在类组件中,render方法通常与生命周期方法(如componentDidMount、componentDidUpdate和componentWillUnmount)一起使用。这些方法在组件的不同阶段被调用,用于执行特定的任务,如初始化状态、处理用户输入、更新状态等。render方法则负责根据当前的状态和属性渲染UI。

24.Real DOM和Virtual DOM的区别

Real DOM,也称为真实DOM,是浏览器提供的原生DOM API,是对网页上所有内容的抽象表示。当开发者通过JavaScript操作DOM时,实际上是在操作这个Real DOM。然而,由于直接操作DOM可能会导致性能问题,例如频繁的重排和重绘,因此Virtual DOM被引入以提高性能。

Virtual DOM,即虚拟DOM,是一个轻量级的JavaScript对象,是对Real DOM的一种抽象表示。它并不是真实存在的DOM树,而是一个虚拟的节点树。当状态发生变化时,Virtual DOM会生成一个新的虚拟节点树,并与旧的虚拟节点树进行比较,记录两棵树之间的差异。然后,这些差异会被应用到Real DOM上,从而更新页面。

两者的主要区别体现在以下几个方面:

更新机制:Real DOM在更新时,浏览器会对整个页面进行重新渲染,效率较低。而Virtual DOM在更新时,只需要比对前后两个虚拟DOM树的差异,然后只更新需要更新的部分,这样可以大大提高渲染效率。渲染效率:Real DOM在进行频繁的数据操作时,会产生大量的DOM操作,消耗大量的性能和资源。而Virtual DOM通过DOM-diff算法来尽量减少DOM操作的次数,从而提高渲染效率。开发体验:Real DOM开发需要手动维护DOM结构,而Virtual DOM通过模板语法可以更方便地进行组件化开发,提高开发效率。

总的来说,Real DOM和Virtual DOM各有优缺点,选择使用哪种DOM取决于具体的应用场景和需求。在需要高性能和复杂交互的场景下,通常会选择使用Virtual DOM。而在一些简单的场景下,使用Real DOM可能更加方便和直接。

25.React Jsx转换成真实DOM过程

在React中,JSX(JavaScript XML)语法是一种JavaScript的语法扩展,用于描述组件的UI结构。当你在React代码中编写JSX时,它会在编译阶段被转换为普通的JavaScript函数调用,具体来说,就是React.createElement()函数的调用。这个过程通常是由Babel这个JavaScript编译器来完成的。

以下是React JSX转换为真实DOM的过程:

JSX编写:在React组件中,可以使用JSX来编写组件的UI结构。Babel转换:在开发过程中,Babel会负责将JSX代码转换为React.createElement()函数调用。React.createElement():React.createElement()函数接受三个参数:第一个参数是你要创建的DOM元素的类型(如'h1')。第二个参数是一个可选的对象,包含了元素的属性(props)第三个参数以及之后的参数是子元素。Reconciliation(调和):当React元素被创建后,React会开始一个称为“调和”的过程。在这个过程中,React会比较新旧两个React元素(通常是之前渲染过的元素和新创建的元素),并计算出最小化的、必要的DOM变更来更新UI。DOM更新:基于调和的结果,React会生成一个包含所有必要DOM变更的指令列表,然后这些指令会被执行,从而更新实际的DOM。真实DOM:最终,经过上述过程,React会将必要的DOM变更应用到浏览器中的真实DOM上,用户就可以看到更新后的UI。

这个过程确保了React能够高效地更新UI,只进行必要的DOM操作,从而提高了应用的性能。同时,它允许开发者使用声明式的方式来描述UI,而不是手动进行繁琐的DOM操作。

26.Fiber架构的理解

Fiber架构是React团队为了提高应用程序的性能和用户体验而引入的一种新的渲染机制。它的主要思想是将组件的渲染工作划分为多个小任务,并通过优先级调度和可中断、恢复的方式,使得浏览器在执行这些任务时具有更好的反应能力,避免了长时间阻塞主线程。

Fiber架构通过将渲染过程拆分为多个任务单元(fiber)并赋予不同的优先级,实现了增量渲染和可中断、恢复的机制。这样,当浏览器需要处理其他高优先级的任务(例如用户输入事件)时,React可以暂停当前任务,并立即处理其他任务,从而提高用户体验。

此外,Fiber架构也采用了协调与提交两个阶段。协调阶段包含了fiber的创建与diff更新,此过程可暂停。而提交阶段则必须同步执行,保证渲染不卡顿。这种机制使得React能够在保证帧数流畅的同时,总是在浏览器有剩余时间的情况下去完成目前最高优先级的任务。

总的来说,Fiber架构是一种针对React应用程序性能优化的重要机制,它通过优先级调度、增量渲染和可中断、恢复的方式,提高了应用程序的响应能力和用户体验。

27.如何提高组件的渲染效率

避免不必要的重新渲染:使用shouldComponentUpdate生命周期方法或React.memo高阶组件来避免不必要的重新渲染。这些方法允许你比较新旧props或state,并返回一个布尔值来决定是否应该更新组件。使用纯组件:如果你的组件不依赖于外部状态或props,并且给定相同的输入总是返回相同的输出,那么你可以将其标记为纯组件。这可以通过扩展React.PureComponent或使用React.memo来实现。纯组件在更新时会进行props和state的浅比较,如果它们没有变化,则不会触发重新渲染。合理设计state和props:只将需要渲染的state和props传递给组件。避免传递不必要的数据,以减少不必要的渲染和比较操作。使用key属性:在列表渲染中,为每个元素提供一个唯一的key属性。这有助于React识别哪些元素发生了变化,从而进行高效的DOM更新。避免在render方法中使用复杂的计算:将复杂的计算移到组件的构造函数或生命周期方法中,以避免在每次渲染时都进行这些计算。使用React DevTools:使用React的开发者工具来分析组件树和性能瓶颈。这可以帮助你找到需要优化的部分。利用React的缓存机制:React会缓存已经渲染过的组件,当相同的props和state再次出现时,它会跳过渲染过程并直接复用之前的输出。因此,合理设计组件的props和state可以提高渲染效率。使用React的新特性:React团队不断推出新特性来优化性能。例如,React 18引入了新的并发模式(Concurrent Mode),使得React能够更好地处理高优先级的用户交互,同时保持应用的响应性。

28.React性能优化的手段

避免不必要的渲染:这是最重要的一点。React的重新渲染是非常昂贵的操作,因此我们需要尽可能减少不必要的渲染。这可以通过使用shouldComponentUpdate,React.memo,PureComponent等方法来实现。这些方法可以让我们在组件更新前进行一些判断,如果组件的props或state没有变化,那么就可以跳过渲染,直接复用之前的渲染结果。使用key属性:在列表渲染中,为每个元素提供一个唯一的key属性。React使用key来帮助识别哪些元素改变了,比如被添加或删除。这样,React只需更新改变了的元素,而不是重新渲染整个列表。合理拆分组件:将大组件拆分成小组件可以提高性能。因为当父组件更新时,所有子组件也会重新渲染。如果子组件很多,或者某些子组件的渲染非常昂贵,那么这就可能导致性能问题。通过合理拆分组件,我们可以将渲染昂贵的部分拆分成单独的组件,这样当父组件更新时,只有必要的子组件才会重新渲染。使用生产环境构建:使用如Webpack等构建工具进行代码压缩、混淆、分离等操作,减小文件大小,提高加载速度。利用React的新特性:React团队不断推出新特性来优化性能。例如,React 16引入了新的生命周期方法,React 17引入了新的函数组件API,React 18引入了新的并发模式(Concurrent Mode)。了解和利用这些新特性可以帮助我们提高应用的性能。代码层面的优化:避免在render方法中进行复杂的计算或操作,可以将这些操作放到生命周期方法或事件处理函数中。同时,尽量减少在组件中使用行内函数,因为每次渲染都会创建新的函数实例,这可能会导致不必要的性能开销。使用不可变数据(Immutable Data):不可变数据可以降低“可变”带来的复杂度,节省内存。使用不可变数据结构可以避免在渲染过程中产生不必要的中间状态,从而提高性能。

柚子快报激活码778899分享:2024React前端面试题

http://yzkb.51969.com/

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。