个人总结八股文的背诵方案
对比 React 和 Vue
- 相同点:组件化、虚拟DOM、响应式更新
- 区别:
(1)渲染方式:React使用JSX(一种JavaScript语法扩展)来描述组件的结构和行为,将HTML和JavaScript混合在一起。Vue则使用模板语法,将组件的结构和行为放在单独的模板文件中,更接近传统的HTML和CSS开发方式。
(2)状态管理:在React中,状态管理需要使用额外的库(如Redux或Mobx),来管理全局的应用状态。而Vue内置了Vuex,提供了一种方便的方式来管理应用的状态。
(3)diff算法:react的diff算法是基于组件树的递归实现,而vue是使用双端队列实现。
(4)社区支持:由于React的普及度更高,更多的开发者和公司在使用React,因此可以更容易地找到React相关的教程、文章和解决方案。Vue的社区虽然也很活跃,但相对规模较小。
(5)react是单向数据流,vue是双向数据绑定
对比 React 和 Angular:组件化、数据绑定、响应式(angular是RxJS的Observable流)
- 功能:Angular是一个功能丰富的框架,提供了依赖注入、模板、路由、AJAX、表单、CSS封装等标准功能。React是一个UI的组件库,只提供了基本的功能,需要配合第三方的库来实现一些常用的功能,如路由、AJAX、CSS封装。
- 渲染方式:Angular使用TypeScript作为开发语言,React使用JSX(一种JavaScript语法扩展)来描述组件的结构和行为,将HTML和JavaScript混合在一起。
- 性能:Angular使用真实Dom,React使用虚拟Dom和React使用虚拟Dom,可以在更新时最小化DOM操作,从而提高性能。
什么是函数式编程?什么是声明式编程?区别?
- 函数式编程鼓励将计算视为数学函数的组合,通过函数的组合、高阶函数和递归等方式来表达计算逻辑。
- 声明式编程通常使用领域特定语言(DSL)或声明式语法来表达计算逻辑,使代码更加简洁、易读和易理解。声明式编程更关注问题的本质和逻辑,而不是具体的实现细节。
MVC 和 MVVM 的区别
- MVC = Model View Controller。模型 视图 控制器
单向绑定、控制器处理用户输入并根据输入更新视图和模型 - MVVM = Model View ViewModel。模型 视图 视图模型
双向数据绑定、ViewModel维护视图状态和与视图的双向数据绑定
React 有哪些版本?分别有哪些新特性?
- React 15:
虚拟DOM、生命周期方法、JSX语法
- React 16:
新生命周期、render、hooks、懒加载、Portals、Profiler、Fiber
(1)新生命周期:componentDidCatch、getDerivedStateFromProps、getSnapshotBeforeUpdate
(2)render返回类型:之前只能返回react节点,现在可返回Fragments、字符串、数字、布尔值、null等
(3)hooks:useState、useEffect、useContext、useMemo、useCallback、useRef等
(4)memo、lazy、Suspense:memo浅比较;lazy和Suspense搭配使用动态加载组件并占位。
(5)Portals:传送门ReactDOM.createPortal(child, container);
(6)Profiler:能添加在 React 树中的任何地方来测量树中这部分渲染所带来的开销。onRender={callback},三个参数:id、phase 和 actualDuration。
(7)Fiber架构,它是一个新的内部算法,可以让React在渲染过程中中断和恢复,从而实现时间切片(Time Slicing)和并发渲染(Concurrent Rendering)。 - React 17:
JSX转换、事件委托的变更、渐进式升级
(1)JSX转换:使用 JSX 时不再需要引入 React 命名空间。
(2)事件委托的变更:document变更到react的根节点上,提交兼容性和性能。
(3)渐进式升级:可以在同一个应用中同时运行多个版本的react,从而降低升级成本和风险。 - React 18:
新hooks、并发模式、自动批处理
(1)新的内置Hooks:useId、useSyncExternalStore和useDeferredValue,它们可以让开发者更方便地使用并发模式和过渡效果。
(2)并发模式:它是一个可选的模式,可以让React在渲染过程中中断和恢复,从而提高用户体验和性能。
(3)自动批处理:它是一个默认的优化,可以让React在同一个事件循环中自动合并多个状态更新,从而减少不必要的渲染。
(4)新的startTransition API:它可以让开发者显式地标记某些状态更新为低优先级的,从而避免阻塞高优先级的更新。
组件
函数组件和类组件区别
- 代码方面:函数式组件更简洁,代码量更少,易于阅读和理解。类组件需要继承React.Component,并创建render函数返回一个React元素,代码冗余。
- 性能方面:函数组件的性能比类组件的性能要高,类组件需要实例化,函数组件直接执行函数取返回结果即可。
- 功能方面:旧版本函数组件没状态,限制了某些复杂组件的实现,16.8引入了hooks解决了这个问题。
- 副作用:函数组件鼓励函数式编程风格,强调无副作用和数据不可变性。
受控组件和非受控组件区别:
- 一般用受控组件来处理表单数据,表单元素的值由组件的状态(state)来驱动,并通过事件处理程序(event handlers)进行更新。但也能用非受控组件搭配ref来处理表单数据。
- 状态管理:受控组件使用组件的状态来管理和控制表单元素的值,而非受控组件直接使用DOM节点来管理表单元素的值。
react高阶组件
- 是一个函数,接受一个组件作为参数,并返回一个新的包装组件。
- 作用 :代码复用、日志记录、功能增强和修饰、数据校验、错误处理。
劫持 React 组件,通过使用高阶组件来实现,方法有多种:
- 劫持props,高阶组件可以通过 props 传递数据或回调给原组件,也可以修改或过滤原组件的 props。
- 劫持state,高阶组件可以管理原组件的状态,比如提供一个统一的状态管理器,或者使用 Redux 等库来连接状态。
- 劫持 render:高阶组件可以控制原组件的渲染过程,比如添加额外的元素,修改样式,条件渲染等。
- 劫持生命周期:高阶组件可以在原组件的生命周期方法中执行一些操作,比如发送请求,设置定时器,添加事件监听等。
Pure Components
- 可以通过继承React.PureComponent类或使用React.memo函数进行创建。
- PureComponent类自动实现了shouldComponentUpdate方法,利用浅比较props和state来确定是否需要重新渲染组件。
- React.memo通过对props进行浅比较来确定是否重新渲染,还可以接收一个自定义的比较函数作为第二个参数,以便进行更复杂的比较逻辑。
展示组件和容器组件
- 展示组件关注于呈现和渲染UI,接收props并根据props渲染UI,通常是无状态的函数式组件
- 容器组件关注于管理数据和业务逻辑,处理数据获取、状态管理、副作用等逻辑,通常是有状态的类组件,可包含一个或多个展示组件。
- 通过将展示组件和容器组件分离,可以实现代码的分离和职责的清晰划分,提高代码的可维护性和可复用性。容器组件处理数据和逻辑,而展示组件专注于UI的呈现,使代码更易于理解和测试。
状态
什么是 React 的状态?
- 状态(state)是组件内部用于存储和管理数据的对象,类组件通过this.setState管理,函数组件使用useState或useReducer管理。
- React将状态看做一个自动机,通过状态的更新,可以重新渲染组件,并反映出新的数据状态。
- React建议减少使用状态:无法通过props计算得到或者随时间变化的数据才作为state。多个组件的state相同时提升到父组件。
什么是 React 的状态提升?
- 在React中,任何可变数据应该只有一个唯一的数据源。
- state应该首先添加到需要渲染数组的组件 中,当其他组件也需要渲染这个state,那么将该state提升到最近的共同父组件中。
- 作用,保证数据一致性、减少重复的逻辑、减小bug排查范围。
state和props的区别:
- 来源:state右当前组件定义,而props是由父组件传入。
- 更新:state可变,props在当前组件不可变,只能由父组件更改。
- 数据:state是组件内部用于存储和管理数据的对象,改变时可以重新触发组件更新,props除数据外,还可以是回调函数,组件(children)和路由(history)的传递。
如何创建动态的状态名称?
使用ES6的计算属性名:
const dynamicStateName = 'dynamicState';// 动态状态名称
this.state = { [dynamicStateName]: 'Initial value' };// 创建具有动态状态名称的初始状态
setState 支持哪些用法?
- 使用对象作为参数:最常见的用法。
- 使用函数作为参数:需要基于先前的状态进行更新时选择,使用 prevState 和 props 计算新的状态。
- 使用回调函数:需要在状态更新后执行某些操作时选择。
如何优化 setState,减少不必要更新?
- 使用函数作为参数,基于先前的状态进行更新计算,对比新旧状态值,若相同则返回null不渲染,否则返回新状态渲染。
- 将涉及太多状态的组件拆分成更小的子组件,减少 setState 的影响范围。
- 避免使用 Object 作为 State 值,并使用shouldComponentUpdate、PureComponent、memo 等方法对state或props进行浅层比较。
- 必要使用 Object 作为 State 值时,避免嵌套过多层级。如果 State 值是一个复杂的 Object,那么每次更新时都需要创建一个新的 Object,并且复制所有的属性和子属性,这会增加内存和计算的开销。
- 使用useCallback和useMemo进行函数和值的缓存。
setState 和 replaceState 的区别是?
- setState会合并之前的状态,而replaceState会丢弃之前的状态,使用新的状态来代替。
- replaceState(value) = setState(null,()=>{setState(value)})。
- replaceState在React V16已经被废弃,V17已经移除。
#属性
什么是 React 的属性?
- 属性是组件的入参,也就是从父组件向子组件传递数据的一种机制,用于定义组件的配置和行为。
- props除数据外,还可以是回调函数,组件(children)和路由(history)的传递。
- 只读
为什么不能直接修改属性?
- 单向数据流:React遵循单向数据流的设计模式,数据从父组件流向子组件。通过将属性设置为只读,确保了数据的单向流动,使数据的流向更加清晰和可控。
- 可预测性和稳定性:属性是只读的,可以保证父组件的数据只会被自己修改,不担心会被子组件修改,这种可预测性有助于降低代码出错的可能性,并增强组件的稳定性。
- 性能优化:React使用虚拟DOM和高效的更新机制来优化组件的渲染和更新过程。属性设置为只读让React可以更好地跟踪组件的变化,从而更准确地确定是否需要进行重新渲染。
通过属性传递组件本身的方法有哪些?
- 直接传递 JSX 创建好的元素:把要传递的组件作为 JSX 元素写在属性值里,然后在接收的组件里用 {this.props.xxx} 来渲染。这种方法的优点是直观和灵活,缺点是可能造成不必要的重复渲染。
- 传递组件本身:把要传递的组件作为一个变量或常量,然后把它赋值给属性。这种方法的优点是可以避免重复渲染,缺点是需要额外定义一个变量或常量。
- 传递返回 JSX 创建好的元素的函数:把要传递的组件封装成一个函数,然后把函数赋值给属性。这种方法的优点是可以实现动态渲染和传递参数,缺点是可能造成性能损失。
使用 key 属性有哪些注意事项?
- 唯一性:key用于区分同一列表中的元素,在更新列表时进行比较和重渲染。
- 稳定性:不要使用索引或随机值,因为列表中的元素可能会被重新排序、添加或删除导致索引的改变,重渲染时若随机值改变那么会导致不稳定的渲染结果和不必要的组件重新创建。
- 不传递性:key不会传递给子组件,若需要可额外定义一个变量存储key属性的值。
- 作用域:同一列表中的key互相比较才有意义,不同列表间应使用不同的属性作为key。
- 无序性: React不保证列表元素的渲染顺序和key的顺序一致。在渲染列表时,React可能会重新排序元素,以提高渲染效率。因此,不要依赖key的顺序来进行操作或编写业务逻辑。
如何在 React 中进行静态类型检查?
- PropTypes:React自带了一个名为PropTypes的库,可以用于定义组件的属性类型。
- TypeScript:TypeScript是一种静态类型检查的超集
- Flow: Flow是Facebook开源的一个静态类型检查工具,
// @flow
是一种用来启用 flow 类型检查的注释 - ESLint + TypeScript 或 Flow:可以使用ESLint与TypeScript或Flow集成,通过ESLint插件进行静态类型检查。
如何限制某个属性是必须的?
- 使用 PropTypes,
import PropTypes from 'prop-types'; MyComponent.propTypes = { requiredProp: PropTypes.string.isRequired};
- 使用 TypeScript,没有 ? 表示必填
type Props = { requiredProp: string; };
如何设置属性的默认值?
- 函数组件:使用默认参数语法来设置属性的默认值
function MyComponent({ propWithDefault = 'defaultValue' }) {}
- 类组件:使用 defaultProps 静态属性来设置属性的默认值
MyComponent.defaultProps = { propWithDefault: 'defaultValue'};
React 支持 HTML 属性,但有区别;React 支持自定义属性
- 命名规范:html小写字母,React是小驼峰,如className、htmlFor、 tabIndex
- 状态:html的属性值是静态的,而react的属性值可以静态也可以动态,值可以是字符串、数字、布尔值、函数、对象等。
- 自定义属性:html的自定义属性不会影响元素的行为,react的data-前缀的自定义属性会视为数据属性并进行处理。
通信
React 父子组件通信有哪些方法
- 属性传递与回调函数(Props):父组件可以通过属性(props)将数据或回调函数传递给子组件。子组件通过读取父组件传递的属性来获取数据或调用回调函数进行交互。状态提升也是使用属性传递
- 上下文(Context):父组件可以通过创建上下文对象并在其子组件树上共享它,从而在多层级的组件之间进行通信。子组件可以通过订阅上下文来获取共享的数据。
- Refs:父组件可以通过创建 ref 并将其传递给子组件,从而在父组件中引用子组件的实例。这允许父组件直接操作子组件的方法或访问子组件的属性。
- 发布订阅模式:父组件作为事件的发布者,通过事件中心(Event Bus)发布事件,子组件作为事件的订阅者,通过订阅事件来接收通知和数据。
- 全局状态管理:使用第三方状态管理库(如 Redux、MobX)来管理应用程序的全局状态,父子组件通过访问共享的状态来进行通信。
为什么react是单向数据流
- 为了简化数据的流动和降低组件之间的耦合度,提高代码的可维护性和可预测性。
- 避免数据冲突和不一致:单向数据流可以防止数据在不同的组件之间产生冲突和不一致,因为数据的更新只能由父组件进行,而子组件只能接收和展示数据,而不能修改数据。
- 性能优化:单向数据流使得数据变化更加可控和可预测,React 可以更精确地进行组件的 diff 和更新操作,减少了不必要的重渲染和计算开销。
什么是 Context ?
- Context是 React 提供的一种用于在组件树中共享数据的机制。它允许您在组件之间传递数据,而不需要手动通过 props 层层传递。
- Context 包括两个主要的组件:Context.Provider 和 Context.Consumer。
- 过度使用或滥用 Context 可能会导致组件之间的耦合性增加,使代码难以维护。在真正需要在多个组件之间共享数据时才使用 Context。
什么是 ContextType ?
- ContextType 用于订阅单一的 context,而无需用Context.Consumer。
- 写法
static contextType = MyContext
或ChildComponent.contextType = MyContext
。前提是const MyContext = React.createContext();
- 使用 ContextType 需要确保组件位于 Context.Provider 的子组件树中。否则为默认值或undefined。
如何优化 Context ?
- 使用 React.memo 或 React.PureComponent:适用于只依赖于 Context 中的某些特定数据,并且不需要订阅整个 Context 的变化的情况。
- 拆分:将复杂的Context对象拆分,让组件只订阅需要用到的上下文。
- 记忆化:使用 useMemo 和 useCallback来分别缓存计算结果和回调函数。
- 采用props代替少部分组件才用到的状态
- 避免在上层组件频繁更新 Context。
什么是 Ref 转发?
- Ref 转发可以将 ref 传递到子组件,由React.forwardRef 实现。
- 转发表单组件的 ref 到 DOM 节点,便于访问 DOM 节点,来管理焦点、选中或动画。
- 在高阶组件内,转发外层组件的 ref 到 被包裹的组件。
渲染
React 返回空对象有哪些方法?
- {}/ false / true / null / undefined 将被忽略,不被渲染。
- 如果需要在返回空对象的同时保留一些子元素或属性,可以使用 React.Fragment 或 <> 包裹它们,而不会引入多余的 DOM 节点。
如何优化不必要的渲染?
- 优化state:避免多层嵌套,状态提升和隔离,合并状态更新
- 优化props:避免多层嵌套,避免使用对象字面量或者匿名函数作为props,每次render会创建新对象或者新函数导致子组件重新渲染。
- 先比较再更新:类组件使用PureComponent浅比较props和state,或者使用shouldComponentUpdate函数手动比较props和state。函数式组件使用React.memo,不传第二个参数时浅比较props,传第二个参数时手动比较props。返回false渲染,返回true不渲染。
- 记忆化:使用useMemo的第二参数来传入依赖数组存储回调函数的计算值,使用useCallback第二参数存储回调函数本身。若不传第二参数那么就失去了效果,如果第二参数为空数组就会在组件整个生命周期中保持不变。
- 减少组件嵌套:合理地提取子组件,并且使用React.Fragement或其缩写<>来减少不必要的根组件。
React 如何渲染 HTML ,有什么风险?
return <div dangerouslySetInnerHTML={{__html: '<b>1</b>'}} />
代码直接设置 HTML 存在风险,很容易无意中使用户暴露于跨站脚本(XSS)攻击。
React 为什么要引入基于 Fiber 协调器的异步渲染?
- 早期React版本采用阻塞渲染的方式,一旦渲染开始就要完成渲染才能停止。防抖牺牲响应即时性,节流降低更新频率,这些都无法提供最佳体验。
- Fiber引入了一种可中断和恢复的渲染过程:可拆分渲染工作,根据优先级渲染,渐进式渲染。
React Fiber 异步渲染分为哪几个阶段,对应生命周期是什么?
- Reconciliation(协调)阶段:React Fiber 会执行协调工作,计算出组件的更新、新增和删除等操作,并构建 Fiber 树。对应生命周期有:componentWillMount,componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate。
- render阶段:React Fiber 会根据计算出的更新操作,递归遍历 Fiber 树,并执行组件的 render 方法,生成组件的虚拟 DOM(Virtual DOM)表示。对应生命周期是:render
- commit阶段:React Fiber 将对比新旧虚拟 DOM,将变化的部分应用到实际的 DOM 中,更新视图。对应生命周期有:componentDidMount,componentDidUpdate。
生命周期
React 组件有哪些生命周期方法?可分为哪些阶段?
-
挂载:constructor,getDerivedStateFromProps,render,componentDidMount。
-
更新:getDerivedStateFromProps,shouldComponentUpdate,getSnapshotBeforeUpdate,componentDidUpdate
-
卸载:componentWillUnmount
-
constructor:构造函数,用于初始化 state 或绑定事件处理函数
-
getDerivedStateFromProps:静态方法,用于根据 props 计算 state,在 props 和 state 不一致时才返回一个新的 state 对象,否则返回 null。
-
render:渲染方法,用于返回组件的 JSX 结构
-
componentDidMount:挂载完成后执行,用于发送网络请求或添加事件监听等
-
shouldComponentUpdate:返回一个布尔值,用于判断是否需要更新组件
-
getSnapshotBeforeUpdate:在更新前获取一个快照值,用于传递给 componentDidUpdate
-
componentDidUpdate:更新完成后执行,用于根据 props 或 state 的变化进行操作。当且仅当props和state条件满足时,通过网络请求数据。
-
componentWillUnmount:卸载前执行,用于清除定时器或事件监听等
useEffect useLayoutEffect 与生命周期的对应关系是?
- useEffect:执行时机是浏览器完成渲染之后,是一个异步宏任务,相当于 componentDidMount 和 componentDidUpdate。返回清楚函数时相当于componentWillUnmout。
- useLayoutEffect:执行时机是浏览器把内容真正渲染到界面之前,是一个同步任务,相当于 componentDidMount 和 getSnapshotBeforeUpdate。
- useEffect 不会阻塞渲染,适用于大多数正常情况,而 useLayoutEffect会阻塞渲染, 适用于涉及到修改 DOM、动画等场景。
在 constructor 中使用 super 的意义是?
在 constructor 中使用 super 的意义是调用父类的构造函数,从而继承父类的属性和方法¹²。如果子类没有自己的构造函数,或者没有在构造函数中调用 super,就会报错,因为子类没有自己的 this 对象,而是依赖于父类的 this 对象。super 既可以作为函数使用,也可以作为对象使用,但是要注意区分它们的用法和作用域。
对比 React Hook 与生命周期
- constructor:函数组件不需要构造函数,可以通过 useState 来初始化 state。
- getDerivedStateFromProps:可以使用 useState 里面的 update 函数来根据 props 更新 state。
- shouldComponentUpdate:可以使用 useMemo 来优化渲染性能,避免不必要的重渲染。
- render:函数组件本身就相当于 render 函数。
- componentDidMount:可以使用 useEffect 并传入一个空数组作为第二个参数,来实现只在组件挂载时执行一次的副作用。
- componentDidUpdate:可以使用 useEffect 并传入一个依赖数组作为第二个参数,来实现根据依赖变化而执行的副作用。
- componentWillUnmount:可以使用 useEffect 并在返回一个函数,来实现组件卸载时执行的清理操作。
- componentDidCatch 和 getDerivedStateFromError:目前没有对应的 Hook,可以使用错误边界组件来捕获子组件树中的错误。
- getSnapshotBeforeUpdate:类组件获取快照,目前没有对应的hook。
事件处理
React 和 DOM 事件区别
- 事件命名:React是小驼峰,dom是纯小写。
- 事件绑定:React是接收参数,dom是采用onclick或addEventListener接收字符串字符串
- 阻止默认行为:React是preventDefault,dom是return false
- 事件委托:16及之前版本是document.addEventListener(),17版本之后是reactNode。addEventListener()。dom是自行添加事件委托
什么是 React 合成事件?
- 跨浏览器兼容性:React 合成事件封装了底层的原生浏览器事件,使开发者不需要考虑不同浏览器之间的事件差异,提供了一致的事件处理机制。
- 事件池:为了性能优化,函数执行完毕后会将事件对象重置并放入事件池中,以便在后续事件中重复使用。这种复用机制减少了内存消耗和垃圾回收的开销,React 17 版本及之后的版本不再使用事件池机制,引入持久化事件处理函数思想。解决“异步代码中访问已经被清空的合成事件对象”的情况。
- 合成事件委托:react16版本委托到document上,react17之后委托到reactNode,也就是react节点上。
- 事件执行顺序:原生事件先执行,合成事件后执行。
如何解决 类组件 中,事件处理的 this 为 undefined 的问题?
- 使用箭头函数:声明函数的时候使用,或者在调用函数的地方使用。
- 使用bind方法绑定this,constructor中使用或者在调用函数的地方使用。
如何传参给事件处理函数?各有什么优缺点?
- 箭头函数:语法简洁,但每次渲染都会创建一个新的函数实例,会造成子组件的重新渲染。
- bind方法:语法比箭头函数复杂一点,但不会对性能造成影响。
如何阻止事件处理函数被频繁调用?
- 防抖:延迟时间内多次触发的事件只会在延迟时间过去之后执行最后一个。发请求,移动,拖拽
- 节流:延迟时间内只会执行一次事件,延迟时间过后才依次执行下一个。页面滚动,输入框输入。
样式管理
如何在 React 中使用样式?各有什么优缺点?
- 内联样式:样式与组件逻辑更紧密地结合,但样式较为复杂时,可能会导致代码冗长,并且难以维护和重用。
- CSS 模块化:更好地组织和复用样式,同时提供了更好的样式隔离性。需要额外的配置和构建工具。
- CSS-in-JS 库:styled-components、Emotion、JSS 。更灵活地定义样式,并提供了一些额外的功能,但需要引入额外的库。
如何按条件加载样式?
- 条件渲染:三元运算符结合style或者className来使用
- 动态引入样式文件。&&运算符
如何合并多个内联样式?
- 使用扩展运算符…
- 使用Object.assign()
- 遍历对象赋值
错误边界
什么是React错误边界?
- why?部分组件的错误不应该导致整个应用的奔溃,所以React16.8引入了错误边界,保持应用的稳定性和可靠性。
- 错误边界是一种React组件,可以捕获并打印子组件的错误,同时展示降级UI来代替发生错误的子组件。
- 错误边界仅可以捕获其子组件内部发生的错误,并不会捕获错误边界自身的错误,或者异步代码(例如setTimeout或Promise内部的错误)。
- 生命周期:componentDidCatch 打印错误信息,getDerivedStateFromError方法在componentDidCatch之前调用,它用于派生更新的状态,从而渲染备用UI。
错误边界可以捕获什么错误?不能捕获什么错误?
- 可捕获:子组件渲染错误、生命周期方法、整个组件树的构造函数
- 不可捕获:错误边界自身错误、事件处理函数中的错误(使用 try/catch 捕获)、异步代码(例如setTimeout或Promise内部的错误)、服务端渲染
如何处理事件处理函数内部错误?
- 对于大多数事件处理函数中的错误,使用js的try/catch语句即可
- 可以用 window.addEventListener(‘error’, () => {}) 捕获大部分同步、定时器、Generator 异步错误
- 用 window.addEventListener(‘unhandledrejection’) 捕获 Promise 及其语法糖 async / await 错误
React 如何处理未捕获错误,为什么这样处理?
- 如何:React16开始,未被错误边界捕获的错误会冒泡到最外层组件,并触发浏览器的默认行为,通常表现为应用崩溃或显示错误信息。
- 为什么:
(1)React认为保留错误UI比移除它更糟糕,例如在即时通信应用中展示错误的消息,或者在支付类应用中展示错误金额比白屏更糟糕。
(2)白屏更有利于开发者发现已存在但未曾留意的bug,手动去增加错误边界,完善用户体验。
HOOKS
什么是 React Hook? 解决了哪些问题?
- React Hooks(React钩子)是React 16.8版本引入的一组函数,用于在函数式组件中添加和使用状态(state)、副作用(side effects)等特性,使得函数式组件可以具备类组件的能力。
- 类组件状态逻辑和 UI 耦合:Hooks 可以让你在不影响组件树结构的情况下,抽离和重用状态逻辑。
- 单个生命周期包含多种逻辑:Hooks 将组件中相互关联的部分拆分成更小的函数,而并非按照生命周期划分。
- this 指向问题:Hooks 无需使用this,不需要进行手动绑定this的操作,也避免this指向出错
尽可能地列举Hooks
- useState:用于在函数组件中引入状态,返回一个状态变量和一个更新状态的函数
- useEffect:用于在函数组件中执行副作用,接受一个函数作为参数,该函数会在组件渲染后执行,可以返回一个清理函数
- useContext:用于在函数组件中访问 Context 对象,接受一个 Context 对象作为参数,返回该 Context 的当前值
- useMemo:用于在函数组件中缓存计算结果,避免重复计算,接受一个创建函数和一个依赖数组作为参数,返回该创建函数的最新返回值
- useCallback:用于在函数组件中缓存函数,避免不必要的重新渲染,接受一个内联回调和一个依赖数组作为参数,返回该回调的 memoized 版本
- useRef:用于在函数组件中创建和访问 ref 对象,接受一个初始值作为参数,返回一个可变的 ref 对象,其 current 属性指向初始值或最新的值
- useLayoutEffect:用于在函数组件中读取 DOM 布局并同步触发重渲染,与 useEffect 类似,但会在 DOM 更新后同步执行
- useReducer:用于在函数组件中管理复杂的状态逻辑,接受一个 reducer 函数和一个初始状态作为参数,返回一个状态变量和一个 dispatch 函数
- useImperativeHandle:用于在函数组件中自定义暴露给父组件的 ref 值,接受一个 ref 对象和一个创建函数作为参数,返回该创建函数的返回值,并赋给 ref 对象的 current 属性
- useDebugValue:用于在函数组件中显示自定义的 Hook 标签,接受一个标签值或者一个生成标签值的函数作为参数,在 React 开发者工具中显示
- useId:用于在函数组件中生成一个唯一的标识符,可以用于关联 label 和 input 等元素,或者生成 DOM 元素的 id 属性
- useSyncExternalStore:用于在函数组件中订阅和读取外部数据源,例如 Redux、MobX 等,它可以与 React 的并发渲染特性兼容,例如选择性 hydration 和 time slicing
- useDeferredValue:用于在函数组件中延迟更新某个值,直到浏览器有空闲时间,它可以避免因为频繁的状态更新而导致的性能问题
使用 Hook 需要遵循的规则是?
- 只在顶层使用 Hook:不要在循环、条件判断或嵌套函数中使用 Hook。确保 Hook 在组件的最顶层调用,以确保每次渲染都以相同的顺序调用 Hook。
- 只在 React 函数组件中使用 Hook:只在 React 函数组件或自定义HOOK中使用HOOK,不要在普通的 js 函数或类组件中使用 Hook。
- 自定义 Hook 名称必须以 “use” 开头:自定义 Hook 的名称必须以 “use” 开头,这是为了让开发者知道它是一个 Hook,并且遵循 React 的规范。
- 安装校验Hook的工具
npm install eslint-plugin-react-hooks -D
useMemo 和 useCallback 的区别是?
- useMemo 的主要作用是缓存计算结果,并在依赖项发生变化时重新计算。
- useCallback 的主要作用是缓存函数引用,并在依赖项发生变化时返回新的函数引用。
- useCallback 返回的是函数,useMemo 返回的是值,也可以是函数。useMemo(() => fn, deps)
- useMemo 本质上就是将 useCallback 缓存的函数直接执行:useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useState 和 useReducer 的区别是?
- 用法不同:useState接收一个参数state返回的是包含状态值和更新状态的函数的数组[state, setState];而useReducer接收参数为:一个 reducer 函数和初始状态,返回当前状态和 dispatch 函数[state, dispatch]。
- 适用场景不同:useState适用于简单的状态管理;useReducer 适用于复杂的状态逻辑,如当状态之间有复杂的依赖关系或需要进行多种操作时。
- 同步异步:使用 useReducer 可以确保更新是同步的,而不是像 useState 那样是异步的。
useLayoutEffect 和 useEffect 的区别是?
- 执行时机:useLayoutEffect 组件渲染完成后,在浏览器绘制之前同步执行,会阻塞组件的渲染过程;useEffect 在组件渲染完成后异步执行,它不会阻塞组件的渲染过程。
- 适用场景:useLayoutEffect 适用于需要在 DOM 更新之前立即执行副作用操作的情况。比如需要测量 DOM 元素的尺寸、位置等信息,然后根据这些信息做出相应的布局调整。useEffect 适用于大多数情况,它不会阻塞组件的渲染过程。
forwardRef,createRef,useRef 和Refs的用法和区别是什么?
- forwardRef 是一个高阶函数,用于创建具有 ref 属性的组件,以便在父组件中获取对子组件实例的引用。
- createRef:用于在类组件中创建 ref 对象,返回一个特殊的对象,其 current 属性指向关联的元素或实例
- useRef:用于在函数组件中创建和访问 ref 对象,接受一个初始值作为参数,返回一个可变的 ref 对象,其 current 属性指向初始值或最新的值
- Refs 是 React 中的一个特殊属性:可通过上述三种方式创建。
React自定义Hooks 参考:https://www.nowcoder.com/discuss/514031564364247040
- 命名规范:自定义 Hook 必须以 “use” 开头,以便 React 能够正确识别它为一个 Hook。
- 逻辑抽象:将可复用的逻辑抽象出来,并封装在自定义 Hook 内部。这样,其他组件就可以使用该 Hook 来获得相同的功能。
- 不能渲染 JSX:自定义 Hook 是一个函数,不能包含任何 JSX 代码,它只能包含 JavaScript 逻辑。
是否有必要使用 Hook API 重写所有类组件?
- hook暂时不能覆盖类组件的全部场景:错误边界:getDerivedStateFromError和componentDidCatch,获取快照getSnapshotBeforeUpdate。
- 没有计划从React中移除class。
useState 返回更新 state 的函数是同步,还是异步的?
- useState 返回更新 state 的函数是异步的,也就是说,调用该函数后,state 的值不会立即改变,而是在下一次渲染时才会反映出来。这样做的原因是为了提高性能,避免不必要的渲染。如果你在同一个事件处理函数或 useEffect Hook 中多次调用 setCount 函数,React 会将它们合并为一个批量更新操作,只触发一次重新渲染。
- 解决方式:回调函数:
setCount((prevCount) =>{return prevCount + 1; });
、或者使用useEffect来监听count。
测试 jest相关,先跳过
原理
什么是 Virtual DOM ?
- 用一个js对象来表示整个整个DOM结构,当状态发生改变时会先比较前后两个js对象,得到最小操作序列再应用到真实的DOM上。
- 频繁操作真实DOM会引起浏览器的重绘和回流,导致浏览器的性能变差,虚拟DOM是在内存中进行操作,不涉及真实的页面渲染和布局,所以它的计算成本相对较低。
- 虚拟DOM是一种编程概念,它用一个理想的或“虚拟的” UI 表示形式保存在内存中,并通过一个库(如 ReactDOM)与真实DOM 同步。这个过程叫做 reconciliation(协调)
- Virtual DOM 的目的是为了计算出最少的 DOM 操作,从而提高 UI 的重新渲染性能。它不是与真实的 DOM 竞争,也不一定比真实的 DOM 快。Virtual DOM 提供了一种机制,让开发者不用手动操作 DOM,而是可以写出更可预测的代码。
虚拟DOM的解释过程
- 构建虚拟DOM:开发的组件会被解析器解析成为一颗虚拟DOM树,本质是一个js对象。
- 初次渲染:将虚拟DOM树转化为真实DOM树并挂载到页面上。
- 更新虚拟DOM:当页面需要更新时,修改虚拟DOM树,即修改纯js对象,不会直接修改真实DOM。
- 比较虚拟DOM树:比较修改前后的虚拟DOM,计算出最少的 DOM 操作。
- 更新真实dOM:根据计算出最小的DOM操作,更新对应的真实DOM。
React Diff。详细请看https://blog.csdn.net/web2022050903/article/details/125198132
- Tree Diff:递归对比两棵树,同级比较。根节点为不同元素时,卸载旧树生成新树;元素相同时保留节点只更新有变化的属性。处理完根节点再递归比较。
- Component Diff:组件类型相同:组件的更新流程;组件类型不相同,删除和创建。
- Element Diff:递归DOM节点的子元素。key唯一标识、相同类型的React元素,保留Dom节点,仅对比改变的属性。
VUE Diff。
- 对于同层级节点首先对比新旧节点的头尾,头与头、尾与尾分别进行对比,寻找未移动的节点。
- 新旧节点头尾对比完后,然后进行头与尾、尾与头的交叉对比,这一步的目的是寻找可复用的节点。
- 在交叉对比结束后,因为有可能还有可复用的节点,所以创建一个老节点 keyToIndex 的哈希表 map 记录 key,然后继续遍历新节点索引通过 key 查找可以复用的旧的节点。
- 节点遍历完成后,通过新老索引,移除多余旧节点或者增加新节点。
什么是 React Concurrent 模式?(并发) 详细https://www.nowcoder.com/discuss/514332209768095744
- 传统的React渲染是同步的,阻塞的,当某一组件的渲染耗时较长时会导致用户界面卡顿的情况。Concurrent模式通过将任务拆分成可中断的小单元,允许React中断低优先级的任务,去执行高优先级的任务。
- 特点:可中断渲染、按优先级渲染、时间切片和并发渲染(单个时间片内多个状态更新合并成一个批量更新)
- 常用API:Suspense(fallback)、SuspenseList(编排显示组件的顺序)、startTransition(延迟更新低优先级)、useTransition(
[isPending, startTransition] = useTransition()
)、useDeferredValue(延迟处理的值)
React 如何定义任务的优先级?
- Lane 模型:核心思想是使用一个 31 位的二进制数来表示一个任务的优先级,每一位代表一个 Lane(车道)。不同的 Lane 有不同的优先级,比如用户交互相关的 Lane 优先级最高,而后台数据获取相关的 Lane 优先级最低
- Scheduler调度器:优先级常量:ImmediatePriority、UserBlockingPriority、NormalPriority、LowPriority、IdlePriority等
Redux
什么是Redux?核心原则:单一数据源、状态只读、reducer纯函数
- 基于 Flux 架构思想的状态管理库。
- 单一数据源,有且仅有一个store来存储状态。
- 状态只读:当状态改变时,reducer会根据原有的状态计算并返回一个全新的状态,而不是直接修改旧状态。
- reducer是一个纯函数,接收当前的状态和action作为输入,根据action的类型来处理状态的更新,并返回一个新的状态。
- 单向数据流:View通过action分发给reducer更新store中的状态,并且从store中获取状态来渲染。
- action是一个js对象,包含type字段用于标识操作类型,也可以包含可选字段来传递数据。
- 中间件:在action和reducer之间处理自定义逻辑,如异步操作、日志记录、错误处理等。
Flux和Redux的区别:
- Flux 是一种架构模式,而 Redux 是基于 Flux 架构思想的状态管理库。
- Redux 使用一个store管理整个应用的状态,仅包含状态,而 Flux 中的数据存储是多个 Stores,每个 Store 包含部分的状态和逻辑。
- Redux 提供了中间件来扩展处理 action 的能力,例如处理异步操作、记录日志等。而在 Flux 中,这些功能需要自己手动实现。
React Context 和 Redux 的区别
- 所属:React Context 是 React 的内置特性,Redux是基于Flux 架构思想的状态管理库。
- 数据源:React Context 可以创建多个数据源,而 Redux 只能创建一个数据仓库。
- 功能:两者都可以在组件之间共享和管理状态,避免组件之间逐层传递 props的情况。Redux 提供了一些高级的功能,比如中间件、时间旅行、热重载等。
React 访问 Redux Store 的方法有哪些?
- connect:connect 函数接收两个参数,第一个参数是一个函数,用于将 Store 中的状态映射到组件的 props 上,第二个参数是组件本身。
connect(mapStateToProps)(MyComponent)
connect(mapDispatchToProps)(MyComponent)
- useSelector 和 useDispatch:
const someValue = useSelector(state => state.someValue);
或const dispatch = useDispatch();
- useStore:访问 Store 的一些方法,如:getState, dispatch, subscribe。
const store = useStore();store.getState();
- 导出store。
Redux 中异步请求数据时发送多 Action 方法有哪些?https://www.nowcoder.com/discuss/514403534230581248
- mapDispatchToProps:取出dispatch方法分发多个action
- redux-thunk:在 Redux Store 中分发一个函数,而不是一个action对象。这个函数可以接收 dispatch 和 getState 作为参数。
- redux-saga:分发一个特殊的 action,Redux Saga 捕获action并执行一个生成器函数。
- Redux Toolkit:createAsyncThunk 函数。