当前位置: 首页 > news >正文

前端常见面试题

1. Vue 3 响应式原理:Proxy 相较于 Object.defineProperty 的优势

一句话概括

  • Object.defineProperty 是对对象的每个属性进行拦截。
  • Proxy 是对整个对象进行代理,拦截所有操作,更强大高效。

Object.defineProperty(Vue 2)的痛点

  • 深度递归遍历所有属性,初始化性能差,层级深时消耗大。
  • 不能监听新增或删除属性,动态增删属性需特殊 API(如 $set$delete)。
  • 不能原生监听数组索引或长度变更,需要重写七个数组方法实现监听。
function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {get() {console.log(`读取属性 ${key}:`, val);return val;},set(newVal) {console.log(`设置属性 ${key} 为:`, newVal);val = newVal;}});
}function observe(obj) {if (typeof obj !== 'object' || obj === null) return;Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}const data = { name: 'Vue2', age: 2 };
observe(data);data.name;       // 读取属性 name: Vue2
data.age = 3;    // 设置属性 age 为: 3// 新增属性 —— 无法监听
data.gender = 'male';
data.gender = 'female'; // 没有触发 set// 数组监听 —— 不能监听索引变化
let arr = [1, 2, 3];
observe(arr);
arr[0] = 99; // 无法监听
arr.push(4); // Vue 2 内部会 hack push 方法

image-20250810140242862

Proxy(Vue 3)的优势

  • 代理整个对象,拦截所有操作(读、写、删、遍历等)。
  • 支持动态新增/删除属性,无须特殊 API。
  • 支持所有数组操作,包括索引赋值、长度修改等,无需 hack。
  • 性能更优,惰性求值,初始化无需递归遍历。
import { reactive } from 'vue'
function reactiveWithoutLog(target) {return new Proxy(target, {get(obj, key) {return obj[key]},set(obj, key, value) {obj[key] = valuereturn true},deleteProperty(obj, key) {delete obj[key]return true}})
}// 组合式 API 核心:用 reactive 包裹 Proxy
function useReactiveState() {const state = reactive(reactiveWithoutLog({ name: 'Vue3', age: 3 }))const arr = reactive(reactiveWithoutLog([1, 2, 3]))function demo() {// 访问属性console.log('读取 name:', state.name)// 修改属性state.name = 'Vue3 Updated'console.log('修改 name:', state.name)// 新增属性state.gender = 'male'console.log('新增 gender:', state.gender)// 删除属性delete state.ageconsole.log('删除 age,当前 age:', state.age)// 数组修改arr[0] = 99console.log('修改数组第0项:', arr[0])arr.length = 1console.log('修改数组长度:', arr.length)
}return { state, arr, demo }
}// 运行演示
const { demo } = useReactiveState()
demo()

image-20250810142511829

总结

Vue 3 通过 Proxy 实现响应式,解决了 Vue 2 的多项缺陷:

  • 响应式性能更优,启动更快。
  • 支持动态属性新增和删除。
  • 天然支持数组所有操作。
  • 代码实现更简洁且易维护。

2. Composition API vs Options API

核心区别

  • Options API(选项式 API):按功能类别data, methods, computed 等)来组织代码。
  • Composition API(组合式 API):按业务逻辑功能组织代码,相关状态和方法聚合在一起。

对照表

对比项 Options API Composition API
代码组织方式 data / methods / computed 分块 按逻辑功能集中编写
逻辑聚合 同一功能的逻辑可能分散在多个选项中 同一功能的逻辑集中在一个函数或代码块内
逻辑复用 主要依赖 mixins(容易命名冲突、来源不清晰) 使用 composables(可导入函数,来源清晰)
类型支持 this 推导不直观,TypeScript 支持弱一些 变量是普通变量,TS 推导自然
学习曲线 对初学者友好,结构固定 灵活但需要理解 Hooks 式思维
大型项目维护 代码易分散,跨功能修改麻烦 高内聚,可模块化拆分功能
//Options API 示例
<script>
export default {data() {return {count: 0}},computed: {doubleCount() {return this.count * 2}},methods: {increment() {this.count++}}
}
</script>
//Composition API 示例
<script setup>
import { ref, computed } from 'vue'const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++
}
</script>
//逻辑复用示例 — 使用 Composition API 创建 useCounter 复用函数
// composables/useCounter.js
import { ref, computed } from 'vue'export function useCounter() {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return {count,doubleCount,increment}
}
//组件里使用:
<script setup>
import { useCounter } from '@/composables/useCounter'const { count, doubleCount, increment } = useCounter()
</script>

