写 Vue3 组件这件事很多人以为自己已经会了——毕竟从 Options API 迁过来模板照写script setup一贴页面能跑起来就算完事。真正让我改变看法的是一次线上事故一个看似无害的列表组件因为 props 类型没写严谨父级传了个字符串进来子组件内部做了数值运算结果10 1变成了101分页参数一路错到后端接口才被发现。那次之后我重新梳理了一遍 Vue3 组件的写法从 props 契约、emits 约束、插槽设计到 composable 拆分和渲染性能一条条过。这篇内容就是我整理下来的实践记录,面向的是已经写过 Vue3 组件、但想把它写得更稳更可复用的同学也照顾刚上手的新人——只要你写过ref、绑定过v-model就能顺着往下读。我不会讲太多框架 API 的罗列重点放在“为什么这么设计”和“踩过什么坑”上。1. 先想清楚组件到底承担了什么1.1 一个组件不是“一段模板”而是一份契约新手容易把组件理解成“把 HTML 抽出来复用”。抽出模板只是第一步真正决定组件能用多久的是它对外暴露的接口接收哪些 props、发出哪些事件、暴露哪些插槽、有没有对外方法。这四样东西一旦定下来就相当于给组件签了一份合同。父组件按合同调用子组件按合同履约中间的实现细节随便改都不影响调用方。我见过不少项目组件内部直接去读写父级传入的对象甚至把 props 当本地状态改。短期看代码少写了几行长期看就是灾难父组件不知道子组件什么时候改了数据调试时断点都不知道往哪打。Vue 的单向数据流设计不是限制而是把“谁改数据”这件事的唯一性守住了。子组件需要改父级数据时正确做法是emit一个意图明确的事件让父级自己去改。这个动作多写一行代码换来的是数据流向可追踪。举个具体对比。下面这种写法是典型的反例// 反例子组件直接改 props const props defineProps({ user: Object }) function updateName(name) { props.user.name name // 能跑但数据流向不可控 }Vue 在开发环境下对 props 对象做了浅层只读代理改props.user这种嵌套属性不会报警告因为只读代理只到第一层。这种“能跑但危险”的写法最坑人因为它在开发阶段一切正常等到多人协作、组件嵌套三层以上时问题才暴露出来。正确姿势是定义一个事件// 正例把修改意图抛给父级 const props defineProps({ user: { type: Object, required: true } }) const emit defineEmits([update:user]) function updateName(name) { emit(update:user, { ...props.user, name }) }用展开运算符创建一个新对象父级收到的是不可变引用触发响应式更新更可控也避免了同一对象被多个组件同时持有的引用污染。1.2 判断组件该不该拆的三条经验线组件拆得太细文件数量爆炸改一个功能要跳五六个文件拆得太粗单个文件上千行谁都不敢动。我一般用三条线来判断行数线单文件超过 300 行不含样式就考虑拆超过 500 行基本一定要拆。变更频率线如果一段 UI 的改动和另一段几乎不一起发生它们就该分开。改动频率不同的代码通常是不同职责。状态边界线如果一块区域有自己的独立状态且这个状态不需要被外部频繁读写它就是一个天然组件。反过来有三种情况我会刻意不拆一是纯展示的几行 HTML拆出去反而增加跳转成本二是强耦合的表单联动拆开后父子要互相传一堆状态不如留在一个文件里三是还在探索阶段的界面需求没定型就拆拆完还得合并回去。这里有个容易被忽略的点组件的“粒度”不是按视觉区块分的是按数据边界分的。视觉上分三块但三块共享同一份筛选状态那它们本质上是一个组件视觉上是一整块表格但每一列的数据来源和格式化规则完全独立那它内部其实可以拆出列组件用作用域插槽向外解析。1.3script setup之后组件写法发生了哪些实质变化script setup不是一个语法糖那么简单它改变了组件内部的代码组织结构顶层变量自动暴露给模板不需要returnprops 和 emits 用编译器宏声明编译期就能做静态分析defineExpose显式声明对外暴露的方法默认全封闭。这带来两个实际好处。第一是类型推导更自然用 TypeScript 写的时候defineProps{ list: Item[] }()直接得到完整的类型提示不需要再写一套运行时校验。第二是编译产物更小因为编译器知道你用了哪些变量没被模板引用的顶层变量在编译时可以直接被优化掉。但也有代价script setup的顶层代码相当于setup()函数体每次组件实例化都会执行一遍。如果你在里面写了耗时计算它会跟着组件一起跑。所以我在顶层只放两类东西响应式状态的定义和不需要重复执行的常量。重活要么放进computed有缓存要么放进生命周期钩子里。2. props 与 emits把接口定义得让调用方不用看文档2.1 props 定义的三个关键点props 是组件最外层的输入面定义得好不好直接影响调用方的使用体验和调试成本。我的习惯是三件事必须做全类型、必填、默认值。const props defineProps({ list: { type: Array, required: true }, pageSize: { type: Number, default: 20 }, loading: { type: Boolean, default: false }, rowKey: { type: String, default: id }, emptyText: { type: String, default: 暂无数据 }, options: { type: Object, default: () ({}) } })第一点对象和数组的默认值必须用工厂函数。这是老生常谈但确实有人踩。默认值写成default: {}时这个对象在模块加载时就被创建了一次所有组件实例共享同一个引用。一个实例改了这个对象其他实例跟着变。写成default: () ({})每次实例化都拿一个新对象。第二点Boolean 类型的默认值要小心。Vue 对 Boolean 类型的 props 有特殊处理如果你不写default它会被默认成false而模板中传入disabled这种不带值的 attribute 时会被解析成true。这个行为和原生 HTML 一致但如果你想让 Boolean prop 在未传时表现为“未设置”而不是false就得用default: undefined或改成三态设计。第三点Number 类型的坑比想象中多。模板里写:page-size20是对的写page-size20拿到的是字符串20。Vue 在开发环境下会对类型不匹配发警告但生产环境警告会被构建工具裁掉。我在项目里通常会加一层兜底数值型 props 在内部用computed转一次保证类型稳定。const safePageSize computed(() { const n Number(props.pageSize) return Number.isFinite(n) n 0 ? Math.floor(n) : 20 })这种兜底看着啰嗦但省掉的是一整类“为什么我传了 10 却按 100 处理”的排查时间。2.2 v-model 从“一个 prop 一个事件”到defineModelv-model在 Vue 3 里本质上是:modelValue加update:modelValue的语法糖。如果你要做一个自定义输入组件最经典的写法是这样const props defineProps({ modelValue: { type: String, default: } }) const emit defineEmits([update:modelValue]) const inner computed({ get: () props.modelValue, set: (val) emit(update:modelValue, val) })这套写法用了很多年好处是兼容性好坏处是每次都要重复写computed的 get/set。Vue 3.4 之后提供了defineModel宏可以直接声明一个可读写的模型引用const model defineModel({ type: String, default: }) // 模板里直接 v-modelmodeldefineModel的好处不只是少写代码更在于它把“受控”和“非受控”的切换做在了框架内部。当你传了v-model它就是受控的不传它就是本地状态。这个细节在手写computed方案里要自己处理很容易漏。多模型场景也很常见比如一个表单区块要同时同步value和visibleconst value defineModel(value) const visible defineModel(visible, { type: Boolean, default: false })父级调用时写v-model:value和v-model:visible即可。需要注意的是自定义的 v-model 名称最好用语义化的短词不要写成v-model:dataValue这种一看就知道是随手起的名字调用方每次都要回想一遍。2.3 emits 声明不是可选项很多项目里defineEmits是省掉的因为不写也能emit。但省掉之后丢失的是三样东西事件名的统一管理、事件参数的约束、以及 IDE 的自动补全。我遇到过的最典型的混乱场景是一个组件同时发出change、onChange、update三个语义重叠的事件分别在不同分支触发调用方根本不知道监听哪个。这类问题在多人协作里极难靠 review 抓出来因为它不报错只是让人困惑。const emit defineEmits({ // 带校验的写法参数不合法时开发环境会警告 update:selected: (keys) Array.isArray(keys), row-click: (row, index) row ! null index 0, sort-change: (payload) { return payload [asc, desc, null].includes(payload.order) } })命名上我遵循两条规则一是动作类事件用动词短语submit、refresh、load-more二是数据更新类事件统一用update:xxx格式因为 Vue 的 v-model 机制依赖这个前缀。事件名在脚本里用小驼峰在模板里用短横线这是 Vue 3 的官方约定混用虽然不会立刻出错但自动补全和全局事件校验会失效。2.4 插槽设计默认插槽给简单场景作用域插槽给数据需求插槽设计的核心问题是父组件需不需要拿到子组件内部的数据。不需要用普通插槽需要用作用域插槽。// 子组件把行数据和列配置一起抛出去 slot namecell :rowrow :columncolumn :indexindex {{ row[column.key] }} /slot默认内容写在slot标签里父级不填时有兜底。这个细节很多人忽略导致组件在没传插槽时渲染空白。作用域插槽的常见误用是给得太多。我见过一个组件一次性抛出了十几个字段实际上父级只用了其中两个。这种“敞开了给”的做法会导致组件内部状态泄漏过大父级依赖了内部实现组件后续改造就会受制。我的做法是只抛出“父级渲染必须用到的数据”其余全部封在子组件里。3. 逻辑复用什么时候该抽 composable3.1 抽 composable 的判断标准不是“代码重复了”代码重复只是表面现象。我判定是否抽 composable 的标准有三条这段逻辑在两个及以上组件里出现了这段逻辑包含响应式状态或者生命周期副作用这段逻辑的变化原因和组件的其他部分不同。如果只是纯函数计算抽成普通工具函数就够了没必要包一层 composable。composable 的价值在于它能持有ref、能注册清理逻辑纯计算抽进去反而增加了一层不必要的包装。// utils/format.js —— 纯计算抽成普通函数 export function formatAmount(value, digits 2) { if (value null || value ) return - return Number(value).toFixed(digits) } // composables/usePagination.js —— 持有状态抽成 composable import { ref, computed } from vue export function usePagination(fetcher, { pageSize 20 } {}) { const page ref(1) const size ref(pageSize) const total ref(0) const loading ref(false) const list ref([]) async function load() { loading.value true try { const res await fetcher({ page: page.value, size: size.value }) list.value res.items total.value res.total } finally { loading.value false } } function changePage(next) { page.value next return load() } const pageCount computed(() Math.ceil(total.value / size.value) || 1) return { page, size, total, loading, list, pageCount, load, changePage } }这个usePagination返回了一组响应式引用和一个加载方法。调用方在组件里const { list, load } usePagination(api)就拿到了全部能力。3.2 composable 的返回值用对象不用数组有一条实践规范我一直在坚持composable 返回对象不返回数组。原因很直接——对象支持按名解构调用方只取自己需要的数组只能按顺序取加一个返回值就是破坏性变更。另一个细节是返回ref还是返回reactive。我倾向返回ref因为ref可以被整体替换reactive不能。当你需要list.value newArray这种整体赋值时reactive会丢掉响应式。而且ref在模板里自动解包写法和普通变量一样没有额外的心智负担。// 不推荐返回 reactive整体替换会失效 const state reactive({ list: [] }) state.list newList // 这是 OK 的因为改的是属性 // 但如果返回整个 state 让外部替换就会出问题 // 所以内聚地返回 ref 更安全3.3 provide/inject 与 composable 的边界provide/inject 常被拿来和 composable 比较其实它们解决的是不同问题。composable 解决的是逻辑复用provide/inject 解决的是跨层级数据传递。如果一个深层组件需要知道“当前主题”或“当前表单的校验上下文”用 props 一层层传下去会污染中间层组件的接口。这时用 provide/inject中间层完全不用感知。// 祖先组件 import { provide, readonly, ref } from vue const theme ref(light) provide(theme, readonly(theme)) // 只读暴露防止后代误改 provide(setTheme, (val) { theme.value val }) // 后代组件 import { inject } from vue const theme inject(theme, ref(light)) // 第二个参数是默认值 const setTheme inject(setTheme, () {})这里有两个经验点。第一注入 key 用 Symbol 而不是字符串避免不同库之间的命名冲突。第二注入出去的状态用readonly包一层后代只能读不能写写操作走单独注入的函数这样改动来源依然可追踪。4. 性能问题先定位再优化4.1 响应式粒度决定渲染成本Vue 3 的响应式基于 Proxy依赖收集是精确到属性的。这意味着state.a变化不会触发依赖state.b的组件重新渲染。但有一个前提你访问的必须是响应式对象上的属性。常见的性能问题是把一个巨大的列表包成reactive然后每次操作都触发整个列表的依赖更新。更好的做法是列表本身用ref([])整体替换列表项如果需要独立更新再单独处理。// 整体替换触发一次更新 list.value list.value.map(item item.id targetId ? { ...item, done: true } : item )还有一种情况是shallowRef的适用场景。当你有一个大型对象只关心它的整体替换、不关心里面的属性变化时用shallowRef能省掉大量依赖收集开销。比如图表配置对象、地图实例、第三方库的实例对象。import { shallowRef, triggerRef } from vue const chartInstance shallowRef(null) function initChart(el) { chartInstance.value new ChartLib(el, { /* 配置 */ }) } // 如果第三方库内部改了配置对象需要手动触发 function refresh() { triggerRef(chartInstance) }4.2 key 的选择直接决定 DOM 复用是否正确v-for的key用索引几乎是每个 Vue 项目里都会出现的问题。用索引做 key在列表只做展示时看不出问题一旦列表项带内部状态输入框内容、展开状态、动画状态插入或删除元素就会导致状态错位。我遇到过最典型的一次一个可编辑表格用户在第一行输入了内容然后在前面插入一行输入的内容跑到了新插入的行上。原因就是 key 用的是索引Vue 复用了 DOM 节点把状态带了过去。改成:keyrow[rowKey]之后问题消失。选择 key 的原则是用业务上唯一且稳定的标识优先用后端返回的 ID其次用组合键如type code绝对不要用Math.random()——那会导致每次渲染都重建节点性能反而更差。4.3 异步组件与懒加载的时机异步组件不是越多越好。拆得太碎会导致首屏出现大量小的网络请求反而拖慢渲染拆得太少则首屏包体过大。我的划分标准是按“路由 重量级弹窗”两层来拆。路由级用() import(...)天然分包重量级弹窗比如富文本编辑器、图表库、地图用defineAsyncComponent懒加载因为这些依赖往往几百 KB而用户可能一次都不打开。import { defineAsyncComponent } from vue const RichEditor defineAsyncComponent({ loader: () import(./RichEditor.vue), loadingComponent: LoadingSkeleton, delay: 200, // 200ms 后才显示 loading避免闪烁 timeout: 10000 // 超时后展示错误组件 })delay这个参数很多人不写。不写的后果是网络快的时候 loading 闪一下视觉上很脏。设成 200ms 左右快的时候用户完全感知不到加载过程。5. 实操从零做一个可复用表格组件5.1 需求拆解与接口设计假设场景是某后台系统的数据列表页。需求有这几条支持列配置、支持自定义单元格渲染、支持排序、支持加载态和空态、支持行点击。接口设计先定下来再写实现接口类型名称说明propdata表格数据数组必填propcolumns列配置含key、title、width、sortableproprowKey行唯一标识字段名默认idproploading加载态propstripe斑马纹eventrow-click行点击回传行数据与索引eventsort-change排序变化回传列 key 与方向slotcell-[key]按列 key 定制单元格slotheader-[key]按列 key 定制表头slotempty自定义空态用“按列 key 命名插槽”的方式比一次性抛出一个大插槽更好用。调用方只需要为自己关心的列写插槽其他列走默认渲染。5.2 核心实现template div classdata-table :class{ is-stripe: stripe } table thead tr th v-forcol in columns :keycol.key :style{ width: col.width ? col.width px : auto } clickcol.sortable toggleSort(col) slot :nameheader-${col.key} :columncol {{ col.title }} span v-ifcol.sortable classsort-arrow {{ sortState.key col.key ? (sortState.order asc ? ↑ : ↓) : ⇅ }} /span /slot /th /tr /thead tbody tr v-ifloading td :colspancolumns.length classtable-tip加载中.../td /tr tr v-else-if!data.length td :colspancolumns.length classtable-tip slot nameempty暂无数据/slot /td /tr tr v-for(row, index) in data v-else :keyrow[rowKey] clickemit(row-click, row, index) td v-forcol in columns :keycol.key slot :namecell-${col.key} :rowrow :columncol :indexindex {{ formatCell(row[col.key], col) }} /slot /td /tr /tbody /table /div /template配套的脚本部分import { reactive, watch } from vue const props defineProps({ data: { type: Array, required: true }, columns: { type: Array, required: true }, rowKey: { type: String, default: id }, loading: { type: Boolean, default: false }, stripe: { type: Boolean, default: false } }) const emit defineEmits({ row-click: (row, index) row ! null index 0, sort-change: (payload) payload typeof payload.key string }) const sortState reactive({ key: , order: null }) function toggleSort(col) { if (sortState.key ! col.key) { sortState.key col.key sortState.order asc } else { // 三态循环升序 - 降序 - 取消 sortState.order sortState.order asc ? desc : sortState.order desc ? null : asc } emit(sort-change, { key: sortState.key, order: sortState.order }) } function formatCell(value, col) { if (value null || value ) return - if (typeof col.formatter function) return col.formatter(value) return value } // 外部数据全量替换时重置排序避免排序状态与数据不一致 watch(() props.data, (next, prev) { if (next ! prev) { sortState.key sortState.order null } })这段实现里有几个值得说明的取舍。第一排序状态放在组件内部通过事件抛出去由父级决定是否真的对数据排序。这样做的好处是组件保持无状态父级可以接到后端做服务端排序也可以本地排序组件不关心。第二watch里重置排序的逻辑是为了避免“换了数据源但排序箭头还留着”的视觉错乱。第三空态和加载态用同一行占位保证表格高度稳定避免加载完成时页面跳动。5.3 虚拟滚动的参数计算数据量超过 500 行时全量渲染会有明显卡顿。这时需要虚拟滚动。核心思路是只渲染可视区域内的行用上下撑开的占位元素保持滚动条长度。关键参数计算const rowHeight 44 // 单行高度必须固定 const viewportHeight 600 // 可视区高度 const overscan 5 // 上下预留行数防止快速滚动时白屏 // 可视区能容纳的行数 const visibleCount Math.ceil(viewportHeight / rowHeight) // 14 // 滚动时计算的起始索引 function onScroll(scrollTop) { const start Math.floor(scrollTop / rowHeight) const begin Math.max(0, start - overscan) const end Math.min(total.value, start visibleCount overscan) // 渲染 data.slice(begin, end) // 上占位高度 begin * rowHeight // 下占位高度 (total - end) * rowHeight }overscan这个参数的作用是缓冲。不设的话用户快速滚动时会看到一瞬间的空白行因为渲染跟不上滚动。设 5 行左右是比较平衡的值太小容易白屏太大就失去了虚拟滚动的意义。另外要注意虚拟滚动要求行高固定如果你的单元格内容高度不定得改用测量方案复杂度会明显上升我一般建议先约束设计稿保证行高统一。6. 常见问题与排查实录6.1 props 解构后响应式丢失这是 Vue 3 早期版本最容易踩的坑。const { list } defineProps(...)之后list是一个普通值不是响应式的。数据更新时组件不会重新渲染。Vue 3.5 之后defineProps的解构赋值被编译器做了响应式处理可以正常用。但如果项目版本低于 3.5安全写法是toRefs或者直接用props.listimport { toRefs } from vue const props defineProps({ list: Array }) const { list } toRefs(props) // list 是 ref排查方法很简单在模板里同时打印list和$props.list如果前者不更新后者更新就是解构丢响应式。6.2 reactive 对象整体替换const state reactive({ a: 1 }) state { a: 2 } // 错误直接报错或失去响应 Object.assign(state, { a: 2 }) // 可行但只做浅合并reactive返回的是代理重新赋值会切断代理关系。需要整体替换的场景用ref更合适ref.value newObj是标准做法。6.3 组件销毁未清理副作用定时器、事件监听、ResizeObserver、WebSocket 连接这些都必须在onUnmounted里清理。我见过一个组件每次挂载都注册一个window.resize监听切了二十次页面之后一次 resize 触发了二十个回调页面直接卡死。import { onMounted, onUnmounted } from vue let timer null function onResize() { /* ... */ } onMounted(() { window.addEventListener(resize, onResize) timer setInterval(poll, 5000) }) onUnmounted(() { window.removeEventListener(resize, onResize) clearInterval(timer) timer null })如果这段逻辑在多个组件里重复就抽成 composable把清理逻辑封在里面调用方不用记。6.4 常见问题速查表现象可能原因处理方式父传子数据变了但视图不动props 被解构或数据源不是响应式用toRefs或升级到 3.5v-for 列表重排后输入框内容错位key 用了索引改用业务唯一 ID子组件改 props 报只读警告直接赋值 props改为 emit 事件由父级修改组件切走后内存持续增长定时器/监听器未清理onUnmounted中统一释放深层对象修改不触发更新对象不是响应式的或用了shallowRef检查数据来源必要时triggerRef异步组件加载时页面闪烁未设置delay设置 200ms 左右延迟事件监听不生效模板里用了小驼峰事件名模板统一用短横线命名6.5 调试技巧让渲染过程可见大部分人调 Vue 组件靠console.log和 DevTools 瞎点。其实 Vue 提供了两个钩子专门用来定位“为什么这个组件重渲染了”import { onRenderTriggered, onRenderTracked } from vue onRenderTriggered((event) { console.log([触发渲染], event.key, event.oldValue, -, event.newValue) })onRenderTriggered告诉你哪次依赖变化触发了重渲染onRenderTracked告诉你组件收集了哪些依赖。这两个钩子只在开发环境生效上线前会被构建工具裁掉不影响体积。我用它定位过一次“明明只改了一个字段但整个列表重渲染”的问题最后发现是columns配置在模板里用了内联对象字面量每次渲染都生成新引用。另一个技巧是 Chrome 的 Performance 面板配合 Vue DevTools 的组件树高亮。开启重渲染高亮后被更新的组件会闪一下绿框肉眼就能看出更新范围是否合理。最后分享一个我在实际项目中养成的习惯每写完一个通用组件我会先写三个“最不配合”的调用示例——一个只传必填项一个传满所有可选配置一个所有插槽都用上。这三个例子跑通组件的接口设计基本就没问题了。真正难的是在接口定下来之前忍住不写实现先想清楚调用方会怎么用它。这件事我做得越久越觉得它比写代码本身更花时间但也更值。
企业数字化 ERP 产品动态
相关推荐
手机DeepSeek表格导出全攻略:Word/Excel/PDF三格式转换 1. 手机DeepSeek表格导出全攻略最近在技术社区看到不少人在问:用手机版DeepSeek生成的表格数据该怎么导出?作为一款新兴的AI生产力工具,DeepSeek确实在数据处理方面表现亮眼,但它的导出功能确实藏得有点深。今天我就结合自己这两个… · 2026/11/2 20:09:35
Vue3组件设计实战:组合式API、通信与性能优化 1. 先把Vue3组件这件事重新捋一遍这几年带团队做前端项目,组件库更新了一茬又一茬,框架也换过几轮,但真正让我反复回头研究的,还是Vue3的组件体系。原因很简单:组件是Vue这个框架里最基础、也最容易被写歪的东西。基础… · 2026/11/2 20:09:35
VGG19图像分类迁移学习:冻结策略与验收避坑指南 简介:面向智能计算系统课程实验与深度学习部署入门者,这份实验4-1资料围绕VGG19图像分类任务,提供从卷积网络推理到硬件加速评估的实践素材,适合需要理解模型量化与不同计算平台性能差异的学习者。压缩包共4个文件,由3… · 2026/11/2 20:56:00
22亿Agent算力账本:Token消耗、并发调度与成本优化实战 1. 22亿Agent背后的算力账本:这笔账到底怎么算1.1 从Token消耗倒推算力需求IDC和浪潮信息这份报告里最抓眼球的数字就是22亿Agent。很多人看到这个数字第一反应是"Agent真多",但作为搞基础设施的人,我第一反应是:这22亿… · 2026/11/2 20:56:00
UML用例图实战指南:从基础到工具选型 1. 用例图基础与核心价值解析用例图作为UML(统一建模语言)中最基础也最实用的图表类型,本质上是一种需求可视化工具。它通过"参与者(Actor)-用例(Use Case)-关系(Relationship)"三元结构,将抽象的系统需求转化为直观的图… · 2026/11/2 20:56:00
数字化工厂建模技术解析与应用实践 1. 数字化工厂建模的行业背景与核心价值在工业4.0浪潮席卷全球制造业的今天,数字化工厂建模已成为智能制造落地的关键技术支点。根据国际数据公司(IDC)的调研报告,采用数字化建模技术的制造企业平均生产效率提升达23%,… · 2026/11/2 20:56:00
一枚回形针的力学密码:双环结构、制造工艺与百变用法 一枚放在桌面上的回形针,大概是这世上最容易被无视的工业品之一。我从小到大用过成千上万枚,却从来没认真想过它为什么是两圈半的环、为什么能夹住纸又不硌手、为什么一百多年来没人能推翻它的基础形态。直到前两年整理书房,我一口气买了几百… · 2026/11/2 20:56:00
Weaviate向量数据库:AI时代的非结构化数据处理方案 1. Weaviate向量数据库:当传统数据库遇上AI最近两年,AI应用爆炸式增长,传统数据库在处理非结构化数据时越来越力不从心。作为一名长期从事数据架构的工程师,我亲历了从关系型数据库到NoSQL,再到如今向量数据库的技术演… · 2026/11/2 20:54:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18