Vue
1. 声明式渲染
-
模板语法:
{{ }}文本插值、v-html输出 HTML -
指令:
- 内置指令:
v-bind、v-on、v-model、v-if/v-else-if/v-else、v-show、v-for、v-pre、v-cloak、v-once - 自定义指令:全局
Vue.directive(Vue 2)/app.directive(Vue 3),局部directives选项
- 内置指令:
2. 响应式系统
-
Vue 2:基于
Object.defineProperty,递归遍历对象属性,无法检测:- 对象属性的添加/删除(需用
Vue.set/this.$set) - 数组索引修改(需用变异方法或
Vue.set) - 数组长度变化(需用
splice等)
- 对象属性的添加/删除(需用
-
Vue 3:基于
Proxy,可监听动态添加属性、数组索引修改,性能更优,支持Map、Set等原生集合的响应式 -
响应式原理:
- 数据劫持:Vue 对
data中的属性进行响应式处理(Vue 2 用Object.defineProperty,Vue 3 用Proxy)。 - 依赖收集:当组件渲染或计算属性等执行时,会访问响应式数据,此时将当前正在执行的
Watcher(观察者)添加到该数据的依赖列表(Dep)中。 - 派发更新:当数据被修改时,
Dep通知所有依赖它的Watcher执行更新。 - 异步更新队列:
Watcher更新时并不立即执行 DOM 操作,而是将自身推入一个队列,在下一个事件循环(microtask)中统一执行,并利用nextTick提供更新后的回调。
- 数据劫持:Vue 对
3. vue3 API
| API | 说明 |
|---|---|
ref() | 声明任意类型的响应式数据,需通过 .value 访问。 |
reactive() | 声明对象/数组类型的响应式数据,可直接访问属性。 |
computed() | 定义计算属性,基于响应式依赖缓存结果。 |
watch() | 监听特定数据源,在数据变化时执行副作用。 |
watchEffect() | 自动追踪其内部使用的响应式数据,并在数据变化时立即重新运行。 |
onMounted(), onUpdated(), onUnmounted() 等 | 组件生命周期不同阶段执行的钩子函数,用法与选项式 API 对应。 |
provide(), inject() | 用于跨层级组件通信,祖先组件提供数据,后代组件注入使用。 |
toRefs(), toRef(), isRef(), unref() 等 | 用于处理 ref / reactive 对象的辅助函数,帮助进行响应式转换和判断。 |
defineProps(), defineEmits() | 在 <script setup> 中声明组件的 props 和 emits,享受完整类型推导。 |
defineExpose() | 在 <script setup> 中声明当前组件暴露给父组件的属性或方法。 |
defineOptions() | 在 <script setup> 中声明组件名 (name) 或 inheritAttrs 等选项。 |
内置组件
内置指令
| 指令 | 说明 |
|---|---|
v-model | 在表单元素或组件上创建双向绑定。 |
v-if, v-else-if, v-else | 条件渲染,为 false 时不渲染元素。 |
v-show | 条件渲染,通过 CSS 的 display 属性切换。 |
v-for | 基于源数据多次渲染元素或模板块。 |
v-on (@) | 绑定事件监听器。 |
v-bind (:) | 动态地绑定一个或多个属性。 |
v-slot (#) | 用于声明具名插槽或作用域插槽。 |
4. 生命周期钩子
| 阶段 | Vue 2 钩子 | Vue 3 钩子(Options API) | Vue 3 钩子(Composition API) |
|---|---|---|---|
| 初始化 | beforeCreate, created | 同 | setup() 代替 beforeCreate/created |
| 挂载 | beforeMount, mounted | 同 | onBeforeMount, onMounted |
| 更新 | beforeUpdate, updated | 同 | onBeforeUpdate, onUpdated |
| 卸载 | beforeDestroy, destroyed | beforeUnmount, unmounted | onBeforeUnmount, onUnmounted |
| 错误捕获 | errorCaptured | 同 | onErrorCaptured |
| 其他 | activated, deactivated(keep-alive) | 同 | onActivated, onDeactivated |
| 调试 | renderTracked, renderTriggered(开发) | 同 | onRenderTracked, onRenderTriggered |
5. 插槽
- 默认插槽、具名插槽、作用域插槽(
slot-scopein Vue 2,v-slotin Vue 2.6+ & Vue 3) - Vue 3 中
v-slot统一为指令语法,slot和slot-scope被废弃
6. 混入(Mixin)
- 全局混入、局部混入
- 合并策略:数据递归合并,同名钩子合并为数组,方法/组件/指令等直接覆盖
- 缺点:命名冲突、隐式依赖、代码不直观 → 推荐组合式 API 替代
7. 自定义指令
-
钩子函数:
- Vue 2:
bind,inserted,update,componentUpdated,unbind - Vue 3:
beforeMount,mounted,beforeUpdate,updated,beforeUnmount,unmounted
- Vue 2:
-
参数:
el,binding,vnode,prevVnode
8. 过滤器(Filters)
- Vue 2 支持模板内过滤器(
{{ msg | filter }})及全局/局部定义 - Vue 3 中移除,推荐用计算属性或方法替代
9. 动画与过渡
<transition>单元素过渡<transition-group>多元素/列表过渡- 类名约定:
v-enter-from/v-enter-to等(Vue 3 命名变化) - JavaScript 钩子:
@before-enter,@enter,@after-enter等
10、组件通信方式(详细对比)
| 方式 | Vue 2 | Vue 3 |
|---|---|---|
| props / $emit | 支持 | 支持,emit 需在 setup 中声明 |
| v-model | 单个,value + input | 可多个,modelValue + update:modelValue,支持自定义修饰符 |
| parent / $children | $children 存在 | 移除 $children,推荐 ref + $parent 或组合式 API |
| provide / inject | 默认非响应式,可传递响应式对象 | 支持响应式传递,可提供 ref/reactive |
| event bus | new Vue() 作为总线 | 推荐用 mitt 等第三方库 |
| Vuex | Vuex 3 | Vuex 4 / Pinia |
| slot 作用域 | slot-scope | v-slot 统一语法 |
| 组合式 API | 无 | 可直接使用 ref 传递,逻辑复用更灵活 |
11、Vue Router 对比(3.x vs 4.x)
| 特性 | Vue Router 3(Vue 2) | Vue Router 4(Vue 3) |
|---|---|---|
| 创建方式 | new VueRouter(...) | createRouter({ ... }) |
| 模式 | mode: 'history' / 'hash' | history: createWebHistory() / createWebHashHistory() |
| 路由守卫 | beforeEach / beforeResolve / afterEach | 同,但支持组合式 API 中的 onBeforeRouteUpdate 等 |
| 路由元信息 | meta | 同 |
| 动态路由 | addRoutes | addRoute,且支持动态删除 |
| 导航故障 | NavigationFailureType | 更完善的类型 |
| 组合式 API | 不支持 | useRouter、useRoute |
12、状态管理:Vuex vs Pinia
| 特性 | Vuex 3/4 | Pinia |
|---|---|---|
| 设计理念 | 基于 Flux,强调 mutations / actions / getters | 更简洁,直接修改 state,支持组合式 API |
| 类型推断 | 需要额外处理 | 原生 TypeScript 支持 |
| 模块化 | 通过 modules | 通过多个 store 自然分割 |
| 异步处理 | actions 中 | actions 中,可直接使用 async/await |
| 代码量 | 较多模板代码 | 更少,更直观 |
| 热更新 | 有限支持 | 支持 store 热更新 |
| Vue 3 推荐 | 可用,但官方转向 Pinia | 官方推荐,轻量且强大 |
13、构建工具:Vue CLI vs Vite
| 特性 | Vue CLI(基于 webpack) | Vite |
|---|---|---|
| 启动速度 | 慢(打包后启动) | 极快(按需编译,原生 ES modules) |
| 生产构建 | 基于 webpack,配置灵活但复杂 | 基于 Rollup,预配置更简单 |
| 插件生态 | 丰富的 webpack 插件 | 插件系统兼容 Rollup 插件,且提供 Vite 插件 |
| 配置方式 | vue.config.js | vite.config.js |
| 开发环境 | HMR 较慢(大规模项目) | HMR 快速,保留状态 |
| 环境变量 | VUE_APP_* | VITE_* |
14、响应式原理(Vue 2 vs Vue 3)
Vue 2 响应式
-
遍历 data 对象,对每个属性递归调用
defineReactive,为每个属性创建 Dep(依赖收集器)。 -
每个属性对应一个 Watcher(观察者),在渲染时收集依赖。
-
缺点:
- 无法检测对象属性的新增/删除(需用
Vue.set/this.$set)。 - 无法直接通过索引修改数组(
arr[0] = xx不触发更新,需用变异方法如push,splice)。 - 初始化时需要递归遍历,性能略差。
- 无法检测对象属性的新增/删除(需用
Vue 3 响应式
-
基于
Proxy代理整个对象,可拦截get、set、deleteProperty等操作。 -
优点:
- 动态添加/删除属性自动响应。
- 数组索引修改和
length变化自动响应。 - 支持
Map、Set等原生集合。 - 惰性响应式:只有访问到属性时才会递归代理,性能更好。
总结
Vue 2 通过 Object.defineProperty 劫持对象属性的 getter/setter 来实现响应式,但存在局限性,比如无法监听动态添加的属性,需要通过 Vue.set 处理。Vue 3 改用 Proxy,可以代理整个对象,支持多种操作拦截,解决了上述问题,同时性能更优。响应式核心是依赖收集和派发更新,在 getter 中收集依赖,在 setter 中触发更新,并通过异步队列实现批量更新
15、虚拟 DOM 与 diff 算法
diff 策略
- 同层比较:只比较同一层节点,不跨层。
- 双端比较(Vue 2):新旧 VNode 的 children 数组通过头尾交叉比较,找到可复用的节点。
- 静态提升(Vue 3):编译时标记静态节点,更新时跳过它们。
- Patch flag(Vue 3):标记动态节点,只更新变化的部分
总结
虚拟 DOM 是一种用 JS 对象模拟真实 DOM 的结构,通过 diff 算法对比新旧 VNode,找出差异并批量更新真实 DOM,减少了直接操作 DOM 的性能开销。Vue 2 的 diff 采用双端比较,Vue 3 则引入了静态提升和 patch flags,进一步优化了更新效率。key 是 diff 过程中识别节点的重要依据,使用稳定的 key 可以保证节点复用,避免渲染错误。
16、 生命周期钩子(执行顺序、使用场景)
父子组件生命周期顺序
- 创建:父 beforeCreate → 父 created → 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted → 父 mounted
- 更新:父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
- 销毁:父 beforeDestroy → 子 beforeDestroy → 子 destroyed → 父 destroyed
常用钩子作用
- beforeCreate:实例初始化后,数据观测和事件配置之前。无法访问 data、props。
- created:可访问数据,但 DOM 未挂载,适合异步请求、初始化数据。
- mounted:DOM 已挂载,可操作 DOM,适合第三方库初始化。
- beforeDestroy:销毁前,适合清除定时器、取消订阅。
- activated / deactivated:
keep-alive组件激活/停用。
17、$nextTick 原理及使用场景
原理
Vue 的异步更新队列。数据变化后,Vue 将开启一个队列,把同一个事件循环内的所有数据变化缓存起来,然后在下一个事件循环(microtask)统一执行 DOM 更新。$nextTick 的回调会在 DOM 更新完成后执行。
使用场景:
- 在数据变化后,需要获取更新后的 DOM 结构。
- 需要在 mounted 钩子中确保子组件渲染完成。
- 异步操作后需要等待 DOM 同步。
面试回答:
“$nextTick 利用 Promise 或 MutationObserver 等微任务机制,将回调延迟到下次 DOM 更新循环之后执行。我们常用来解决数据变化后立即操作 DOM 的问题,比如滚动到底部、获取元素宽高等。Vue 3 中同样有 nextTick 函数,可在组合式 API 中使用。
18、 keep-alive 实现原理及生命周期
作用:
缓存不活动的组件实例,避免反复渲染。
原理:
内部维护一个缓存对象(键是组件的 key 或自身),当组件切换时,将被移除的组件实例保留在缓存中,而不是销毁。再次激活时从缓存取出复用,触发 activated 和 deactivated 钩子。
相关属性:
include/exclude:正则或数组,指定要缓存/不缓存的组件。max:最大缓存数,超出时根据 LRU 策略删除。
生命周期:
- 首次进入:
created→mounted→activated - 缓存后再次进入:
activated(不会重新执行created/mounted) - 离开时:
deactivated
面试回答:
“keep-alive 是一个抽象组件,它通过缓存 VNode 来保留组件状态,避免重复渲染。内部使用 LRU 算法管理缓存,可以通过 include 和 max 控制缓存策略。被缓存的组件会多出 activated 和 deactivated 钩子,用于在激活/停用时执行逻辑。
19、 组合式 API 与选项式 API 的优缺点
选项式 API(Vue 2 主流):
- 优点:结构清晰(data、methods、computed 分块),适合初学者。
- 缺点:逻辑分散,复杂组件难以维护;复用逻辑需借助 mixin,存在缺陷。
组合式 API(Vue 3 引入):
-
优点:
- 逻辑集中,按功能组织代码,可读性和可维护性高。
- 逻辑复用简单,通过组合函数(hooks)实现,无命名冲突。
- 更好的 TypeScript 类型推断。
-
缺点:学习曲线稍陡,对初学者不够直观。
面试回答:
“选项式 API 将组件选项按类型划分,代码直观但逻辑分散。组合式 API 将相关逻辑聚合在 setup 中,通过组合函数实现复用,尤其适合大型复杂组件。Vue 3 并未废弃选项式 API,两者可混用,但组合式 API 提供了更好的逻辑复用能力和类型支持,是未来的推荐写法。”
20、SSR 原理及优缺点
原理:
- 服务端运行 Vue 应用,生成 HTML 字符串直接返回给浏览器,客户端再“激活”(hydrate)为可交互应用。
- 同构:同一份代码在服务端和客户端均可运行。
优点:
- 更好的 SEO:搜索引擎能抓取完整 HTML。
- 更快的首屏加载:用户无需等待 JS 下载即可看到内容。
缺点:
- 开发复杂度高:需考虑 Node.js 环境兼容性。
- 服务器负载大:每个请求都重新渲染,需注意缓存策略。
- 部分 API 在服务端不可用(如 window、document),需条件判断。
面试回答:
“SSR 在服务端将 Vue 组件渲染成 HTML,发送给客户端,然后客户端进行激活。它主要解决 SPA 的 SEO 问题和首屏加载速度。但实现成本较高,需要处理服务端和客户端环境的差异,并关注服务器性能。通常我们会借助 Nuxt.js(Vue 2)或 Nuxt 3(Vue 3)这样的框架来简化 SSR 开发。”
21、Vue 3 新特性及与 Vue 2 的区别
核心新特性:
- 组合式 API:更好的逻辑复用和代码组织。
- Proxy 响应式:解决 Vue 2 的响应式局限,性能更优。
- Teleport:将组件内容渲染到任意 DOM 位置。
- Fragment:组件支持多个根节点。
- Suspense:用于异步组件加载时的占位。
- 全局 API 改造:
createApp替代new Vue,全局配置隔离。 - 更好的 TypeScript 支持:源码用 TS 重写,类型更完善。
- 性能提升:编译优化(静态提升、patch flag),打包体积更小。
- Vite 官方构建工具:开发体验极大提升。
破坏性变更:
- 移除过滤器、
$children、$on/$once/$off、v-on.native等。 v-model默认 prop 和事件变化,支持多个绑定。v-if与v-for优先级改变。
面试回答:
“Vue 3 相比 Vue 2 在响应式系统、组合式 API、性能、TypeScript 支持等方面有重大改进。它引入了 Teleport、Suspense 等内置组件,并用 createApp 创建应用,避免全局污染。虽然有一些破坏性变更,但官方提供了迁移构建和工具帮助升级。Vue 3 也带来了更现代的构建工具 Vite,提升了开发体验。”
22、常见API使用方式 defineEmits、defineExpose、defineOptions、defineProps、
1. defineProps – 接收父组件传递的数据
作用:声明组件的 props(属性),代替传统的 props 选项。
基本用法:
<script setup>
// 运行时声明(自动推断类型)
const props = defineProps(['title', 'count'])
// 带类型的声明(TypeScript)
const props = defineProps<{
title: string
count?: number // 可选
}>()
</script>
父组件使用:
<MyComponent title="Hello" :count="10" />
2. defineEmits – 向父组件发送事件
作用:声明组件可以触发的事件。
<script setup>
// 简单声明
const emit = defineEmits(['update', 'delete'])
// 带参数验证(TypeScript)
const emit = defineEmits<{
(e: 'update', id: number): void
(e: 'delete', name: string): void
}>()
// 触发事件
emit('update', 123)
</script>
父组件监听:
<MyComponent @update="handleUpdate" @delete="handleDelete" />
3. defineExpose – 暴露组件内部属性/方法给父组件(通过 ref)
作用:默认 <script setup> 下的组件是关闭的,父组件无法通过 ref 访问其内部成员。使用 defineExpose 明确暴露。
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
// 只暴露 increment 和 count,其他不暴露
defineExpose({
increment,
count
})
</script>
父组件访问:
<template>
<MyComponent ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const childRef = ref()
onMounted(() => {
childRef.value.increment() // 调用子组件方法
console.log(childRef.value.count) // 读取子组件数据
})
</script>
4. defineOptions – 设置组件选项(Vue 3.3+)
作用:在 <script setup> 中声明组件名、继承属性、自定义选项等,无需单独的 <script> 块。
<script setup>
defineOptions({
name: 'MyCustomName', // 组件名称
inheritAttrs: false, // 是否继承非 prop 属性
// 其他选项(如 components、directives 一般不在这里,但可自定义)
})
</script>
典型场景:
- 设置组件名(方便 Vue Devtools 识别)
- 关闭属性继承(手动控制
$attrs)
23、wacth & watchEffect 区别
| 特性 | watch | watchEffect |
|---|---|---|
| 依赖收集 | 显式指定要监听的数据源(ref、reactive 属性、getter 函数) | 自动收集回调函数中使用的所有响应式数据 |
| 初始执行 | 默认不执行,数据第一次变化时才执行(可配置 immediate: true) | 立即执行一次,同时收集依赖 |
| 访问新旧值 | 回调中提供旧值和新值 | 只能访问新值(无法直接获取旧值) |
| 监听多个源 | 支持同时监听多个数据源(数组形式) | 自动收集多个依赖,无需显式指定 |
| 精准控制 | 可以配置 deep、flush、immediate 等选项 | 只有 flush 选项(以及 onTrack/onTrigger 调试) |
| 停止监听 | 调用返回的函数 | 同样返回停止函数 |
| 适用场景 | 需要知道具体哪个数据变化、需要旧值、需要惰性执行 | 简单副作用,自动跟踪依赖,不需要旧值 |
1、watch 基础用法
<script setup>
import { ref, reactive, watch } from 'vue'
const count = ref(0)
const state = reactive({ name: 'Vue', age: 3 })
// 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
// 监听 getter 函数
watch(
() => state.age,
(newAge, oldAge) => {
console.log(`年龄从 ${oldAge} 变为 ${newAge}`)
}
)
// 监听多个源(数组)
watch([count, () => state.age], ([newCount, newAge], [oldCount, oldAge]) => {
console.log(`count: ${oldCount}->${newCount}, age: ${oldAge}->${newAge}`)
})
// 立即执行 + 深度监听
watch(
() => state,
(newVal, oldVal) => {
console.log('state 变化了', newVal)
},
{ immediate: true, deep: true }
)
</script>
2、watchEffect 基础用法
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const count = ref(0)
const state = reactive({ name: 'Vue', age: 3 })
// 自动收集依赖:count 和 state.age
watchEffect(() => {
console.log(`count: ${count.value}, age: ${state.age}`)
})
// 初始立即输出:count: 0, age: 3
// 之后任何依赖变化都会重新执行
// 停止监听
const stop = watchEffect(() => { /* ... */ })
stop() // 手动停止
</script>
24、provide() 和 inject() 跨层级组件通信例子
<!-- Ancestor.vue -->
<script setup>
import { provide, ref } from 'vue'
// 提供普通值
provide('theme', 'dark')
// 提供响应式数据(推荐)
const count = ref(0)
const updateCount = () => count.value++
provide('count', count)
provide('updateCount', updateCount)
</script>
<!-- Descendant.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 默认值 'light'
const count = inject('count')
const updateCount = inject('updateCount')
// 使用
console.log(theme) // 'dark'
count.value++ // 响应式更新
updateCount() // 调用方法
</script>
25、toRefs、toRef、isRef、unref 响应式引用工具
1. toRefs – 将响应式对象转换为普通对象,每个属性都是 ref
作用:解构 reactive 对象时保持响应性。
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Vue' })
// ❌ 直接解构会丢失响应性
let { count, name } = state
count++ // 不会触发视图更新
// ✅ 使用 toRefs 包装
const stateRefs = toRefs(state)
const { count, name } = stateRefs
count.value++ // 响应式生效
原理:toRefs 为每个属性创建一个 ref 链接到原对象的对应属性。
2. toRef – 为响应式对象的单个属性创建 ref
作用:保持对源对象属性的响应式引用,常用于将 props 的某个属性转为 ref 以便传递。
import { reactive, toRef } from 'vue'
const state = reactive({ count: 0 })
const countRef = toRef(state, 'count')
countRef.value++ // 同时修改 state.count
console.log(state.count) // 1
典型场景:组合函数接收 props 中的某个属性并保持响应性。
// useFeature.js
import { toRef, watchEffect } from 'vue'
export function useFeature(propRef) {
const propVal = toRef(propRef) // 确保是 ref
watchEffect(() => {
console.log(propVal.value)
})
}
3. isRef – 判断某个值是否为 ref
import { ref, reactive, isRef } from 'vue'
const count = ref(0)
const state = reactive({})
console.log(isRef(count)) // true
console.log(isRef(state)) // false
4. unref – 如果参数是 ref 则返回其 value,否则返回参数本身
作用:方便地获取值,无需手动判断 .value。
import { ref, unref } from 'vue'
const count = ref(0)
const plain = 42
console.log(unref(count)) // 0
console.log(unref(plain)) // 42
// 等价于
function myUnref(val) {
return isRef(val) ? val.value : val
}
常用场景:在组合函数中,参数可能是 ref 也可能是普通值,使用 unref 统一处理。
React
1. React 是什么?核心特点
React 是 Meta 开源的 JavaScript UI 库,专注于构建用户界面。核心特点:
- 声明式编程:描述 UI 状态,React 自动处理 DOM 更新。
- 组件化:UI 拆分为独立可复用的组件。
- 单向数据流:数据从父组件流向子组件,可预测、易调试。
- 虚拟 DOM + Fiber:高效更新,可中断渲染。
- JSX:JavaScript 语法扩展,允许在 JS 中写类似 HTML 的标记。
2. JSX
JSX 是 React.createElement 的语法糖。
// JSX 写法
const element = <h1 className="title">Hello React</h1>;
// 编译后
const element = React.createElement('h1', { className: 'title' }, 'Hello React');
浏览器无法识别 JSX,需要通过 Babel 编译为普通 JS 代码才能执行
3、组件间通信
| 方式 | 适用场景 | 示例 |
|---|---|---|
| Props | 父→子传递数据 | <Child message={msg} /> |
| 回调函数 | 子→父传递数据 | 父传函数给子,子调用 props.onChildData(data) |
| Context API | 跨层级组件通信(避免 props 逐层传递) | createContext + useContext |
| 状态管理库 | 大型应用全局状态 | Redux Toolkit、Zustand、Jotai |
| Refs | 直接访问 DOM 或子组件实例 | useRef + ref 属性 |
| 自定义 Hooks | 复用状态逻辑 | useAuth、useFetch 等 |
| 高阶组件(HOC) | 共享逻辑(较少使用,Hooks 更优) | 接收组件返回增强组件 |
| Event Bus(事件总线) | 任意组件通信(非 React 原生) | mitt 等第三方库 |
4、API
4.1 内置 Hooks
4.2 内置组件
这些是可以在 JSX 中使用的 React 内置组件,以 Symbol 常量形式导出。
4.3 工具类 API
4.4 通用 DOM API
| API | 说明 |
|---|---|
createPortal | 允许将子组件渲染到 DOM 树中父组件 DOM 层次之外的不同位置,常用于模态框、全局提示等。 |
flushSync | 强制 React 同步执行状态更新并立即刷新 DOM。 |
4.5 资源预加载 API
这些 API 用于预加载脚本、样式表、字体等资源,从而让应用更快。基于 React 的框架通常会自动处理资源加载。
4.6 通用 DOM API
| API | 说明 |
|---|---|
createPortal | 允许将子组件渲染到 DOM 树中父组件 DOM 层次之外的不同位置,常用于模态框、全局提示等。 |
flushSync | 强制 React 同步执行状态更新并立即刷新 DOM。 |
5、React Router(路由)
React Router v6 完全利用 Hooks 重构。
核心组件
| 组件 | 作用 |
|---|---|
BrowserRouter | history 模式路由容器 |
HashRouter | hash 模式路由容器 |
Routes / Route | 定义路由规则 |
Link / NavLink | 声明式导航 |
Outlet | 嵌套路由占位符 |
核心 Hooks
| Hook | 作用 |
|---|---|
useParams | 获取路由参数 |
useLocation | 获取当前 location 对象 |
useNavigate | 程序化导航 |
useRoutes | 配置式路由(替代 Routes + Route) |
6、React-Redux
| API | 说明 |
|---|---|
<Provider store> | 顶层组件,使 store 对下层组件可用。 |
connect(mapStateToProps, mapDispatchToProps, mergeProps, options) | 高阶组件(HOC),将 store 中的 state 和 dispatch 映射到组件的 props。 |
useSelector(selector) | Hook,从 store 中提取数据,当数据变化时强制组件重新渲染。 |
useDispatch() | Hook,返回 store 的 dispatch 函数。 |
useStore() | Hook,返回 store 实例本身(不常用)。 |
Redux 现代推荐: (Redux Toolkit + React-Redux Hooks )
必须掌握的核心 API
| API | 作用 | 一句话说明 |
|---|---|---|
configureStore | 创建 store | 像 createStore 但更智能,自动加 thunk 和 DevTools |
createSlice | 同时生成 reducer 和 action creators | 传入 name、initialState、reducers 对象,自动生成 |
createAsyncThunk | 处理异步 action | 自动生成 pending/fulfilled/rejected 三个 action,并在 extraReducers 中处理 |
// store.js
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 1. 同步 slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 }, // 直接“修改”
decrement: state => { state.value -= 1 },
incrementByAmount: (state, action) => { state.value += action.payload }
}
});
// 2. 异步 thunk
export const fetchUser = createAsyncThunk('user/fetch', async (userId) => {
const res = await fetch(`/api/user/${userId}`);
return res.json();
});
// 3. 异步 slice (处理 thunk 的三种状态)
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => { state.loading = true })
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
// 导出 action creators 和 reducer
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
user: userSlice.reducer
}
});
export default store;
React-Redux Hooks —— 在组件里用状态
两个核心 Hook
| Hook | 作用 | 类比 |
|---|---|---|
useSelector | 从 store 中读取数据 | 类似 mapStateToProps |
useDispatch | 拿到 dispatch 函数 | 类似 mapDispatchToProps |
使用步骤(配合上面的 store)
1. 顶层用 Provider 包裹
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
2. 组件内读取和派发
// Counter.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './store';
function Counter() {
// 读取状态
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
3. 异步 thunk 的派发
import { fetchUser } from './store';
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading, error } = useSelector(state => state.user);
useEffect(() => {
dispatch(fetchUser(userId));
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{data?.name}</div>;
}
7、React 底层原理
1. 虚拟 DOM
- 减少频繁的真实 DOM 操作
- 跨平台(React Native)
- 便于 diff 算法计算差异
2. Diff 算法
- 同层比较,不跨层级
- 类型不同 → 直接销毁重建
- 类型相同 → 对比属性
- 列表靠 key 识别节点复用,复杂度优化到 O(n)
为什么 key 不能用 index?
数组增删前置元素会导致 index 错乱,引发组件状态错位、DOM 复用错误。应使用唯一稳定的业务 id。
3. Fiber 架构(React 16+)
两个阶段:
- Render 阶段:可中断、分片、优先级调度,遍历构建 Fiber 树
- Commit 阶段:不可中断,一次性更新 DOM、布局、绘制
4. React 更新流程
触发 setState → 生成更新任务 → Fiber 调和(可中断)→ 收集 DOM 变更 → Commit 一次性渲染 → 浏览器绘制
5. 合成事件
React 自己实现了一套事件系统(事件委托到 root 节点),性能高,与原生事件混用时,原生先执行 → 合成后执行,阻止冒泡互不生效。