这样通过 Composition API,功能相关的状态和方法可以集中管理,逻辑清晰,复用方便,代码更易维护。

适用场景

  • Options API 更适合:
    • 小型项目或简单组件
    • 初学者快速上手
  • Composition API 更适合:
    • 中大型项目
    • 功能逻辑复杂、跨组件复用多
    • 需要更强的 TypeScript 支持

3. setup语法糖

核心作用

  • 简化 Composition API 写法:省略 setup() 方法和 return,直接在 <script setup> 中声明即可。
  • 自动暴露变量:顶层变量、方法、import 的组件都会自动暴露给模板。
  • 组件自动注册import 的组件可以直接在模板中使用,不用手动写 components
  • 简化 Props & EmitsdefineProps / defineEmits 宏直接声明,无需结构化 props, emit 参数。
  • 更强的 TypeScript 支持:泛型 + 类型推导让 TS 编写体验更好。

对照表

对比项 普通 Composition API <script setup> 语法糖
是否需要写 setup() 需要 不需要
变量暴露 必须 return 才能在模板中用 自动暴露
组件注册 需写 components 选项 直接 import 使用
Props & Emits 声明 在选项或 setup() 参数中定义 defineProps / defineEmits
TypeScript 支持 较为分散 更集中,类型推导更自然
//1. 传统 Composition API 写法(无 <script setup>)
<script>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'export default {components: { ChildComp },props: {title: String},emits: ['update'],setup(props, { emit }) {const count = ref(0)function increment() {count.value++emit('update', count.value)}return { count, increment }}
}
</script><template><div><h2>{{ title }}</h2><p>Count: {{ count }}</p><button @click="increment">+1</button><ChildComp /></div>
</template>
//2. 使用 <script setup> 重写后
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'const props = defineProps({title: String
})const emit = defineEmits(['update'])const count = ref(0)function increment() {count.value++emit('update', count.value)
}
</script><template><div><h2>{{ props.title }}</h2><p>Count: {{ count }}</p><button @click="increment">+1</button><ChildComp /></div>
</template>

4. Pinia vs Vuex

1. 数据修改方式

  • Vuex: 必须通过 mutations 修改 state。
  • Pinia: 可直接在组件中修改 state,也可在 actions 中改(this.count++)。

2. 模块化设计

  • Vuex: 使用 modules 配置,支持 namespaced,结构较复杂。
  • Pinia: 每个 store 独立定义,直接引入使用,更直观。

3. TypeScript 支持

  • Vuex: 类型推导复杂,需要额外定义类型文件。
  • Pinia: 从设计之初就支持 TS,类型推导自然。

4. 体积

  • Vuex: 相对更大。
  • Pinia: 仅约 1KB,轻量。

5. 开发者工具

  • 两者都支持 Vue Devtools。
//Vuex 修改 state 的写法
// store.js
import { createStore } from 'vuex'export const store = createStore({state() {return { count: 0 }},mutations: {increment(state) {state.count++}},actions: {increment({ commit }) {commit('increment')}}
})
// 组件中使用
this.$store.dispatch('increment')
console.log(this.$store.state.count)
//Pinia 修改 state 的写法
<script setup>
import { defineStore } from 'pinia'
// 定义 store(放到单独文件更好)
export const useCounterStore = defineStore('counter', () => {// 状态const count = ref(0)// 计算属性const doubleCount = computed(() => count.value * 2)// 方法function increment() {count.value++}return { count, doubleCount, increment }
})// 组件中使用
const counter = useCounterStore()// 调用方法
counter.increment()console.log('count:', counter.count)
console.log('doubleCount:', counter.doubleCount)
</script><template><div><p>Count: {{ counter.count }}</p><p>Double Count: {{ counter.doubleCount }}</p><button @click="counter.increment">Increment</button></div>
</template>

