Components和Props

Components使得UI被划分为一个个独立、可复用的零件,并单独考虑每个零件。
在概念上,组件就像是JavaScript函数。他们接受任意的输入(称为"props"),并返回React elements来描述屏幕上应该出现的内容。

函数式Components和class Components

定义一个Component最简单的方法就是写个JavaScript函数:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

这个函数是一个有效的React component,因为他接受一个名为'props'对象的参数,并且返回一个React element。我们称这种组件为'函数式'的,因为他们就是普通的JavaScript函数。
你也可以用一个ES6 class来定义component:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

从React的角度来看,上面这两个components是相同的。
我们将在下一节讨论,Classes方式中的一些额外特性。在那之前,方便起见,我们将使用函数式的components。

渲染Component

之前,我们至于见过表现DOM标签的React elements:

const element = <div />;

不仅如此,elements也可以表示用户自定义的components:

const element = <Welcome name="Sara" />

当React发现一个表示用户自定义组件的element,它会把JSX属性作为一个单独的对象传给这个component。我们将这个对象成为'props'。
举个例子,下面的代码在页面上渲染出"Hello, Sara":

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

在CodePen上试试看
让我们重新过一遍这个例子中发生了什么:

  1. 我们调用ReactDOM.render()函数去渲染element:<Welcome name="Sara" />
  2. React调用Wecome组件,将{name: 'Sara'}作为props。
  3. 我们的Welcome组件返回一个element: <h1>Hello, Sara</h1>作为结果。
  4. React DOM高效地更新DOM来匹配<h1>Hello, Sara</h1>

警告:
component名字的首字母始终都是大写。
比如,<div />表示一个DOM标签,但是<Welcome />表示一个component,而且需要将Welcome加入当前范围内。

组合Components

Components可以在他们的输出中引用其他的components。这样我们就可以使用相同的组件来叠加完成任意的细节。按钮,表单,对话框和屏幕,在React应用中,这些通常都表示为组件。
比如:我们可以创建一个App组件用来渲染多个Welcome

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

在CodePen上试一试
通常,新的React应用在最上层有一个单独的App组件。但是如果你将React集成到现有的应用中,你可以自下而上,从一个小的component开始,比如一个按钮,逐步的整合到层次结构的顶层。

警告:
Components必须返回一个单独的根element。这就是为什么我们添加一个<div>来包括所有的<Welcome /> elements。

提取Components

大胆的将components分割成更小的components。
比如,考虑这个Comment component:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

在CodePen上试一试
它接收author(一个object),text(一个字符串)和date(一个date)作为props,描述了社交媒体网站上一则评论。
由于全是嵌套,这个component很难改变,而且也很难去重用他的各个部分。让我们从中提取几个组件。
首先,我们来提取Avatar:

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avatar不需要知道Comment里面究竟在渲染什么东西。因此我们传给prop一个更为通用的名字:user,而不是author
我们建议从组件自身的观点来命名props,而不是依据组件所在的环境。
现在我们就稍微简化了下Comment:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

接下来,我们将提取一个UserInfo组件,他渲染一个Avatar和用户的名字:

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

这使得我们可以进一步简化Comment:

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

在CodePen上试一试
提取components也许乍看之下像是无用功,但在大型的app中,能有一系列可重用的组件,这么做绝对超值。一个很好的经验法则:如果你的UI中某部分被重复使用多次(按钮,面板,头像),或者自身相当复杂(App,FeedStory,评论), 这都是成为可重用组件的最佳人选。

Props是只读的

不论你将component声明为函数还是类,都绝不能去修改它的props。看下这个sum函数:

function sum(a, b) {
  return a + b;
}

这样的函数我们称其为"pure",因为他们不回去改变输入,且对于输入一样返回的结果始终都是相同的。
相比之下,这个函数不是纯函数,因为它改变了自己的输入:

function withdraw(account, amount) {
  account.total -= amount;
}

React虽然相当灵活,但它有一个严格的规则:
所有的React组件必须像个纯函数一样,不得修改他们的props。
当然,应用的UI是动态的,随着事件而变化。在下一节,我们将引入一个新的概念:"state"。State使得React components可以根据用户的行为,网络响应或者其他什么,使得他们的输出每次不同,而不会违反此规则。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容