从零开始学前端 | 第二十八章:Props 与组件复用 本章定位上一章我们已经正式迈进了 React 的基础编码阶段。你已经学会了这些内容JSX 是什么。JSX 为什么看起来像 HTML但本质上是 JavaScript 语法扩展。函数组件最基础的写法。页面如何由多个组件组合而成。这一步非常重要。因为你已经不再只是“知道 React 很流行”而是真的开始能读懂和写出 React 代码了。但学到这里很快就会遇到一个新的问题组件虽然已经能拆出来了可它们怎么接收不同的数据比如你已经能写出这样的组件function WelcomeCard() { return h2欢迎学习 React/h2; }这当然没有问题。但如果你想让这张卡片有时显示“欢迎学习 React”有时显示“欢迎学习 TypeScript”有时显示“欢迎学习 Next.js”那你很快就会发现如果组件里的内容全是写死的它就很难复用。这就是这一章要解决的核心问题。我们要开始学习 React 里非常关键的一层能力让组件接收外部数据从“写死的组件”变成“可配置的组件”。这层能力正是通过props来实现的。所以你可以把这一章理解成从“会写组件”走到“让组件真正具备复用价值”。本章学习目标学完这一章后你应该能做到理解什么是props。知道为什么props是组件复用的基础。掌握父组件向子组件传值的最基础方式。学会在子组件中读取和使用props。理解props本质上就是一个对象。学会使用更常见的解构写法读取props。知道props可以传递哪些常见类型的数据。学会把“写死内容的组件”改成“可配置组件”。建立组件设计时“先满足当前场景再逐步复用”的意识。为下一章学习State、事件与受控组件做好准备。一、从“会写组件”到“让组件可配置”上一章我们已经知道React 页面是由多个组件组合起来的。这句话很重要。但如果你继续往下写很快会发现只有“拆出来”还不够组件还得“用得起来”。例如你写了一个课程卡片组件function CourseCard() { return ( section h3React 入门/h3 p适合前端初学者的基础课程。/p /section ); }这段代码本身没问题。但问题很快就来了。如果页面里有三张课程卡片React 入门TypeScript 基础Next.js 上手你是继续复制三份组件代码吗如果这样做马上就会出现两个问题结构明明一样却要重复写很多份。以后只要卡片结构改了就要到处同步修改。所以从这一章开始你要建立的新意识是组件不只是为了拆分结构更是为了复用结构。而props就是通往这一步的核心入口。二、什么是props先给一个当前阶段最够用的理解props是父组件传给子组件的数据。这个定义你一定要先记住。因为后面很多 React 内容都会围绕这句话展开。1.props最直观的理解是什么你可以先把它理解成给组件传参数。这其实和你前面学过的函数非常像。例如你已经知道函数可以接收参数functionsayHello(userName){return你好${userName};}React 组件本质上也是函数。所以组件当然也可以接收“外部给它的数据”。只不过在 React 里这份外部数据通常就叫做props2. 为什么它叫props你现在不必死记这个单词的来源。当前阶段更重要的是知道当我们说“这个组件有哪些props”本质上是在说“这个组件可以接收哪些外部配置”。3. 标题里写Props代码里常写props这里顺手帮你澄清一个小细节。在概念标题里我们常写Props但在代码里变量名更常写成props你可以把它理解成标题里的Props是概念名代码里的props是具体变量名。三、为什么没有props组件就很难复用这一节很关键。因为如果你不先看见问题就很难真正理解props的价值。先看一个“写死内容”的按钮组件function PrimaryButton() { return button立即开始学习/button; }它的问题不是不能用而是只能显示这一句固定文案。如果你还想显示保存草稿提交作业删除项目你可能就会不自觉写出这种代码function SaveButton() { return button保存草稿/button; } function SubmitButton() { return button提交作业/button; }这时候你会发现结构几乎一样。差别主要只是文字内容不同。继续复制会越来越重复。1. 这里真正重复的是什么真正重复的不是“业务名字”而是按钮结构本身。也就是说外层是button样式结构相似只是显示内容不同这类场景非常适合提炼成一个组件再通过props把不同的内容传进去。2. 所以props真正解决了什么问题一句话概括就是把“固定结构”和“变化内容”分开。这句话非常重要。你以后在设计组件时会反复用到它。四、先用一句话理解props就是给组件传参数如果你现在觉得props还有点抽象别急。我们先把它和函数参数直接对齐。1. 函数参数是怎么工作的functionformatPrice(price){return${price};}这里的price是谁给的答案是调用这个函数的人给的。2. 组件里的props也很像这个逻辑function WelcomeCard(props) { return h2欢迎学习 {props.courseName}/h2; }这里的courseName也是外部传进来的。只是它不是通过普通函数调用传递而是通过组件标签传递。例如WelcomeCard courseNameReact /3. 当前阶段最稳的记法你可以先用一句非常实用的话记住props对组件来说就像函数参数对函数一样。这会帮你把 React 里的很多新概念和你已经熟悉的 JavaScript 基础连接起来。五、父组件是怎么给子组件传值的现在我们正式看最基础的传值方式。先看下面这段代码function WelcomeCard(props) { return h2欢迎学习 {props.courseName}/h2; } function App() { return ( main WelcomeCard courseNameReact / WelcomeCard courseNameTypeScript / WelcomeCard courseNameNext.js / /main ); }1. 这里谁是父组件谁是子组件在这段代码里App是父组件WelcomeCard是子组件因为App在自己的 JSX 里使用了WelcomeCard。2. 数据是怎么传进去的App在使用组件时写了WelcomeCard courseNameReact /这里的courseNameReact就是在给子组件传值。你可以先把它理解成像给组件写“配置项”。3. 为什么这一步这么重要因为从这一刻开始组件就不再是只能显示固定内容的模板了。它开始具备这样的能力同一套结构可以根据传入的数据显示不同结果。这正是组件复用的起点。六、子组件是怎么接收props的父组件传值之后子组件要能把它接住。最基础的写法就是function WelcomeCard(props) { return ( section h3{props.courseName}/h3 p{props.description}/p /section ); }然后父组件这样传WelcomeCard courseNameReact description用组件化方式组织界面。 /1. 这里最值得先看懂什么你先看明白三件事组件函数可以接收一个props参数。父组件传入的值会出现在这个props里。子组件可以通过props.xxx的方式读取它。2.props.xxx的含义是什么比如props.courseName可以理解成从props这个对象里取出courseName这个字段。3. 这一章先别追更复杂的写法当前阶段你先能稳定读懂这种写法就已经非常好了function SomeComponent(props) { return div{props.title}/div; }后面我们再继续把写法写得更自然一些。七、为什么说props本质上是一个对象这一点非常重要。因为一旦你理解了这一点很多语法细节就不会再神秘。先看下面这段代码WelcomeCard courseNameReact description用组件化方式组织界面。 lessonCount{3} /从使用角度看你是在写三个属性。但从子组件接收角度看它更像拿到这样一个对象{courseName:React,description:用组件化方式组织界面。,lessonCount:3}所以你才会在组件内部看到这种读取方式props.courseName props.description props.lessonCount1. 这会带来什么好处它让你很容易理解一个组件为什么能接收多个值。为什么组件里可以按字段去读数据。为什么后面可以用解构的方式把字段直接取出来。2. 当前阶段请记住一句话props不是很多零散变量而是一整个“由外部传入的配置对象”。这句话特别关键。八、更常见的写法直接解构props虽然props.courseName这种写法很好理解但在真实项目里更常见的写法是直接解构。例如function WelcomeCard({ courseName, description }) { return ( section h3{courseName}/h3 p{description}/p /section ); }1. 这和前面的写法有什么关系它本质上还是同一件事。只是从props.courseName变成了courseName也就是说我们先把props里的字段取出来再直接使用。2. 为什么大家更喜欢这种写法因为它通常更简洁、更易读。尤其当你在 JSX 里要多次使用这些字段时直接写courseNamedescription会比反复写props.courseNameprops.description更自然。3. 当前阶段怎么选你现在可以这样理解想先把原理看清楚就先看props.xxx。想让日常代码更简洁就慢慢习惯解构写法。这两种写法都能读懂你的基础就会更稳。九、props可以传哪些东西这一节很实用。因为初学者很容易误以为props只能传字符串。其实并不是。props可以传很多常见类型的数据。十、第一类字符串、数字、布尔值这是最基础、最常见的一类。function LessonCard({ title, lessonCount, isFree }) { return ( section h3{title}/h3 p课时数{lessonCount}/p p{isFree ? 免费课程 : 付费课程}/p /section ); } function App() { return ( main LessonCard titleReact 入门 lessonCount{12} isFree{true} / /main ); }这里你可以很直观地看到字符串titleReact 入门数字lessonCount{12}布尔值isFree{true}1. 为什么数字和布尔值要写花括号因为它们不是普通文本而是 JavaScript 表达式。所以要写成lessonCount{12} isFree{true}而不是lessonCount12 isFreetrue2. 当前阶段先记最基础的区分你可以先这样记纯文本字符串可以直接写引号。数字、布尔值、变量、表达式通常写在花括号里。十一、第二类数组和对象除了简单值props也可以传数组和对象。例如function SkillPanel({ skillList, userInfo }) { return ( section h3{userInfo.userName}/h3 p所在城市{userInfo.cityName}/p p技能数量{skillList.length}/p /section ); }父组件可以这样传function App() { const skillList [HTML, CSS, JavaScript]; const userInfo { userName: 小白, cityName: 北京 }; return SkillPanel skillList{skillList} userInfo{userInfo} /; }1. 这说明了什么说明props不只是能传简单字面量它也可以传一组数据一个对象结构这对真实项目特别重要。因为很多组件本来就需要接收列表数据用户信息商品信息接口返回值的一部分十二、第三类函数这一类你先认识就够了下一章讲事件时会更深入。例如function ActionButton({ text, onPress }) { return button onClick{onPress}{text}/button; }父组件可以这样传function App() { function handleStart() { console.log(开始学习); } return ActionButton text开始学习 onPress{handleStart} /; }1. 为什么函数也能当props因为函数本来就是 JavaScript 里的值。既然它是值就可以像其他数据一样传给组件。2. 当前阶段只需要先知道什么你现在先建立这个认知就够了不只是“显示内容”能通过props传某些交互行为也能通过props传进去。这样到下一章学事件时你就会顺很多。十三、从“写死组件”到“可配置组件”这一节我们来真正对比一下。先看一个写死内容的课程卡片function CourseCard() { return ( section h3React 入门/h3 p用组件化方式组织页面。/p /section ); }这个组件只能表达一个固定课程。如果你要展示多个课程就会很受限。现在把它改成可配置版本function CourseCard({ title, description }) { return ( section h3{title}/h3 p{description}/p /section ); }然后父组件这样使用function App() { return ( main CourseCard titleReact 入门 description用组件化方式组织页面。 / CourseCard titleTypeScript 基础 description让数据结构和函数边界更清楚。 / /main ); }1. 这里真正升级了什么真正升级的不是“代码看起来更高级了”而是同一套组件结构可以服务多个不同内容场景。2. 这就是组件复用的第一步所以你要开始习惯这样思考哪一部分是固定结构哪一部分是变化内容变化内容能不能通过props传进来这三问对你后面设计组件会非常有帮助。十四、为什么说props是组件复用的基础现在我们把结论说得更明确一点。组件复用不是指把同一段代码复制很多次。真正的组件复用应该是保留同一套结构通过不同的props生成不同的使用结果。1. 一个按钮组件的例子例如下面这个按钮组件function ActionButton({ text, buttonType }) { return button className{buttonType}{text}/button; }它可以被这样使用ActionButton text保存草稿 buttonTypesecondary / ActionButton text立即发布 buttonTypeprimary /2. 这里复用的到底是什么复用的是按钮结构按钮写法按钮使用方式变化的是文案类型这正是很多组件设计最核心的思路结构复用变化通过props控制。十五、父组件和子组件的角色分工这一节很重要。因为很多初学者学props时容易只盯着语法而忽略了组件关系本身。1. 父组件通常更像“提供数据的人”例如function App() { const userName 小白; return ProfileCard userName{userName} /; }在这里App更像是在决定要给子组件什么数据。2. 子组件通常更像“消费这些数据的人”例如function ProfileCard({ userName }) { return h2{userName}/h2; }在这里ProfileCard负责的是根据收到的数据把界面渲染出来。3. 当前阶段先记住最基础的分工你可以先这样记父组件传值。子组件接值。子组件根据值显示内容。这已经是理解props的核心骨架了。十六、props是单向的从父到子这一点越早理解越好。React 里最基础的数据流方向是父组件把props传给子组件。也就是说当前阶段你先把props理解成自上而下传递的数据。1. 为什么这条规则重要因为它能让组件之间的关系更清楚。你更容易回答这些问题数据从哪里来谁在负责提供它谁在负责展示它2. 当前阶段不要把props当成“可以随便改的内部变量”这很重要。props是外部给组件的数据不应该在子组件里把它当成“我自己的内部状态”随意改动。你现在先建立一个温和但关键的意识props更像输入不像组件内部自己管理的状态。下一章讲state时这个区别会变得非常清楚。十七、在 TypeScript 项目里props为什么特别适合配类型你前面已经学过 TypeScript所以这里我们可以很自然地往前接一步。如果你在 TypeScript 项目中写 Reactprops往往特别适合配上类型。例如interface CourseCardProps { title: string; description: string; } function CourseCard({ title, description }: CourseCardProps) { return ( section h3{title}/h3 p{description}/p /section ); }1. 这样做有什么好处它能更清楚地说明这个组件需要哪些props。每个props应该是什么类型。父组件传错时编辑器能更早提醒。2. 当前阶段怎么用这个知识最稳你不用一上来给每个组件都写得很复杂。先做到这一步就很够用了给核心组件的props补上清楚、简单的类型。这会明显提升你对组件边界的理解。十八、设计可复用组件时先抓哪几个原则这一节是整章的“实战思维”部分。因为学会语法之后真正决定你能不能把组件写好的是你怎么设计props。1. 第一条原则先找“固定结构”和“变化内容”这是最重要的一条。你要先问自己这个组件里哪些内容每次都一样哪些内容只是换个标题、换个颜色、换个说明那些变化内容往往就适合变成props。2. 第二条原则props名字要有意义例如titledescriptionbuttonTypeuserName通常都比data1value2info更清楚。因为组件的可读性很大一部分就来自使用组件时一眼能看懂这些props在表达什么。3. 第三条原则不要一开始就追求“万能组件”这是初学者特别容易踩的坑。很多人一学会props就会想把一个组件设计成什么都能显示什么场景都能用什么样式都能配结果通常是props数量越来越多组件职责越来越模糊自己过几天回来都看不懂所以更稳的做法是先满足当前场景再逐步抽象而不是一开始就设计成万能工具。4. 第四条原则复用的目标是减少重复不是制造复杂度如果你为了复用最后写出了一个很难读、很难传值、很难维护的组件那通常就说明这个抽象可能用力过猛了。好的复用应该让代码更清楚更省重复更容易维护而不是反过来。十九、一个通用按钮组件示例下面我们看一个更完整、但依然适合入门阅读的例子。function ActionButton({ text, buttonType, onPress }) { return ( button className{buttonType} onClick{onPress} {text} /button ); }这个组件接收了三个propstext按钮文案buttonType按钮类型onPress点击按钮时要执行的逻辑父组件可以这样使用它function App() { function handleSave() { console.log(保存成功); } function handlePublish() { console.log(发布成功); } return ( main ActionButton text保存草稿 buttonTypesecondary onPress{handleSave} / ActionButton text立即发布 buttonTypeprimary onPress{handlePublish} / /main ); }1. 这个例子最值得你观察什么它展示了一个非常重要的组件思路同一个组件可以通过不同props表现出不同用途。2. 当前阶段别被“事件”吓到这里出现了onPress你先不用急着把事件系统全部吃透。当前你只要先理解按钮“点完做什么”也可以作为一种配置传给组件。这样就足够了。二十、再看一个卡片组件示例除了按钮卡片也是非常典型的可复用组件场景。function CourseCard({ title, level, description }) { return ( section h3{title}/h3 p难度{level}/p p{description}/p /section ); }父组件可以这样使用function App() { return ( main CourseCard titleReact 入门 level初级 description理解 JSX、组件与 Props。 / CourseCard titleTypeScript 基础 level初级 description学会类型注解、接口和泛型。 / /main ); }1. 这个例子在训练什么能力它在训练你建立这样一种判断卡片结构是固定的。标题、难度、描述是变化的。所以这些变化项适合变成props。2. 这类思路以后会反复出现不只是课程卡片。以后你写这些内容时都会反复用到同样的判断商品卡片用户卡片评论项导航项表格行所以这一节看似简单其实非常关键。二十一、初学者最容易踩的几个坑这一节建议你认真看。因为props本身不难难的是一开始很容易用歪。1. 坑一把所有内容继续写死在组件里有些人虽然开始学props了但还是习惯把很多本该变化的内容直接写死。例如标题写死描述写死按钮文案写死这样组件表面上拆出来了实际上复用价值还是很低。所以你要经常问自己一句话这个地方会不会随着场景变化如果会就值得考虑是不是该做成props。2. 坑二props命名太模糊例如infodataitem这些名字有时候不是不能用但如果上下文不清楚就很容易让代码变得模糊。更稳的做法是优先用能体现业务含义的名字。3. 坑三为了复用把组件设计得过于万能这几乎是 React 初学者最常见的“过度设计”问题。比如一个按钮组件本来只需要文字类型点击逻辑但有些人会一上来就想支持十几种场景、几十个配置项。结果不是更灵活而是更混乱。4. 坑四以为props和state是一回事这一点先轻轻记住。props更像外部传入的数据。而state更像组件内部自己管理的数据。这一章你先不要急着完全吃透但至少先知道这两个概念不是一回事。下一章我们会正式把它讲清楚。5. 坑五组件一多就看不清谁在给谁传值当组件层级变多时你有时候会开始混乱这个值是谁传来的这个组件为什么能拿到这个数据这时候最稳的办法是回到组件树从父到子一层层看数据流。这也是为什么上一章先让你建立“组件树”思维这一章再讲props。二十二、本章实践练习这一章的练习重点是把“组件接收外部配置”这件事真正练熟。1. 练习 1把写死的欢迎卡片改造成可配置组件请你先写一个固定内容的组件例如欢迎学习 React欢迎学习 TypeScript然后把它改造成可以通过props接收课程名的组件。练习重点是看见这个变化结构不变内容通过props传入。2. 练习 2封装一个可复用按钮组件请你写一个按钮组件至少支持按钮文字按钮类型如果你状态比较稳还可以多加一个点击后执行的函数这个练习的重点是理解按钮不应该被“写死成一个用途”而应该被设计成“可以配置的结构”。3. 练习 3封装一个课程卡片组件请你写一个课程卡片组件支持课程标题难度等级课程描述然后在页面中渲染至少两张不同内容的卡片。这个练习在训练你用同一套组件结构展示多组不同数据。4. 练习 4尝试为组件props写简单类型如果你正在使用 TypeScript请尝试给上面的按钮组件或课程卡片组件加上props类型。哪怕只写最基础的stringnumber函数类型也已经很有价值了。这个练习会帮你把 React 和 TypeScript 真正接起来。二十三、学习重点提示这一章请你重点记住下面这些话props是父组件传给子组件的数据。props对组件来说就像函数参数对函数一样。组件复用的核心不是复制同一段代码而是通过不同props复用同一套结构。props本质上可以理解为一个由外部传入的配置对象。在组件内部可以通过props.xxx或解构方式读取数据。props不只能传字符串也可以传数字、布尔值、数组、对象和函数。设计组件时要先区分固定结构和变化内容。不要一开始就设计万能组件先满足当前场景再逐步抽象。props是外部输入state是内部状态这两个概念不能混为一谈。如果你只记一句话请记住props的核心价值是让组件从“写死的界面块”变成“可配置、可复用的界面单元”。二十四、本章小结这一章我们正式把 React 从“会写组件”推进到了“会让组件接收数据”。你已经理解了什么是props父组件和子组件之间最基础的传值方式为什么说props本质上是一个对象如何通过props.xxx和解构方式读取数据props可以传递哪些常见类型的数据为什么props是组件复用的基础设计可复用组件时应该先抓哪些原则更重要的是你开始建立起这样一种组件设计思路先保留稳定结构再把变化内容通过props抽出来。这个思路非常关键。因为从这里开始React 组件就不再只是“被拆开的页面片段”而开始真正成为可以被重复使用、可以根据数据变化呈现不同结果的界面单元。二十五、课后思考题请你认真思考下面这些问题为什么说props对组件来说就像函数参数对函数一样为什么没有props组件通常很难真正复用为什么说props本质上是一个对象组件设计时怎么区分“固定结构”和“变化内容”为什么“复用组件”不等于“把一段代码复制很多次”为什么初学阶段不建议一上来就设计万能组件props和state最基础的区别是什么建议你把这些问题用自己的话写下来。只要你能把这些问题讲清楚说明你已经真正进入 React 的数据传递思维了。二十六、下一篇预告下一章我们会进入从零开始学前端 | 第二十九章State、事件与受控组件到那时你会继续解决另一个非常关键的问题如果数据不是外部传进来的而是组件自己会变化那该怎么办你会开始学习什么是state状态变化为什么会驱动界面更新React 中事件处理的基础写法表单为什么会和状态联系起来也就是说下一章开始我们会从“接收外部数据”继续走到管理组件内部会变化的数据。