5. Vue Router:路由守卫

路由导航流程

  1. 在路由配置里调用 beforeEnter
  2. 解析异步路由组件
  3. 在被激活的组件里调用 beforeRouteEnter
  4. 调用全局的 beforeResolve 守卫
  5. 导航被确认
  6. 调用全局的 afterEach 钩子
  7. DOM 更新
  8. beforeRouteEnter 中传给 next 的回调函数被调用(此时可访问组件实例)

1. 全局守卫(beforeEachafterEach

使用场景: 全局登录权限控制、页面统计、修改标题、清理加载状态等。

  • //beforeEach(登录权限控制)
    import router from './router'
    router.beforeEach((to, from, next) => {const isLoggedIn = Boolean(localStorage.getItem('token'))if (to.meta.requiresAuth && !isLoggedIn) {next('/login') // 未登录跳转登录页} else {next()}
    })
    //afterEach(页面统计、修改标题)
    router.afterEach((to) => {document.title = to.meta.title || '默认标题'// 发送 PV 埋点等操作
    })

2. 路由独享守卫(beforeEnter

使用场景: 针对特定路由的权限校验(如管理员权限)。

  • //2. 路由独享守卫
    const routes = [{path: '/admin',component: Admin,beforeEnter: (to, from, next) => {const userRole = getUserRole()if (userRole === 'admin') {next()} else {next('/403')}}}
    ]
    

3. 组件内守卫

使用场景: 数据预加载、组件复用时刷新数据、阻止未保存离开。

//beforeRouteEnter(数据预加载)
export default {beforeRouteEnter(to, from, next) {fetchData().then(() => {next(vm => {vm.doSomething() // 访问组件实例})})}
}
//beforeRouteUpdate(路由参数变化时刷新数据)
beforeRouteUpdate(to, from, next) {this.loadData(to.params.id)next()
}
//beforeRouteLeave(阻止误操作离开)
beforeRouteLeave(to, from, next) {if (this.hasUnsavedChanges) {const answer = window.confirm('你有未保存内容,确定离开吗?')answer ? next() : next(false)} else {next()}
}

6. 组件通信

6.1 各种通信方式对比

通信方式 适用场景 优点 缺点
props / emits 父子组件通信 单向数据流,职责清晰,最常用 跨级或兄弟组件通信繁琐
v-model 父子组件通信(语法糖) 简化双向绑定代码 本质是 prop + emit,适用场景单一
provide / inject 祖孙 / 跨级组件通信 解决了 props 层层传递的“钻透”问题 数据来源不直观,默认非响应式(需传递 refreactive
Pinia / Vuex 任何组件间通信(尤其大型应用) 集中式管理,可预测、可调试 增加项目复杂度,小项目不必要
EventBus(不推荐) 任意组件通信 简单粗暴,快速实现 容易造成逻辑混乱、难调试、内存泄漏

6.2 props / emits(父子通信)

<!-- 父组件 -->
<template><Child :msg="parentMsg" @update-msg="handleUpdate" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'const parentMsg = ref('Hello from parent')function handleUpdate(newMsg) {parentMsg.value = newMsg
}
</script><!-- 子组件 Child.vue -->
<template><div><p>{{ msg }}</p><button @click="$emit('update-msg', 'Hello from child')">修改父消息</button></div>
</template>
<script setup>
const props = defineProps({ msg: String })
</script>

6.3 v-model(父子通信语法糖)

<!-- 父组件 -->
<template><Child v-model="text" /><p>父组件显示: {{ text }}</p>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'const text = ref('')
</script><!-- 子组件 Child.vue -->
<template><input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
const props = defineProps({ modelValue: String })
</script>

6.4 provide / inject(跨级通信)

<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'const sharedMsg = ref('这是祖先提供的消息')
provide('msg', sharedMsg)
</script><!-- 孙子组件 -->
<script setup>
import { inject } from 'vue'const msg = inject('msg')
</script><template><div>收到消息:{{ msg }}</div>
</template>

6.5 Pinia / Vuex(全局状态管理)

// Pinia store 例子 store/counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'export const useCounterStore = defineStore('counter', () => {const count = ref(0)function increment() {count.value++}return { count, increment }
})// 组件中使用
<script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()
counter.increment()
</script>

6.6 EventBus(不推荐)

// eventBus.js
import mitt from 'mitt'
export const eventBus = mitt()// 发送事件
eventBus.emit('myEvent', { data: 123 })// 监听事件
eventBus.on('myEvent', (payload) => {console.log('收到事件:', payload)
})

7. Vite:启动为什么那么快?与 Webpack 的核心区别

7.1 Vite 启动快的原因

  1. Dev Server 利用原生 ES 模块 (Native ESM):
    现代浏览器原生支持 import,Vite 利用此特性,开发时只按需转换和提供模块文件。浏览器根据模块内 import 动态请求依赖,Vite 分别提供对应模块,无需预先整体打包。
  2. 使用 esbuild 进行预构建:
    对第三方依赖(如 vuelodash),Vite 用 esbuild 预构建成 ESM 格式,打包成少数模块。esbuild 速度远快于传统 JS 打包器,提升整体启动效率。

7.2 与 Webpack 的核心区别

特性 Vite (开发模式) Webpack (开发模式)
核心原理 利用浏览器原生 ES Module,按需提供模块 启动前整体打包所有模块和依赖成 bundle
启动速度 极快,秒级启动,无需整体打包 较慢,项目大时遍历依赖图打包耗时较长
热更新 (HMR) 极快,只重新编译被修改模块,利用 ESM 热替换 较快,但需处理模块依赖关系,影响整个 bundle
构建工具 esbuild (预构建) + Rollup (生产打包) Webpack 自带(Loader + Plugin 体系)

7.3 Vite 与 Webpack 启动流程示意

项目结构:
src/
main.js
utils.js
componentA.js

Vite(按需模块加载)

  1. 浏览器请求 main.js,Vite 按需编译并返回 main.js
  2. 浏览器解析 main.js 中的 import,再请求 utils.jscomponentA.js
  3. Vite 按需编译并返回 utils.jscomponentA.js
  4. 模块逐个加载,启动速度快,无需先整体打包。

Webpack(启动时整体打包)

Webpack 在启动时:

  1. 遍历 main.js 的依赖图(utils.jscomponentA.js)。
  2. 将所有模块打包成一个大的 bundle.js
  3. 浏览器加载整个 bundle.js
  4. 启动时耗时较长,尤其项目依赖多时。

8. 图片懒加载和路由懒加载

8.1 图片懒加载(Image Lazy Loading)

  • 作用:
    延迟加载当前视口外的图片,减少页面初次加载资源,提升渲染速度和用户体验。

  • 方式一:原生 loading="lazy"

    <img src="image.jpg" alt="描述性替代文本" loading="lazy" width="200" height="200">
    

    浏览器会自动延迟加载图片,简单高效,但兼容性有限。

  • 方式二:使用 Intersection Observer API

    利用浏览器提供的接口检测图片是否进入视口,进入时才加载真实图片。

<script setup>
import { ref, onMounted } from 'vue'const imgSrc = ref('')
const placeholder = 'placeholder.jpg'let imgRef = nullonMounted(() => {const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) {imgSrc.value = 'real-image.jpg'observer.unobserve(entry.target)}})})if (imgRef) observer.observe(imgRef)
})
</script><template><img :src="imgSrc || placeholder" ref="imgRef" alt="懒加载图片" />
</template>

这段代码的核心思想是:先显示一张轻量的占位符图片,而不是立即加载所有图片。只有当图片即将进入用户的视口时,才动态地加载真实的图片。这样做可以显著减少页面初次加载时的资源消耗,提升用户体验。

8.2 路由懒加载(Route Lazy Loading)

  • 作用:
    路由组件只有在访问对应路由时才加载,减小首屏打包体积,加快页面初始加载速度。
  • Vue Router 实现示例:
import { createRouter, createWebHistory } from 'vue-router'const routes = [{path: '/home',component: () => import('@/views/Home.vue')  // 懒加载},{path: '/about',component: () => import('@/views/About.vue') // 懒加载}
]const router = createRouter({history: createWebHistory(),routes
})export default router

9. 资源压缩和缓存

9.1 资源压缩

  • JS/CSS 压缩
    使用工具(如 Vite 默认集成的 Terser)去除空格、注释,缩短变量名,减少文件体积,加快加载。
  • 图片压缩
    利用 imageminTinyPNG 等压缩图片体积,同时保证视觉质量。
    推荐使用现代图片格式 WebP,压缩率更高,且兼容性较好。
  • 传输压缩(Gzip / Brotli)
    服务器(如 Nginx)开启压缩功能,传输文本资源时自动压缩,浏览器端自动解压,节省传输带宽。
http {gzip on;gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;gzip_min_length 256;
}

9.2 缓存机制:强缓存与协商缓存

缓存类型 相关 Headers 工作流程 优点 缺点
强缓存 Expires(HTTP/1.0),Cache-Control: max-age(HTTP/1.1) 浏览器直接使用缓存,过期前不请求服务器 速度最快,无请求 资源变更需等缓存过期
协商缓存 Last-Modified / If-Modified-SinceETag / If-None-Match 浏览器带条件请求,服务器判断资源是否变更,未变则返回 304 保证资源最新,节省流量 仍需一次请求开销

9.3 强缓存示例说明

服务器响应:

Cache-Control: max-age=3600
  • 说明服务器告知浏览器:这个资源可以缓存,且缓存时间为 3600 秒(1小时)。
  • 浏览器拿到这个响应后,会将资源和这个缓存时间一起存储。
  • 在这1小时内,浏览器再次请求该资源时,不会向服务器发送请求,直接从本地缓存读取,速度最快。

9.4 协商缓存示例说明

首次服务器响应:

Last-Modified: Tue, 01 Aug 2025 10:00:00 GMT
ETag: "12345abcde"
  • Last-Modified:表示该资源最后修改时间,服务器告诉浏览器资源创建或修改的时间。
  • ETag:是资源的唯一标识符(类似指纹),对比文件内容是否变动更准确。

浏览器再次请求时带的请求头:

If-Modified-Since: Tue, 01 Aug 2025 10:00:00 GMT
If-None-Match: "12345abcde"
  • If-Modified-Since 会让服务器比对资源的最后修改时间。
  • If-None-Match 会让服务器比对资源的 ETag 标识。
  • 两者任何一个判断资源未变,服务器就返回 304 Not Modified,浏览器继续用缓存。

服务器返回 304 响应:

HTTP/1.1 304 Not Modified
  • 资源未变,响应无正文,浏览器直接使用缓存内容。

如果资源变了,服务器返回新的内容和新的头:

HTTP/1.1 200 OK
Last-Modified: Wed, 02 Aug 2025 08:00:00 GMT
ETag: "67890fghij"[新的资源内容]
  • 服务器告知浏览器资源已经变更,浏览器更新缓存内容。

9.5 总结

  • 强缓存适合资源短期不变,直接读取缓存无请求,速度最快。
  • 协商缓存确保资源最新,但每次至少发一次请求,节省带宽。

10. Sass/Less:解决了原生 CSS 的哪些痛点?

痛点1:无变量

  • 问题:原生 CSS 不能定义变量,颜色、尺寸等多处写重复,修改麻烦。
  • 解决:Sass/Less 支持变量,统一管理,修改方便。
// Sass 变量
$primary-color: #3498db;.button {background-color: $primary-color;
}

痛点2:代码重复

  • 问题:相同样式多处写,维护困难。
  • 解决:支持 Mixin 混入,封装复用。
@mixin center {display: flex;justify-content: center;align-items: center;
}.box {@include center;height: 100px;
}

痛点3:结构不清晰

  • 问题:CSS 选择器写法平铺,层级关系不明显,难维护。
  • 解决:支持嵌套写法,层级结构更清晰。
.nav {ul {margin: 0;padding: 0;li {list-style: none;a {color: blue;}}}
}

痛点4:缺乏模块化

  • 问题:原生 CSS 文件庞大,难拆分和管理。
  • 解决:支持 @import / @use 拆分文件,方便维护。
特性 @use @import
命名空间 (默认是文件名) 没有(全局暴露)
访问方式 namespace.variable variable
私有成员 支持(下划线开头) 不支持
复用 只编译一次 可能导致重复代码
推荐状态 Sass 新版本推荐使用 已过时,将逐步被废弃
// _variables.scss
$font-stack: Helvetica, sans-serif;// main.scss
@use 'variables';body {font-family: variables.$font-stack;
}

痛点5:逻辑运算能力弱

  • 问题:原生 CSS 无法做条件、循环等逻辑处理。
  • 解决:Sass/Less 支持条件判断、循环等逻辑。
@for $i from 1 through 3 {.col-#{$i} {width: 100% / 3 * $i;}
}

11. mixin, extend,placeholder 的区别是什么?

特性 @mixin(混入) @extend(继承) %placeholder(占位符选择器)
原理 复制粘贴样式代码 合并选择器,多个类共享同一套样式 类似 @extend,但不会单独生成样式,避免冗余
生成的 CSS 每个调用都会复制一份代码,可能冗余 生成合并选择器,避免重复样式 只有被 @extend 调用时才生成对应 CSS
适用场景 需要动态传参,生成多种变体样式 简单共享样式,减少重复,无法传参 写基础样式模板,只有被继承时才生效,避免无用代码
缺点 可能生成冗余 CSS,增大文件体积 选择器变复杂,增加 CSS 权重,可能导致样式冲突 只能配合 @extend 使用,不能单独调用

1. @mixin(混入)

@mixin btn($color) {padding: 10px 20px;background-color: $color;border-radius: 4px;color: white;
}.btn-primary {@include btn(blue);
}.btn-danger {@include btn(red);
}

结果:

.btn-primary {padding: 10px 20px;background-color: blue;border-radius: 4px;color: white;
}
.btn-danger {padding: 10px 20px;background-color: red;border-radius: 4px;color: white;
}

会复制相同样式,文件体积增大。


2. @extend(继承)

.btn-base {padding: 10px 20px;border-radius: 4px;color: white;
}.btn-primary {@extend .btn-base;background-color: blue;
}.btn-danger {@extend .btn-base;background-color: red;
}

结果:

.btn-base, .btn-primary, .btn-danger {padding: 10px 20px;border-radius: 4px;color: white;
}
.btn-primary {background-color: blue;
}
.btn-danger {background-color: red;
}

样式合并,减少重复。


3. %placeholder(占位符)

%btn-base {padding: 10px 20px;border-radius: 4px;color: white;
}.btn-primary {@extend %btn-base;background-color: blue;
}.btn-danger {@extend %btn-base;background-color: red;
}

结果:

.btn-primary, .btn-danger {padding: 10px 20px;border-radius: 4px;color: white;
}
.btn-primary {background-color: blue;
}
.btn-danger {background-color: red;
}

区别是: %btn-base 本身不会单独生成 .btn-base 样式,避免无用代码。

总结

  • 用需要动态参数和多变样式时用 @mixin
  • 需要多个选择器共享基础样式,且不传参数时用 @extend
  • 想写基础样式模板,但不想生成冗余类时用 %placeholder

12. ECharts:复杂图表实现案例

场景说明

实现一个展示服务器实时 CPU 占用率的动态折线图,数据来源模拟后端实时推送或定时轮询。


核心流程

  1. 初始化图表
    使用 echarts.init 初始化图表实例,并设置好初始的 option,其中数据数组可先设为空或初始值。
  2. 动态数据更新
    实时从后端(WebSocket、轮询等)获取数据后,更新图表中的数据。通过调用 setOption 只传递变化部分,实现高效更新。
  3. 性能优化技巧
    • 关闭动画 (animation: false) 减少渲染开销。
    • 使用 appendData 进行增量渲染(大数据场景)。
    • 使用 dataZoom 组件支持缩放,避免一次渲染过多数据点。
    • 数据抽样减少点数。
<script setup>
import * as echarts from 'echarts'
import { ref, onMounted, onBeforeUnmount } from 'vue'// 图表容器的 ref
const chartRef = ref(null)
let chartInstance = null// 模拟初始数据,50个数据点
const dataCount = 50
const data = Array(dataCount).fill(0).map(() => Math.random() * 50 + 10)// 初始配置
function getOption() {return {title: {text: '服务器 CPU 占用率(动态)',left: 'center'},tooltip: {trigger: 'axis'},xAxis: {type: 'category',data: Array(dataCount).fill('').map((_, i) => i.toString()),boundaryGap: false},yAxis: {type: 'value',min: 0,max: 100},series: [{name: 'CPU %',type: 'line',data,smooth: true,animation: false // 关闭动画,提升性能}],dataZoom: [{type: 'slider', // 支持拖动缩放start: 0,end: 100}]}
}// 模拟动态数据更新
function updateData() {// 移除最旧的数据,添加最新的数据data.shift()data.push(Math.random() * 50 + 10)// 只更新 series 的 data 部分,提升性能chartInstance.setOption({series: [{ data }]})
}onMounted(() => {// 初始化图表实例chartInstance = echarts.init(chartRef.value)chartInstance.setOption(getOption())// 模拟每秒更新数据const timer = setInterval(updateData, 1000)onBeforeUnmount(() => {clearInterval(timer)  // 清除定时器chartInstance.dispose() // 销毁图表实例,避免内存泄漏})
})
</script><template><div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template><style scoped>
/* 容器大小决定图表大小 */
</style>

13. React 常用 Hooks 详解及示例

1. useState

  • 作用:在函数组件中声明状态变量,返回当前状态和更新函数。

  • 用法示例

    import React, { useState } from 'react';function Counter() {const [count, setCount] = useState(0); // 初始值为0return (<div><p>当前计数:{count}</p><button onClick={() => setCount(count + 1)}>增加</button></div>);
    }
    

2. useEffect

  • 作用:处理副作用,比如数据请求、事件监听、订阅等。
  • 依赖项数组说明
依赖项 执行时机
不传(无第二参) 每次组件渲染后都会执行回调
[](空数组) 只在组件首次渲染后执行,相当于 componentDidMount
[a, b] 首次渲染后执行,且依赖 ab 变化时重新执行
  • 示例:组件首次加载时请求数据

    import React, { useState, useEffect } from 'react';function UserList() {const [users, setUsers] = useState([]);useEffect(() => {fetch('/api/users').then(res => res.json()).then(data => setUsers(data));}, []); // 只执行一次return (<ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>);
    }
    

3. useContext

  • 作用:获取 Context 的值,避免多层组件传递 props。

  • 示例

    import React, { createContext, useContext } from 'react';const ThemeContext = createContext('light');function Toolbar() {const theme = useContext(ThemeContext);return <div>当前主题:{theme}</div>;
    }function App() {return (<ThemeContext.Provider value="dark"><Toolbar /></ThemeContext.Provider>);
    }
    

4. useMemo

  • 作用:缓存计算结果,只有依赖变化时重新计算,提升性能。

  • 示例

    import React, { useMemo, useState } from 'react';function Fibonacci({ n }) {const fib = useMemo(() => {function calcFib(num) {if (num <= 1) return num;return calcFib(num - 1) + calcFib(num - 2);}return calcFib(n);}, [n]);return <div>斐波那契数列第 {n} 项是 {fib}</div>;
    }
    

5. useCallback

  • 作用:缓存函数实例,避免子组件无谓重渲染。

  • 示例

    import React, { useState, useCallback } from 'react';function Child({ onClick }) {console.log('Child渲染');return <button onClick={onClick}>点击</button>;
    }function Parent() {const [count, setCount] = useState(0);// 缓存函数实例,只在 count 变化时更新const handleClick = useCallback(() => {setCount(c => c + 1);}, []);return (<div><p>计数:{count}</p><Child onClick={handleClick} /></div>);
    }
    

14. 函数组件 vs 类组件

1. 主要差异

特性 函数组件 类组件
语法 纯函数,写法简洁 ES6 Class,需要继承 React.Component
State 管理 使用 Hook,如 useState 使用 this.statethis.setState
生命周期 使用 Hook,如 useEffect 模拟生命周期 有明确生命周期方法,如 componentDidMount, componentDidUpdate
this 指向 无需关心 this 需要显式绑定 this,常见错误点

2. 函数组件示例

import React, { useState, useEffect } from 'react';function Counter() {const [count, setCount] = useState(0);useEffect(() => {console.log('组件挂载或 count 更新了,当前 count:', count);}, [count]);return (<div><p>计数: {count}</p><button onClick={() => setCount(count + 1)}>增加</button></div>);
}

3. 类组件示例

import React from 'react';class Counter extends React.Component {constructor(props) {super(props);this.state = { count: 0 };this.handleClick = this.handleClick.bind(this);}componentDidMount() {console.log('组件挂载完成');}componentDidUpdate(prevProps, prevState) {if (prevState.count !== this.state.count) {console.log('count 更新了,当前 count:', this.state.count);}}handleClick() {this.setState({ count: this.state.count + 1 });}render() {return (<div><p>计数: {this.state.count}</p><button onClick={this.handleClick}>增加</button></div>);}
}

4. 函数组件优势总结

  • 代码更简洁、逻辑更集中: 组件的状态和副作用逻辑都能写在一起,避免分散在多个生命周期方法中。
  • 更方便复用状态逻辑: 通过自定义 Hook(useXxx)实现状态逻辑的封装和复用。
  • 避免 this 指向错误: 函数组件中不存在 this,避免了类组件中常见的绑定问题。
  • 符合 React 未来发展方向: React 官方推荐函数组件 + Hook,未来新特性更多面向函数组件。
http://www.sczhlp.com/news/9178/

相关文章:

  • 升级openssh以及openssl
  • 《唐雎不辱使命》
  • 防止C语言头文件被重复包含 — ifndef #pragma once
  • 大型动作模型LAM:让企业重复任务实现80%效率提升的AI技术架构与实现方案
  • 通过移民局小程序下载出入境记录文件的详细流程
  • EXPLAIN和PROFILE
  • 炒饭新至
  • 8月10号
  • 2025年8月10日
  • Atcoder 和 Codeforces 入门指南
  • 4-2 排序算法 O(nlgn)
  • wqs二分
  • GPT-5技术解析:多版本模型与软件生成能力
  • 3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot
  • 我的博客园的背景图片
  • 2025/8/10 总结
  • 【NAOI】套圈游戏
  • 【总结】重链剖分
  • 【做题记录】线段树(马思博)
  • 智能体技能——工作流简单介绍
  • 第二十七篇
  • 下载 ubuntu24 arm64
  • 8月10日随笔
  • 2025.7.26 CSP-S模拟赛26
  • DBeaver执行Sql文件报错SQL Error [1406] [22001] Data truncation: Data too long for column xxx at row 1
  • 2025.7.24 CSP-S模拟赛25
  • js的实例,原型和类成员
  • 深入解析:UE5 图片9宫格切割
  • 小屏幕大影响:为功能手机开发Web应用的被遗忘艺术
  • NextAuth.js v5迁移指南与实战示例