一、fiber架构
fiber是一种可用于描述组件树的数据结构,他代表一种可中断,可恢复的异步渲染任务,fiber架构主要是为了解决 react在渲染大型项目时的性能问题,并支持异步渲染,优先级调度的高级特性
- Fiber节点的含义:
- Fiber表示React中的一个组件或DOM节点。
- 每个Fiber节点对应一个React元素(如组件、DOM节点等),并包含了该元素的相关信息(如类型、状态、子节点等)。
- Fiber架构的核心特性:
- 异步渲染:将渲染任务分解为多个小任务,支持任务的暂停、中断和恢复。
- 优先级调度:根据任务的优先级动态调整渲染顺序,确保高优先级的任务优先执行。
- 增量渲染:将渲染任务分成多个小片段,逐步完成。
- 错误处理:支持错误边界(Error Boundaries),更好地捕获和处理组件树中的错误。
- Fiber的双缓存机制:
- React使用双缓存机制来管理Fiber树,包括当前树(Current Tree)和工作树(WorkInProgress Tree)。
- 当前树表示当前显示的UI对应的Fiber树,而工作树则是正在构建的新Fiber树。
- 当工作树构建完成后,React会将其切换为当前树,从而实现UI的更新。
- Fiber架构的工作流程:
- React根据JSX生成虚拟DOM树,并对每个虚拟DOM节点生成对应的Fiber节点,构建起Fiber树的层级结构。
- 执行初次渲染时,React从根节点开始递归遍历Fiber树,执行组件的渲染,将组件渲染到DOM上。
- 在更新阶段,React会根据新的React节点与当前Fiber树生成新的工作树,并通过深度优先遍历和Diff算法比较新旧节点,生成更新标记(删除、移动、插入等)。
- 构建完成后,工作树替换当前树完成DOM更新。
二、Fiber解决的问题
- JS引擎与页面渲染引擎的互斥问题:
由于JS引擎和页面渲染引擎两个线程是互相排斥的,当JS线程长时间占用主线程时,渲染层面的更新就会被迫等待,导致用户可能感受到卡顿。 - 解决方案:
-
任务拆分与优先级调度:
Fiber架构将渲染更新过程拆分成多个子任务,每次只做一小部分。通过给每个任务赋予不同的优先级,React能够确保高优先级的任务能够优先执行,并在必要时中断低优先级的任务。这种机制使得React能够更加灵活地应对复杂的更新场景。 -
合作式调度与requestIdleCallback:
React利用window.requestIdleCallback()方法,在浏览器的空闲时间段内执行低优先级的更新任务。这种方法允许React在不影响用户交互和页面渲染的情况下,完成后台和低优先级的更新工作。- 合作式调度:
React在完成一部分任务后,会主动将控制权交回给浏览器,让浏览器有时间进行页面的渲染和其他高优先级任务的执行。当浏览器有空闲时间时,React再继续执行之前未完成的任务。这种机制使得React和浏览器能够更加和谐地共存,共同为用户提供流畅的体验。
- 合作式调度:
一、setState是同步还是异步
- 在React的时间处理函数和生命周期方法中,setState是
异步的
- 为什么:在 React 的事件处理函数或生命周期方法中可能会频繁的调用setState,如果每次都立即触发就会造成大量不必要的渲染;所以会在 “事件处理完成” 或者 “声明周期方法执行完毕后” 统一更新渲染
- 解决异步问题 : 利用
setState的回调函数
- 在React的合成事件或生命周期方法之外(如:setTimeout、Promise),setState是
同步的
- 为什么:因为在这些上下文中,通常不会有大量setState调用需要合并,所以没必要设计为异步
二、React类组件生命周期
- -----挂载------:
- constructor(props):
- 构造函数,用于初始化组件的state和绑定事件处理器。
- 传入props作为参数。
- render():
- 返回组件的JSX结构。
- 无参数。
- componentDidMount()
-
组件被插入到DOM中后立即调用。
-
无参数。
-
通常用于执行网络请求或启动定时器
-
------更新------
- shouldComponentUpdate(nextProps, nextState)
- 返回一个布尔值,决定组件是否应该因为props或state的改变而重新渲染。
- 传入nextProps和nextState作为参数。
- 默认返回true,但通常用于性能优化。
- getSnapshotBeforeUpdate(prevProps, prevState)
- 在DOM更新之前调用,用于在可能即将发生的DOM变化前捕获一些信息(例如滚动位置)。
- 传入prevProps和prevState作为参数。
- 返回一个值,该值将作为第三个参数传递给componentDidUpdate。
- componentDidUpdate(prevProps, prevState, snapshot)
-
DOM更新后立即调用。
-
传入prevProps、prevState和snapshot作为参数。
-
用于在更新后执行DOM操作。
-
------卸载------
- componentWillUnmount()
- 无参数。
- 通常用于执行清理操作,如取消网络请求、清除定时器或解绑全局事件监听器。
三、函数组件模拟生命周期
useEffect:类似于类组件的componentDidMount、componentDidUpdate和componentWillUnmount生命周期方法的组合。
四、React常用的Hooks
- useState 包含两个元素 第一个为内部当前状态值,第二个是更新的函数
- useMemo
返回一个新的 “值”第一个参数是一个函数,第二个是一个依赖项;当依赖项发生变化的时候才会重新调用并返回一个新的 “值”缓存起来;(避免重复计算) - useCallback
返回一个新的 “函数”第一个参数是一个函数,第二个是一个依赖项;当依赖项发生变化的时候才会重新调用并返回一个新的 “函数”缓存起来;(避免重复渲染) - useReducer 接受一个reducer函数和初始状态,并返回一个包含当前状态和dispatch函数的数组。通过dispatch函数,可以触发状态的更新,然后重新渲染组件
- useEffect
第二个参数必须是一个数组
- 空数组 :只在组件挂载和卸载时运行一次;
- 什么都不传:每次组件渲染后都运行;
- 对象:要确保对象的引用在生命周期内保持不变,否则每次渲染后都运行;
- 空对象:每次渲染后都运行
- 字符串和数字:他们的值实际变化时才重新运行
useLayoutEffect:
useLayoutEffect 会在所有的 DOM 变更之后同步调用,即在浏览器绘制之前执行。这意味着你可以使用它来读取 DOM 布局并同步触发重渲染。然而,由于它会阻塞浏览器的绘制,因此应谨慎使用以避免性能问题。
useLayoutEffect和useEffect的区别:
一、 执行时机
- useEffect:
- 在组件渲染到屏幕之后异步执行。
- 不会阻塞浏览器的绘制和更新。
- useLayoutEffect:
- 在浏览器进行布局和绘制之前同步执行。
- 会在 DOM 更新后立即执行,但在浏览器绘制之前完成。
二、 性能影响
- useEffect:
- 由于是异步执行,不会阻塞页面的渲染,对用户交互的响应性影响较小。
- 如果副作用操作耗时较长,可能会在用户操作后有短暂的延迟才看到效果。
- useLayoutEffect:
- 由于是同步执行,如果操作耗时较长,会阻塞页面的渲染,可能导致页面卡顿,影响用户体验。
- 适用于需要在 DOM 更新后立即同步执行的操作,如读取 DOM 布局或同步更新样式等。
三、使用场景
-
useEffect:
- 适用于大多数常见的副作用处理场景,如数据获取、订阅事件、手动修改 DOM(这些操作不会直接影响页面布局和视觉呈现)。
- 也用于清理定时器、事件监听器等资源。
-
useLayoutEffect:
- 适用于需要在 DOM 更新后立即同步更新布局或样式的场景,如动画、测量元素尺寸等。
- 由于它在浏览器绘制之前执行,可以避免由于异步的
useEffect可能导致的闪烁现象。
-
useRef useRef保存的数据变化不会触发UI的渲染,如果想在React绑定或者解绑的时候运行代码就需要 使用ref回调实现
-
create.ref 和 useRef的区别
- createRef
createRef是一个 React API,通常在类组件中使用来创建引用(refs)。- 每次组件重新渲染时,
createRef都会返回一个新的引用对象。 - 主要用来获取当前组件挂载到 DOM 树中的实际 DOM 节点。
- 使用
createRef,会在组件的构造函数中创建 ref,并将其附加到组件的实例上。
- useRef
- useRef是一个 React Hook,只能在函数组件中使用。
- useRef在整个组件的生命周期中返回一个稳定的引用对象,即使组件重新渲染也不会改变。
- 可以用来获取 DOM 节点,还可以用来存储任意值,这个值在组件的整个生命周期中保持不变,除非你手动改变它。
- 使用
useRef,你会在组件内部直接声明 ref。
- React.memo 和 useMemo的区别
- React.memo: 是一种高阶组件,避免在 props 没有改变的情况下重新渲染组件。如果组件的props没有变化,
React.memo就会复用上一次渲染的结果 - 区别:
useMemo是用来“记住”计算结果的,以避免在不必要的时候重新计算;而React.memo是用来“记住”组件渲染结果的,以避免不必要的组件渲染
五、自定义hooks
其实就是一个函数,但是要遵守 React强制的约定和原则
- 必须以 “use” 开头;
- 只能在函数最外层调用Hooks:不要在循环、条件或嵌套函数中调用;
- 只能在 React函数组件 或者 其他自定义Hooks中调用
六、state 和 props 区别是啥
- state是组件自己管理数据,可变;props是外部传入的参数,不可变;
- 没有state的是 “无状态组件”,有state的是 “有状态组件”;
- 多用props,少用 state(多写无状态组件:增加复用性,可读性和易维护性,提高性能)
七、为什么React中的props是只读的
- 预测性:可以确信在任何时候,组件接收到的 props 都是一致的
- 组件隔离:相同的输入(props)应该产生相同的输出,避免了一个组件意外更改了另一个组件的状态
- 单项数据流:确保从父组件到子组件的数据是不可逆的 不是双向绑定;
- 易于测试:不需要担心外部状态的更改;
- 性能优化:当一个组件的父组件重新渲染时,React 可以快速地检查 props 是否更改,以确定是否需要重新渲染此组件。
八、react虚拟DOM原理 、diff算法
- 模拟DOM树:react会用节点对象来模拟真实的DOM树结构,这个模拟的DOM就叫虚拟DOM;
- 比较新旧DOM原理 如下:
- 分层比较 diff 算法会分别递归比较两个虚拟 DOM 树的根节点,以及它们的子节点。会逐层比较每一对节点。
- 节点类型比较 当比较两个节点时,首先会检查它们的类型(例如,都是 div 元素,或者都是自定义组件)。如果节点类型不同,React 会销毁旧节点及其所有子节点,并创建新节点及其子节点。
- 同层节点比较 对于同层级的节点,它会根据 key 属性(如果有的话)来识别哪些节点是“稳定”的,即它们在新旧两个树中都是存在的,只是位置可能发生了变化。对于没有 key 的节点,React 会自己进行最佳猜测,但结果可能不是最优的。
- 列表组件优化
对于列表组件(例如,使用 map 函数渲染的多个元素),React会给每个列表项一个唯一的
key属性。这样,当列表发生变化时(例如,添加、删除或重新排序项),React 可以利用这些 key 就可以快速识别哪些项是稳定的,哪些项是新增的,哪些项是需要删除的。 - 属性更新 当比较两个相同类型的节点时,React 会比较它们的属性。对于改变了的属性,React 会更新相应的 DOM 属性(只会处理实际发生改变的属性不会更新全部)
- 子节点比较 如果子节点有 keys,React 会根据 keys 来识别应该更新、删除还是重新排序的元素。如果没有 keys,React 则按顺序进行比较(可能会造成性能损失)
九、react 函数组件 和 类组件的区别
- 定义方式: 函数组件是使用JavaScript函数定义的组件,它接收props作为参数并返回一个React元素。类组件则是使用ES6类定义的组件,它继承自React.Component,并在类中定义render方法以返回React元素。
- 性能:函数组件由于没有实例化和复杂的生命周期方法,因此通常比类组件具有更好的性能
- 状态管理:在函数组件中,状态管理通常通过使用React Hooks来实现。类组件中,状态是通过在类中定义this.state并在构造函数中初始化来管理的。
- 生命周期::类组件具有完整的生命周期方法,而函数组件则使用useEffect来模拟生命周期行为。
- 逻辑复用:函数组件更易于实现逻辑复用,因为可以将一些常用的逻辑封装成自定义Hook,类组件的逻辑复用通常需要通过高阶组件(HOC)或render props等模式来实现。
- 使用场景:对于简单的组件,函数组件通常更加简洁和易于理解。然而,对于需要处理复杂状态、生命周期或需要与其他库或框架集成的组件,类组件可能更加适合。
- 语法简洁性:函数组件的语法更为简洁,因为它们只是纯函数,在代码量上通常更为轻量。
- this关键字的使用:在类组件中,this关键字用于访问组件的状态和属性。函数组件中,没有实例化的概念,因此不能使用this关键字。如果需要访问状态或属性,通常是通过React Hooks(如useState和useContext)来实现的。
十、react 组件通信
父子 :父组件通过 props 将数据传递给子组件。这是 React 中最常用的组件通信方式,遵循单向数据流的原则,即数据从父组件流向子组件。
子父 : 父组件向子组件传递一个回调函数作为 props,子组件调用这个函数,并将数据作为参数传递。父组件就能接收到子组件传来的数据。
兄弟组件通信:兄弟组件之间无法直接通信,它们需要通过共同的父组件来传递数据
跨级 :创建一个
Context 对象并使用 Provider 组件来包裹子组件,就可以将数据传递给这些子组件。子组件可以使用 useContext 钩子来访问这些数据
其他 : redux
十一、受控组件 和 非受控组件
- 受控组件 可以随时从 React 的 state 中读取表单的值,数据由React组件的状态(state)管理,输入表单,React 的 state 就会立即更新。
优势:
- **数据集中管理**:所有的表单数据都存储在 React 的状态中,使得状态管理更为集中且可预测。
- **易于维护**:由于数据由组件状态管理,因此可以更方便地进行验证、格式化和其他处理。
- **更好的用户体验**:可以实时响应用户输入,提供即时的反馈。
- 适用场景:适用于大多数表单场景,特别是当表单交互复杂、需要实时验证或格式化输入时
- 非受控组件
不需要每次输入都记录下来,数据由DOM本身管理,需要表单的数据时,你可以通过
ref去直接询问表单元素的值
特点:
- 简化代码:在某些简单表单场景中,使用非受控组件可以减少代码量。
- 直接操作 DOM:通过 ref 属性获取 DOM 节点,并直接操作其值。
- 适用场景:适用于非常简单的表单场景,例如只需要在提交时验证数据的表单,或者没有复杂用户交互的表单。此外,在某些特定情况下,如文件上传输入框,非受控组件也是必要的选择。
十二、react合成事件、react为什么要有自己的合成事件,如何阻止不同时期的事件
- react合成事件
首先呢 React 的 合成事件 是基于浏览器的事件机制 自身实现了一套事件机制, 包括了
事件注册,事件的合成、事件冒泡、事件派发等;合成事件是 React 模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器;
- 当真实的 dom 触发事件的时候,会构造 React 合成事件对象,按照冒泡 或 捕获的路径去收集真正的事件处理函数,这个过程中会处理原生事件,然后当冒泡到 document 对象后,在处理 React 事件;
- React 是在 document 上监听所有支持的事件,当时事件发生 并 冒泡到 document 的时候,React 会将事件的内容封装并交给真正的处理函数运行;
- 合成事件采用了事件池,合成事件会被统一放进事件池中管理,这样可以减少对内存的开销。
- React 通过合成事件,模拟捕获 和 冒泡阶段的,从而达到不同浏览器的兼容。
- 为什么要有自己的合成事件
- 浏览器兼容性:将不同的平台事件抹平成统一的合成事件,确保react在不同的浏览器上有一致的行为
- 性能优化:react事件不会被立即释放,而是会存储在一个数组中,事件触发的时候直接从数组中取避免了频繁的垃圾回收
- 事件统一管理和事件机制:实现了事件的统一管理,所有的事件都挂载在 document上(17版本后事件委托挂载在容器上),然后通过事件代理机制处理
- 阻止不同时期的事件
- 合成事件的冒泡:e.stopPropagation()
- 合成事件和最外层document上的冒泡:e.nativeEvent.stopImmediatePropagation()
- 合成事件与除了最外层document之外原生事件的冒泡:通过e.target判断是否等于该元素
十三、优化Render项目的手段
- 按功能拆分组件:将大组件拆分为多个小组件,每个组件负责单一功能,然后在父组件中整和这些组件,提高代码的可读性和可维护性。
- 创建自定义Hooks:利用React 16.8中的Hooks特性,创建可复用的自定义Hooks,简化组件逻辑,提高代码复用率。
- 虚拟滚动:对于长列表或大数据量的渲染,可以使用虚拟滚动技术,只渲染当前可见的部分,提高渲染性能。
- 优化动画和过渡效果:避免使用复杂的动画和过渡效果,减少CPU和GPU的消耗。如果必须使用,可以优化动画的帧率和持续时间。
- 合并请求:将多个网络请求合并为一个,减少HTTP请求的数量,降低网络延迟。
- 使用缓存:对于不经常变化的数据,可以使用缓存策略,减少重复请求的次数。React 16.8中的Hooks可以配合自定义Hooks来实现数据缓存的功能。
- 代码分割:
- 配置Webpack:在Webpack配置文件中使用
splitChunks插件来配置代码分割。 - 动态加载:使用动态
import()语法来按需加载代码块。 - 处理路由:在React Router中配置路由时,使用
lazy和Suspense来实现代码分割和懒加载。 - 压缩资源:对图片、CSS和JavaScript文件进行压缩,减少文件大小,提高加载速度。
- 高阶组件:高阶组件是一个函数,这个函数接收一个组件作为参数,并返回一个新的组件
- React.memo
- shouldComponentUpdate 和 PureComponent
- shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 就行,shouldComponentUpdate(nextProps, nextState),其中 nextProps 是即将传入的新的 props,nextState 是即将传入的新的 state。
- 引入 why did you render 包来捕获不必要的更新

