[{"data":1,"prerenderedAt":368},["ShallowReactive",2],{"article-typescript-component-props":3,"related-articles":267},{"id":4,"title":5,"body":6,"categoryLabel":254,"categorySlug":255,"description":256,"extension":257,"featured":105,"meta":258,"navigation":105,"path":259,"publishedAt":260,"readingMinutes":96,"seo":261,"stem":262,"tags":263,"__hash__":266},"blog\u002Fblog\u002Ftypescript-component-props.md","给 React 组件的 Props 添加 TypeScript 类型",{"type":7,"value":8,"toc":249},"minimark",[9,13,17,221,232,236,239,242,245],[10,11,12],"p",{},"给组件 Props 添加类型后，编辑器可以提示必填字段，也能在传错数据时尽早发现问题。",[14,15,16],"h2",{"id":16},"声明组件需要的数据",[18,19,24],"pre",{"className":20,"code":21,"language":22,"meta":23,"style":23},"language-tsx shiki shiki-themes github-light-high-contrast","type NoticeProps = {\n  title: string\n  description: string\n  tone?: 'info' | 'success' | 'warning'\n}\n\nfunction Notice({ title, description, tone = 'info' }: NoticeProps) {\n  return \u003Csection data-tone={tone}>\u003Ch2>{title}\u003C\u002Fh2>\u003Cp>{description}\u003C\u002Fp>\u003C\u002Fsection>\n}\n","tsx","",[25,26,27,47,60,70,94,100,107,148,216],"code",{"__ignoreMap":23},[28,29,32,36,40,43],"span",{"class":30,"line":31},"line",1,[28,33,35],{"class":34},"sTJeM","type",[28,37,39],{"class":38},"sV4o_"," NoticeProps",[28,41,42],{"class":34}," =",[28,44,46],{"class":45},"sigWx"," {\n",[28,48,50,53,56],{"class":30,"line":49},2,[28,51,52],{"class":38},"  title",[28,54,55],{"class":34},":",[28,57,59],{"class":58},"sacAq"," string\n",[28,61,63,66,68],{"class":30,"line":62},3,[28,64,65],{"class":38},"  description",[28,67,55],{"class":34},[28,69,59],{"class":58},[28,71,73,76,79,83,86,89,91],{"class":30,"line":72},4,[28,74,75],{"class":38},"  tone",[28,77,78],{"class":34},"?:",[28,80,82],{"class":81},"sYEJz"," 'info'",[28,84,85],{"class":34}," |",[28,87,88],{"class":81}," 'success'",[28,90,85],{"class":34},[28,92,93],{"class":81}," 'warning'\n",[28,95,97],{"class":30,"line":96},5,[28,98,99],{"class":45},"}\n",[28,101,103],{"class":30,"line":102},6,[28,104,106],{"emptyLinePlaceholder":105},true,"\n",[28,108,110,113,117,120,123,126,129,131,134,136,138,141,143,145],{"class":30,"line":109},7,[28,111,112],{"class":34},"function",[28,114,116],{"class":115},"s3dhs"," Notice",[28,118,119],{"class":45},"({ ",[28,121,122],{"class":38},"title",[28,124,125],{"class":45},", ",[28,127,128],{"class":38},"description",[28,130,125],{"class":45},[28,132,133],{"class":38},"tone",[28,135,42],{"class":34},[28,137,82],{"class":81},[28,139,140],{"class":45}," }",[28,142,55],{"class":34},[28,144,39],{"class":38},[28,146,147],{"class":45},") {\n",[28,149,151,154,157,161,164,167,169,172,175,177,180,183,185,187,190,192,194,196,198,200,202,204,206,208,211,213],{"class":30,"line":150},8,[28,152,153],{"class":34},"  return",[28,155,156],{"class":45}," \u003C",[28,158,160],{"class":159},"s742_","section",[28,162,163],{"class":58}," data-tone",[28,165,166],{"class":34},"={",[28,168,133],{"class":45},[28,170,171],{"class":34},"}",[28,173,174],{"class":45},">\u003C",[28,176,14],{"class":159},[28,178,179],{"class":45},">",[28,181,182],{"class":34},"{",[28,184,122],{"class":45},[28,186,171],{"class":34},[28,188,189],{"class":45},"\u003C\u002F",[28,191,14],{"class":159},[28,193,174],{"class":45},[28,195,10],{"class":159},[28,197,179],{"class":45},[28,199,182],{"class":34},[28,201,128],{"class":45},[28,203,171],{"class":34},[28,205,189],{"class":45},[28,207,10],{"class":159},[28,209,210],{"class":45},">\u003C\u002F",[28,212,160],{"class":159},[28,214,215],{"class":45},">\n",[28,217,219],{"class":30,"line":218},9,[28,220,99],{"class":45},[10,222,223,225,226,228,229,231],{},[25,224,122],{}," 和 ",[25,227,128],{}," 是必填项；",[25,230,133],{}," 后面的问号表示可选，并在解构时提供默认值。",[14,233,235],{"id":234},"选择-type-还是-interface","选择 type 还是 interface",[10,237,238],{},"描述对象形状时，两者都能完成常见任务。团队已有约定时跟随约定即可，重点是让组件接口容易读、容易更新。",[14,240,241],{"id":241},"小结",[10,243,244],{},"先把真正需要的 Props 写出来，避免一开始为所有可能性增加复杂类型。随着组件复用范围扩大，再补充变体或泛型也不迟。",[246,247,248],"style",{},"html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html pre.shiki code .s742_, html code.shiki .s742_{--shiki-default:#024C1A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":23,"searchDepth":49,"depth":49,"links":250},[251,252,253],{"id":16,"depth":49,"text":16},{"id":234,"depth":49,"text":235},{"id":241,"depth":49,"text":241},"TypeScript","typescript","用必填属性和可选属性描述组件接口，让使用方式在编辑器里更清晰。","md",{},"\u002Fblog\u002Ftypescript-component-props","2026-09-02",{"title":5,"description":256},"blog\u002Ftypescript-component-props",[254,264,265],"React","类型","cwHdbRfbPc5pV4sE6ur0GGrjPsEHwWwp3aomaPoM8ag",[268,280,290,300,308,316,324,332,340,342,350,358],{"id":269,"path":270,"title":271,"description":272,"categorySlug":273,"categoryLabel":274,"publishedAt":275,"readingMinutes":72,"featured":105,"tags":276},"blog\u002Fblog\u002Fcss-grid-cards.md","\u002Fblog\u002Fcss-grid-cards","用 CSS Grid 排列自适应卡片","使用 repeat、minmax 和 auto-fit，让卡片列数随容器宽度自然变化。","styles","页面与样式","2026-09-10",[277,278,279],"CSS","Grid","页面布局",{"id":281,"path":282,"title":283,"description":284,"categorySlug":273,"categoryLabel":274,"publishedAt":285,"readingMinutes":96,"featured":286,"tags":287},"blog\u002Fblog\u002Fmobile-layout-checklist.md","\u002Fblog\u002Fmobile-layout-checklist","做移动端页面适配时的检查笔记","从内容宽度、触控区域、文字换行和键盘焦点几个方面检查窄屏体验。","2026-08-20",false,[277,288,289],"响应式","可访问性",{"id":291,"path":292,"title":293,"description":294,"categorySlug":295,"categoryLabel":264,"publishedAt":296,"readingMinutes":102,"featured":105,"tags":297},"blog\u002Fblog\u002Freact-controlled-form.md","\u002Fblog\u002Freact-controlled-form","React 受控表单：从一个输入框开始","用一个简单的姓名输入框，理解 value、onChange 与 React state 之间的关系。","react","2026-09-18",[264,298,299],"表单","学习笔记",{"id":301,"path":302,"title":303,"description":304,"categorySlug":295,"categoryLabel":264,"publishedAt":305,"readingMinutes":72,"featured":286,"tags":306},"blog\u002Fblog\u002Freact-list-keys.md","\u002Fblog\u002Freact-list-keys","React 列表渲染里的 key 有什么用","通过稳定的 key 帮助 React 识别列表项目，避免用数组下标造成的状态错位。","2026-08-12",[264,307,299],"列表",{"id":309,"path":310,"title":311,"description":312,"categorySlug":295,"categoryLabel":264,"publishedAt":313,"readingMinutes":96,"featured":286,"tags":314},"blog\u002Fblog\u002Freact-props-and-state.md","\u002Fblog\u002Freact-props-and-state","React 组件里的 Props 和 State","用一个简单的信息卡片区分外部传入的数据和组件内部会变化的数据。","2026-08-28",[264,315,299],"组件",{"id":317,"path":318,"title":319,"description":320,"categorySlug":295,"categoryLabel":264,"publishedAt":321,"readingMinutes":96,"featured":286,"tags":322},"blog\u002Fblog\u002Freact-small-components.md","\u002Fblog\u002Freact-small-components","把重复的界面拆成小组件","从重复的按钮和卡片开始，认识组件拆分的几个简单信号。","2026-07-16",[264,315,323],"练习记录",{"id":325,"path":326,"title":327,"description":328,"categorySlug":295,"categoryLabel":264,"publishedAt":329,"readingMinutes":102,"featured":286,"tags":330},"blog\u002Fblog\u002Freact-use-effect-notes.md","\u002Fblog\u002Freact-use-effect-notes","React useEffect 的依赖和清理","整理 useEffect 何时运行，以及订阅和定时器为什么需要清理。","2026-06-30",[264,331,299],"useEffect",{"id":333,"path":334,"title":335,"description":336,"categorySlug":295,"categoryLabel":264,"publishedAt":337,"readingMinutes":72,"featured":286,"tags":338},"blog\u002Fblog\u002Freact-use-state-practice.md","\u002Fblog\u002Freact-use-state-practice","用 useState 管理一个简单的计数交互","从加一和重置两个操作，熟悉 React state 的声明与更新方式。","2026-07-30",[264,339,323],"useState",{"id":4,"path":259,"title":5,"description":256,"categorySlug":255,"categoryLabel":254,"publishedAt":260,"readingMinutes":96,"featured":105,"tags":341},[254,264,265],{"id":343,"path":344,"title":345,"description":346,"categorySlug":255,"categoryLabel":254,"publishedAt":347,"readingMinutes":96,"featured":105,"tags":348},"blog\u002Fblog\u002Ftypescript-narrowing.md","\u002Fblog\u002Ftypescript-narrowing","TypeScript 类型收窄入门","使用 typeof 和条件判断缩小联合类型的范围，让后续代码更安全。","2026-09-25",[254,349,299],"类型收窄",{"id":351,"path":352,"title":353,"description":354,"categorySlug":255,"categoryLabel":254,"publishedAt":355,"readingMinutes":72,"featured":286,"tags":356},"blog\u002Fblog\u002Ftypescript-union-status.md","\u002Fblog\u002Ftypescript-union-status","用联合类型描述请求状态","把加载、成功和失败等状态限制在明确的集合里，减少拼写错误和遗漏分支。","2026-08-04",[254,357,299],"联合类型",{"id":359,"path":360,"title":361,"description":362,"categorySlug":363,"categoryLabel":364,"publishedAt":365,"readingMinutes":72,"featured":286,"tags":366},"blog\u002Fblog\u002Fvite-react-project.md","\u002Fblog\u002Fvite-react-project","用 Vite 创建一个 React + TypeScript 小项目","记录从初始化模板到启动开发服务器的基础步骤，以及几个值得先检查的文件。","tools","开发工具","2026-06-12",[367,264,254],"Vite",1792674168680]