个人总结八股文的背诵方案
对比 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的社区虽然也很活跃,但相对规模较小。
对比 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,可以使用错误边界组件来捕获子组件树中的错误。