十四、React中 state 和 props 有什么区别
- props是父组件传递过来的数据,具有不可变性;
- state在组件中创建的,在constructor中初始化,可以修改;
- props为什么是只读的
- 单项数据流:如果能够修改props会打破单项数据流,让数据流动变得难以预测;
- 维护组件封装状态:让组件的状态管理封装起来并且可以控制;
- 解决副作用和BUG:规定props为只读可以帮助开发者编写出更清晰且易于维护的代码。
十四、React类组件为什么修改状态用 setState 而不是 this.state.xxx
- React 需要知道何时更新和重新渲染界面。
- 我们改变的状态可能会影响其他界面或组件,React 要确保所有东西保持同步。
setState由 React 安排和批处理,这可以提高应用性能,因为它不会每次改变都重新渲染界面,而是集中处理多个改变然后一次性更新界面。
十五、React为什么要自定义合成事件
- 是对于跨浏览器兼容性和对性能优化的考虑;
- 跨浏览器兼容性:react会处理不同浏览器之间的差异,让你的代码能一样的工作;
- 性能优化:react通过合成事件系统来处理事件而不是直接绑定原生事件到DOM上;
- 合成事件池:所有事件在回调之后都会回收、重用,而不是每次事件触发都重新创建一个;
- 更容易控制:提供reactAPI更容易控制这些事件行为,如:event.stopPropagation()阻止冒泡
- 与react更新机制协调:UI渲染和事件处理更好的同步
十六、commpoentWillUnMount 生命周期对应的hooks怎么写

