1.react理解
React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案 ,是渐进式框架
特性: JSX 语法 , 单向数据绑定 , 虚拟 DOM , 声明式编程 ,组件化开发
2.react-router 里的 标签和 标签有什么区别?
react-router 接管了其默认的链接跳转行为,与传统的页面跳转有区别的是,Link 的 “跳转” 行为只会触发相匹配的对应的页面内容更新,而不会刷新整个页面。
link做了哪些事:1.有onclick就执行onclick,2.click的时候阻止a标签默认事件,3.根据跳转href用history跳转,此时只是链接变了,并没有刷新页面
3.React Jsx转换成真实DOM过程?
首先我们知道Jsx代码是不会被浏览器识别的,最终都会转化为 React.createElement 形式,babel实现这个过程
createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM
4.React Router的理解,原理?常用的Router组件有哪些?
路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新
react-router主要分成了几个不同的包(这一块先不写了)
常用router组件:
BrowserRouter、HashRouter : 路由模式history,hash
route: Route用于路径的匹配,然后进行组件的渲染
Link、NavLink: 通常路径的跳转是使用Link组件 NavLink是在Link基础之上增加了一些样式属性,例如组件被选中时,发生样式变化 ( activeStyle , activeClassName )
redirect:路由重定向
switch: swich组件的作用适用于当匹配到第一个组件的时候,后面的组件就不应该继续匹配
5.react生命周期
组件挂载时
当组件实例被创建并插入DOM时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
组件更新时
当组件的props或state发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
组件卸载时
当组件从DOM中移除时会调用如下方法
componentWillUnmount()
6.react新生命周期取代了哪些?为什么?
React 新的生命周期方法主要是为了支持 Fiber 架构中的三个新概念而设计的: fiber节点,异步渲染,可中断性
getDerivedStateFromProps(静态方法) 取代了componentWillMount和 componentWillReceiveProps
getSnapshotBeforeUpdate 取代了componentWillUpdate
getDerivedStateFromProps 中禁止了组件去访问 this.props, 由于 this.props 可能在任何时刻发生变化,所以计算出来的 state 对象可能会与旧的 state 对象相同,从而导致状态更新无效和不必要的组件重新渲染。 在 getDerivedStateFromProps 方法中应该始终使用参数中的 nextProps,而不是 this.props。这样可以保证组件状态的计算是基于最新的 props,从而避免了状态更新无效和渲染性能的问题。
getSnapshotBeforeUpdate 方法是在组件的 render 方法被调用后,在更新 DOM 之前被调用的 ,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素信息是可以保证与componentDidUpdate 中一致的。
7.Fiber架构的理解?解决了什么问题?
在 React 16 之前,VirtualDOM 的更新采用的是Stack架构实现的,也就是循环递归方式。不过,这种对比方式有明显的缺陷,就是一旦任务开始进行就无法中断,如果遇到应用中组件数量比较庞大,那么VirtualDOM 的层级就会比较深,带来的结果就是主线程被长期占用,进而阻塞渲染、造成卡顿现象。
fiber:为了避免出现卡顿等问题,我们必须保障在执行更新操作时计算时不能超过16ms,如果超过16ms,就需要先暂停,让给浏览器进行渲染,后续再继续执行更新计算。而Fiber架构就是为了支持“可中断渲染”而创建的。 解决了react在渲染大量dom节点出现丢帧的问题
React Fiber 与浏览器的交互流程如下图。
从架构角度:fiber是对react核心算法的重写(调和过程)
从编码角度:fiber是react内部定义的一种数据结构,是fiber树结构的节点单位,react16新架构下的虚拟dom
主要操作:
为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
在 Fiber 架构中,React 将组件的更新过程分为两个阶段:reconciliation 和 commit。其中 reconciliation 阶段主要负责计算出更新后的 Virtual DOM 树,并确定哪些组件需要进行重新渲染,而 commit 阶段则负责将 Virtual DOM 树的变化映射到真实的 DOM 上。
总结:相比传统的Stack架构,Fiber 将工作划分为多个工作单元,每个工作单元在执行完成后依据剩余时间决定是否让出控制权给浏览器执行渲染。 并且它设置每个工作单元的优先级,暂停、重用和中止工作单元。 每个Fiber节点都是fiber tree上的一个节点,通过子、兄弟和返回引用连接,形成一个完整的fiber tree。
8.react有状态组件和无状态组件的理解及使用场景?、
有状态组件
特点:
是类组件
有继承
可以使用 this
可以使用 react 的生命周期
使用较多, 容易频繁触发生命周期钩子函数, 影响性能
内部使用 state, 维护自身状态的变化, 有状态组件根据外部组件传入的 props 和自身的 state 进行渲染
使用场景:
需要使用到状态的
需要使用状态操作组件的(无状态组件的也可以实现新版本 react hooks 也可实现)
总结:
类组件可以维护自身的状态变量, 即组件的 state, 类组件还有不同的生命周期方法, 可以让开发者能够在组件的不同阶段(挂载、更新、卸载), 对组件做更多的控制。类组件则既可以充当无状态组件, 也可以充当有状态组件。当一个类组件不需要管理自身状态时, 也可称为无状态组件。
无状态组件
特点:
不依赖自身的状态 state
可以是类组件或者函数组件
可以完全避免使用 this 关键字(由于使用的是箭头函数事件无需绑定)
有更高的性能, 当不需要使用生命周期钩子时, 应该首先使用无状态函数组件
组件内部不维护 state, 只根据外部组件传入的 props 进行渲染的组件, 当 props 改变时, 组件重新渲染
使用场景
组件不需要管理 state, 纯展示
优点:
简化代码、专注于 render
组件不需要被实例化, 无生命周期, 提升性能, 输出(渲染)只取决于输入(属性), 无副作用
视图和数据的解耦分离
缺点:
无法使用 ref
无生命周期方法
无法控制组件的重渲染, 因为无法使用 shouldComponentUpdate 方法, 当组件接受到新的属性时则会重渲染
9. React 的事件合成?
所有事件都是委托在id = root的DOM元素中(网上很多说是在document中,17版本不是了);
在应用中所有节点的事件监听其实都是在id = root的DOM元素中触发;
React自身实现了一套事件冒泡捕获机制;
React实现了合成事件SyntheticEvent;
React在17版本不再使用事件池了(网上很多说使用了对象池来管理合成事件对象的创建销毁,那是16版本及之前);
事件一旦在id = root的DOM元素中委托,其实是一直在触发的,只是没有绑定对应的回调函数;
10.React组件之间如何通信?
1.父组件向子组件通讯:
父组件可以向子组件传入props的方式,向子组件进行通讯。
2.子组件向父组件通讯:
props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中。
3.兄弟组件通信:
兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递
例:组件A – 传值 --> 父组件 – 传值 --> 组件B
4.跨层级通讯:
Context 设计⽬的是为了共享那些对于⼀个
组件树⽽⾔是“全局”的数据,
使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据
例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过。
5.发布订阅者模式:
发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event模块进⾏通信。
6.全局状态管理工具:
借助Redux或者Mobx等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态。
11.React服务端渲染怎么做?原理是什么?
服务端渲染(SSR): 指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程
服务端渲染解决的问题:1.SEO由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面 2.加速首屏加载,解决首屏白屏问题
React如何做服务端渲染?
手动搭建SSR框架
使用成熟的SSR框架,如next.js
实现原理:
node server 接收客户端请求,得到当前的请求url 路径,然后在已有的路由表内查找到对应的组件,拿到需要请求的数据,将数据作为 props、context或者store 形式传入组件
然后基于 react 内置的服务端渲染方法 renderToString()把组件渲染为 html字符串在把最终的 html 进行输出前需要将数据注入到浏览器端
浏览器开始进行渲染和节点对比,然后执行完成组件内事件绑定和一些交互,浏览器重用了服务端输出的 html 节点,整个流程结束
12.使用 React hooks 怎么实现类里面的所有生命周期?
在 React 16.8 之前,函数组件也称为无状态组件
13.React事件和原生事件的执行顺序
为什么会有合成事件:
在传统的事件里,不同的浏览器需要兼容不同的写法,在合成事件中React提供统一的事件对象,抹平了浏览器的兼容性差异
React通过顶层监听的形式,通过事件委托的方式来统一管理所有的事件,可以在事件上区分事件优先级,优化用户体验。React在合成事件上对于16版本和17`版本的合成事件有很大不同
执行顺序总结:
16版本先执行原生事件,当冒泡到document时,统一执行合成事件,
17版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件,原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行
14.为什么react元素有一个$$type属性?
目的是为了防止 XSS 攻击。因为 Synbol 无法被序列化,所以 React 可以通过有没有 $$typeof 属性来断出当前的 element 对象是从数据库来的还是自己生成的。如果没有 $$typeof 这个属性,react 会拒绝处理该元素。
15.setState 是同步,还是异步的?
react18之前:setState在不同情况下可以表现为异步或同步。在Promise的状态更新、js原生事件、setTimeout、setInterval…中是同步的。在react的合成事件中,是异步的。
react18之后: setState都会表现为异步(即批处理)。
解释react18之前:
原因: 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
16.如何让 useEffect 支持 async/await?
为什么不支持?
effect function 应该返回一个销毁函数(return返回的 cleanup 函数),如果 useEffect 第一个参数传入 async,返回值则变成了 Promise,会导致 react 在调用销毁函数的时候报错
useEffect如何支持async/await?
创建一个异步函数(async...await 的方式),然后执行该函数。
17. React 中可以做哪些性能优化?
使用纯组件 – 纯组件是指那些不依赖于外部状态或引用的组件
shouldComponentUpdate 优化
不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例)
function MyButton(props) { return ( <button onClick={() => console.log('Button clicked!')}> {props.label} </button> ); }
避免使用内联样式属性;
使用 immutable 不可变数据,在我们项目中使用引用类型时,为了避免对原始数据的影响,一般建议使用 shallowCopy 和 deepCopy 对数据进行处理,但是这样会造成 CPU 和 内存的浪费,所以推荐使用 immutable
优点:
降低了可变带来的复杂度
节省内存 ,immutable 使用结构共享尽量复用内存,没有被引用的对象会被垃圾回收
不会有并发问题(因为数据本身不可变)
拥抱函数编程
给子组件设置一个唯一的 key,因为在 diff 算法中,会用 key 作为唯一标识优化渲染
使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对 于相同的输入,不重复执行;
在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行
路由懒加载