十七、闭包问题
- 闭包陷阱是由于回调函数捕获了其定义时作用域内的变量,并且这些变量在后续渲染中发生了变化,但回调函数仍然使用最初捕获的值
- 函数组件利用hooks去捕获某个变量的状态,并且在异步操作(setTimeout、Promise),或者在回调函数中引用了这个状态
- 例如:
App组件初次渲染执行useEffect中的定时器每次执行都会将count加1,但是运行后 发现只会执行一次而且 打印的都是0
- 原因:组件每次渲染的时候都会重新执行函数中的代码,但是重新执行的时候count变量在闭包中,所以只会在首次渲染的时候会捕获到函数里面count的值,之后渲染即使count发生了变化,但是之前捕获到的变量仍旧会保持原来的值,就会形成闭包陷阱
- 解决:
- 利用useRef:用useRef保存数据,每次使用最新数据的时候从ref里面去取就行了
- 利用useEffect第二个参数去更新
十八、redux
redux如何做持久化的存储:用 redux-persist 这个库
-
配置
redux-persist:
接下来,需要设置redux-persist与 Redux Store 一起工作。首先,配置你的 root reducer 以支持持久化。javascript复制代码 import { persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; // 使用 localStorage 作为默认存储 import { combineReducers } from 'redux'; import yourReducer from './yourReducer'; const persistConfig = { key: 'root', storage, }; const rootReducer = combineReducers({ yourReducer: yourReducer, // 任何其他的 reducer }); const persistedReducer = persistReducer(persistConfig, rootReducer); -
创建 Redux Store 与 Persistor:
使用持久化后的 reducer 创建 Redux Store,并创建持久化器(Persistor)。javascript复制代码 import { createStore } from 'redux'; import { persistStore } from 'redux-persist'; import persistedReducer from './reducers'; // 使用持久化的 reducer const store = createStore(persistedReducer); const persistor = persistStore(store); -
在应用中集成 PersistGate:
在你的 React 应用中,使用PersistGate组件包裹应用的根组件,确保在恢复状态后再渲染 UI。一般来说,PersistGate放置在Provider之后。javascript复制代码 import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import App from './App'; import store, { persistor } from './store'; ReactDOM.render( <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <App /> </PersistGate> </Provider>, document.getElementById('root') ); -
处理异步加载(可选) :
PersistGate的loading属性允许你在恢复状态之前显示一个加载组件。可以是任何 React 组件,比如一个简单的加载指示器。 一、核心三要素是什么之间的关系呢
1. 核心三要素
-
单一数据源
-
state只读
-
使用纯函数来执行修改
-
单向数据流:view-->action-->reducer-->store-->view循环
1. 为什么redux要求 reducer 是纯函数
-
保证状态变更可预测、可回溯
-
需要纯函数的特性(无副作用、相同的数据输出)与状态管理的关系
二、原理与工作流程 创建action然后通过dispatch通知到redux的store,store接收到后会触发响应的函数,然后会根据action的type做出响应的改变并返给store,最后store保存新的状态并通知组件触发UI的更新 三、redux如何实现状态共享
- 通过单一的store和react-redux的Provider注入上下文,以及context api实现的状态共享 context api的作用: 状态共享、避免手动传递props、动态更新
四、中间件
1. redux中间件的原理是什么,如何实现一个logger中间件 它允许在action被发送到reducer之前拦截并处理这些action,或者执行一些副作用(effects),本质是通过包装 store的dispatch方法,在action和reducer之间插入一些自定义的逻辑 实现一个 logger中间件
redux-Thunk和recux-saga的区别:
1. thunk:
- 异步处理方式:允许编写一个返回函数的 action creator,这个函数可以接收 dispatch 和 getState作为参数,然后就允许在函数内部进行异步操作,并在适当时候调用 dispatch 来分发同步action
- 适用场景:适合简单的场景; 如:只需要简单的网络请求并且请求完成后更新redux-store
2. saga:
- 异步处理方式:用Es6的Generator函数实现异步的管理,它允许你暂停、恢复函数的执行,使异步代码看起来像同步代码一样,saga提供了一系列的"effects",如:take、put、call等用于描述异步操作和控制流程。
- 适用场景:处理复杂的异步操作场景,比如需要管理多个并发请求、取消操作等
五、性能优化
- 函数组件中可以用 React.memo对组件进行包裹他会对props和state进行浅比较,避免不必要的渲染
- 使用useSelector用于比较当前的state和上次的state,没有变化就不会重新渲染
- 避免在selector中进行复杂的计算,selector应该只用于派发数据
六、useSelector和connect的区别
- useSelector基于Hooks,更加的简介
- connect自动做浅比较,useSelector需要手动处理
十九、react-router
在单页面应用中,一个web项目只有一个html页面,当页面加载完成之后,就不会因为用户的操作 去进行页面的重新加载或者跳转 特性:
- 改变url后不让浏览器向服务器发送请求
- 在不刷新页面的前提下动态改变浏览器地址栏中的URL地址
两种模式
- hash模式(HashRouter):
- URL格式:在URL后面加上 #
- 工作原理:#后面是路由部分,路由部分发生变化的时候浏览器不会向服务器发送请求,而是会触发hashChange事件 然后 SPA可以监听这个事件,然后根据新的 hash 值去渲染对应的组件
- 优点:可以很好的兼容,可以在不支持 Html5 history Api中使用。
- 缺点:不美观 路由中带有 #
- history模式:
- URL格式:没有 # 与常规网站的相同
- 工作原理:根据Html5中的 historyApi来实现路由,根据 history.pushState 和 history.replaceState来改变URL,同时不刷新页面,当URL改变的时候 页面会触发 popState事件,但是SPA一般不会依赖这个来进行对路由的处理,而是会主动监听URL的变化并渲染对应的组件
- 优点:URL看起来比较美观
- 缺点:需要服务器端支持,如果不支持的话,用户输入URL地址可能会返回 404
二十、react的设计思想是什么
- 组件化:React的核心思想是组件化,即将UI拆分成小的、可重用的部件,以提高可维护性和可重用性。这些组件可以嵌套和组合,以构建复杂的用户界面。每个组件拥有自己的状态和生命周期。
- 单项数据流:数据自顶向下流动,父组件通过props向子组件传递数据
- 声明式编程:只需声明在任何给定时刻UI应该是什么样子,React负责更新DOM以匹配该描述
- 虚拟DOM:React在内存中维护一个虚拟DOM树,当状态改变时,React创建新的虚拟DOM树并与旧树比较,只将必要的更改应用到实际DOM,从而减少了直接操作DOM的次数,提高了性能。
- 函数式编程与不可见性:强调纯函数、不可变数据和副作用管理。React鼓励使用不可变数据更新状态,例如通过创建新对象/数组(而非直接修改原数据)来更新状态。不可变性可以使代码更加安全,因为它可以避免在不同组件之间共享状态时出现潜在的问题。
二十一、react类组件中 super 和 super(props)的区别
- 不传:直接调用
super()而不传递props在React组件中可能会导致问题,因为React的基类(即React.Component)的构造函数期望能够接收到props参数,以便正确地初始化组件的状态和生命周期 - 传递 :当你使用
super(props)时,你实际上是在调用React.Component的构造函数,并将props作为参数传递给它。这样,React的内部机制就能够正确地处理props,并将其存储在组件实例上,以便在组件的方法中访问
二十二、hooks为什么要写在顶层
一、确保调用顺序一致:算是他设计的约束,确保调用在每次渲染时顺序保持一致; 二、 遵循 React 的设计原则:就是确保组件状态的清晰可预测,提高了代码的可读性和可维护性; 三、技术实现上的限制:react用的是Fiber架构来控制管理组件的渲染更新,每个组件都会生成一个节点(fiberNode),组件内使用的hook会以链表的形式挂载在节点(FiberNode)的 memoizedState上面,然后他每次都会以固定的顺序来遍历fiber树,并根据之前遍历的生成节点(FiberNode)来复制和更新hooks链表,所以如果Hooks的调用顺序发生变化可能导致找不到对应的hooks; 四、避免潜在的问题:还可以避免一些潜在的问题,如果在条件语句或循环中使用 Hooks,可能会导致在闭包中访问到的状态不是最新的,或者函数引用在多次渲染中不一致;
二十三、Vite为什么比Webpack快
-
利用现代浏览器对ES Modules(ESM)的原生支持:
- Vite直接利用现代浏览器对ESM的原生支持,在开发环境下可以直接运行源代码,
- Webpack得先进行打包。
-
按需编译策略:
- Vite采用了按需编译的策略,即只有当请求某个模块时,才会对该模块进行编译
- Webpack在构建时会对整个项目进行扫描和分析,无论模块是否被使用,都会被打包进最终的输出文件中
-
使用Esbuild预构建依赖:
- Vite使用Esbuild预构建依赖。Esbuild是一个使用Go语言编写的打包工具,其构建速度比以Node.js编写的打包器要快得多。
- Go语言在编译和执行速度上具有优势,使得Esbuild能够更快速地完成依赖的预构建。
-
更简洁的构建流程:
- 在生产环境下,Vite会使用Rollup进行打包。Rollup在处理ES模块方面有很好的性能表现,使得Vite生成的生产包通常比较简洁,构建速度也相对较快。
二十三、Es Module 和CommonJs
- 模块化的目的与意义:
- 目的:解决全局污染和依赖管理混乱的问题
- 意义:可以提高代码的可读性、可复用性、可维护性
ES Modules与CommonJs的区别
- 语法与用法:
- ES Modules使用
export和import关键字,而CommonJs使用require和module.exports/exports。 - ES Modules支持静态分析和优化,而CommonJs主要在运行时确定依赖关系。
- 加载与执行:
- ES Modules是异步加载的,而CommonJs是同步加载的。
- ES Modules在执行阶段执行模块,但依赖关系在预处理阶段确定;而CommonJs在执行阶段分析模块依赖并执行。
- 应用场景:
- ES Modules主要用于浏览器环境和现代JavaScript开发环境,支持树摇(tree shaking)等优化。
- CommonJs主要用于Node.js环境,是Node.js的默认模块化规范(尽管Node.js也支持ES Modules)。
ES Modules(ES6 Modules)
- 基本特性:
- ES Modules是ECMAScript 2015(ES6)引入的官方模块系统。
- 使用
export和import关键字进行模块的导出和导入。 - 支持静态分析和优化,因为模块的依赖关系在编译时就已经确定。
- 导入与导出:
- 导入: 使用
import关键字导入模块 - 导出: 用
export导出关键字导出变量、函数、类等
- 动态绑定与静态分析:
- 使用动态绑定,导入和导出的变量都指向内存中的同一位置。当导出模块的值发生变化时,导入模块中的值也会相应更新。
- 由于依赖关系在编译时确定,因此可以进行可靠的静态分析。
- 加载与执行:
- ES Modules是异步加载的,不会阻塞页面的渲染。
- 在浏览器环境中,可以通过
<script type="module">标签引入ES Module。 - ES Modules在执行阶段执行模块,但依赖关系在预处理阶段就已经确定。
CommonJs
- 基本特性:
- CommonJs是一种模块化规范,主要用于Node.js环境。
- 使用
require函数导入模块,使用module.exports或exports对象导出模块。
- 导入与导出:
- 导入: 使用
require函数导入模块。 - 导出: 将需要导出的变量、函数、类等赋值给
module.exports对象或exports对象
- 缓存机制与循环引用:
- 使用动态绑定,导入和导出的变量都指向内存中的同一位置。当导出模块的值发生变化时,导入模块中的值也会相应更新。
- 由于依赖关系在编译时确定,因此可以进行可靠的静态分析。
- 加载与执行:
- CommonJs模块是同步加载的,即在执行
require函数时会立即加载并执行模块文件。 - CommonJs模块在执行阶段分析模块依赖,并采用深度优先遍历(depth-first traversal)来执行模块。
默认导出和命名导出的区别
默认导出: 引入用:import MyComponentAlias from './MyComponent'
- 使用
export default关键字进行导出。 - 一个模块(文件)中只能有一个默认导出。
- 导入时,可以使用任意名称来引用导出的内容,因为默认导出是“匿名的”,即它不与特定的名称绑定。
命名导出: 引入用:import { MyHeader, MyFooter } from './MyComponents';
- 使用
export关键字进行导出,可以在函数、变量或类声明之前,也可以在模块的末尾统一导出。 - 一个模块可以有多个命名导出。
- 导入时,必须使用与导出时相同的名称(或大括号中的别名)来引用导出的内容。