React 代码风格指南

Avatar of Daniel Jauch
Daniel Jauch

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

我最近一直在用 React 玩得很开心。 但在我的旅途中,我一直很难找到好的代码风格指南来保持 JSX 和 JS 的混合干净且可读。 我一直在制定自己的风格指南,我想分享出来。 也许这些对您有用,当然,请随时在下面的评论区分享类似的指南。

规则 #1:解构您的props

我最喜欢的 ES6 功能之一是解构。 它使将对象属性分配给变量感觉不再那么麻烦。 让我们看一个例子。

假设我们有一条狗,我们想将其显示为一个 div,其类名与其品种相同。 在 div 内部是一个句子,说明了狗的颜色,并告诉我们它是一条好狗还是坏狗。

class Dog extends Component {
  render () {
    return <div className={this.props.breed}>My {this.props.color} dog is {this.props.isGoodBoy ? "good" : "bad"}</div>;
  }
}

从技术上讲,它确实做了我们想要的一切,但这似乎是一块很大的代码,实际上只有三个变量和一个 HTML 标签。

我们可以通过将props的所有属性分配给局部变量来将其分解。

let breed = this.props.breed;
let color = this.props.color;
let isGoodBoy = this.props.isGoodBoy;

使用 ES6,我们可以像这样将其放在一个干净的语句中

let { breed, color, isGoodBoy } = this.props;

为了保持一切清洁,我们也将三元运算符(稍后会详细介绍)放在它自己的变量中,

class Dog extends Component {
  render () {
    let { breed, color, isGoodBoy } = this.props;
    let identifier = isGoodBoy ? "good" : "bad";
    return <div className={breed}>My {color} dog is {identifier}</div>;
  }
}

非常易于阅读。

规则 #2:一个标签,一行

现在,我们都有过这样的时刻,我们想把整个函数变成一堆运算符和微小的参数名,以创建一个丑陋的、超级快、不可读的实用程序函数。 但是,当你在 React 中创建一个无状态组件时,你可以相当容易地做到同样的事情,同时保持干净。

class Dog extends Component {
  render () {
    let { breed, color, goodOrBad } = this.props;
    return <div className={breed}>My {color} dog is {goodOrBad}</div>;
  }
}

vs.

let Dog = (breed, color, goodOrBad) => <div className={breed}>My {color} dog is {goodOrBad}</div>;

如果你只是创建了一个基本元素并将属性放在 HTML 标签中,那么不必为了获得一个完全独立的类而大费周折。 一行代码就够了。

如果你传递一个对象作为你的属性,你甚至可以利用一些 ES6 展开函数来进行创造性的操作。 使用this.props.content 将自动将字符串放在开始和结束标签之间。

let propertiesList = {
  className: "my-favorite-component",
  id: "myFav",
  content: "Hello world!"
};
let SimpleDiv = props => <div {... props} />;

let jsxVersion = <SimpleDiv props={propertiesList} />;

何时使用展开函数

  • 不需要三元运算符
  • 只传递 HTML 标签属性和内容
  • 可以重复使用

何时使用展开函数

  • 动态属性
  • 需要数组或对象属性
  • 需要嵌套标签的渲染

规则 #3:3 的规则

如果你有三个或更多属性,那么在实例和渲染函数中都将它们放在单独的行上。

只有一行属性是可以的

class GalleryImage extends Component {
  render () {
    let { imgSrc, title } = this.props;
    return (
      <figure>
        <img src={imgSrc} alt={title} />
        <figcaption>
          <p>Title: {title}</p>
        </figcaption>
      </figure>
    );
  }
}

但考虑一下

class GalleryImage extends Component {
  render () {
    let { imgSrc, title, artist, clas, thumbnail, breakpoint } = this.props;
    return (
      <figure className={clas}>
        <picture>
          <source media={`(min-width: ${breakpoint})`} srcset={imgSrc} />
          <img src={thumbnail} alt={title} />
        </picture>
        <figcaption>
          <p>Title: {title}</p>
          <p>Artist: {artist}</p>
        </figcaption>
      </figure>
    );
  }
}

或者渲染

<GalleryImage imgSrc="./src/img/vangogh2.jpg" title="Starry Night" artist="Van Gogh" clas="portrait" thumbnail="./src/img/thumb/vangogh2.gif" breakpoint={320} />

它可能成为一个太大的代码块,无法阅读。 将每个属性放到下一行,以获得干净、可读的外观

let { imgSrc,
      title,
      artist,
      clas,
      thumbnail,
      breakpoint } = this.props;

<GalleryImage
  imgSrc="./src/img/vangogh2.jpg"
  title="Starry Night"
  artist="Van Gogh" 
  clas="landscape"
  thumbnail="./src/img/thumb/vangogh2.gif"
  breakpoint={320} />

规则 #4:属性太多?

属性管理在任何级别都是很棘手的,但有了 ES6 解构和 React 的基于状态的方法,有很多方法可以清理大量属性的外观。

假设我们正在创建一个地图应用程序,该应用程序有一个已保存地址列表和您的当前位置的 GPS 坐标。

当前用户的位置信息和与收藏地址的接近程度应该在 App 的父组件中,如下所示

class App extends Component {
  constructor (props) {
    super(props);
    this.state = {
      userLat: 0,
      userLon: 0,
      isNearFavoriteAddress: false
    };
  }
}

因此,当我们创建一个地址并希望它记录您距离地址有多远时,我们至少要从 App 传递两个属性。

在 App 的render ()

<Address
  ... // Information about the address
  currentLat={this.state.userLat}
  currentLong={this.state.userLon} />

在 Address 组件的渲染函数中

render () {
  let { houseNumber,
        streetName,
        streetDirection,
        city,
        state,
        zip,
        lat,
        lon,
        currentLat,
        currentLon } = this.props;
  return ( ... );
}

你已经可以看出这变得多么笨拙。 如果我们将两组信息分解成它们自己的对象,它会变得更易于管理。

在我们的 App 的constructor ()

this.state = {
  userPos: {
    lat: 0,
    lon: 0
  },
  isNearFavoriteAddress: false
};

在 App 的render ()之前

let addressList = [];
addressList.push({
  houseNumber: "1234",
  streetName: "Street Rd",
  streetDirection: "N",
  city: "City",
  state: "ST",
  zip: "12345",
  lat: "019782309834",
  lon: "023845075757"
});

在 App 的render ()

<Address addressInfo={addressList[0]} userPos={this.state.userPos} />

在 Address 组件的渲染函数中

render () {
  let { addressInfo, userPos } = this.props;
  let { houseNumber,
        streetName,
        streetDirection,
        city,
        state,
        zip,
        lat,
        lon } = addressInfo;
  return ( ... );
}

干净得多。 React 还有一些很棒的方法来确保对象属性存在并且是特定类型的,使用PropTypes,这是我们在 JavaScript 中通常没有的,这本身就是一件很棒的面向对象编程的事情。

规则 #5:动态渲染 - 映射数组

在 HTML 中,我们经常一遍又一遍地编写相同的基本代码段,只是有一些关键区别。 这就是 React 最初创建的原因。 你创建了一个具有属性的对象,它返回一个复杂的动态 HTML 块,而无需重复编写每个部分。

JavaScript 已经有一个很好的方法来处理类似信息的列表:数组!

React 使用.map() 函数按顺序排列数组,使用数组中的一个参数作为key

render () {
  let pokemon = [ "Pikachu", "Squirtle", "Bulbasaur", "Charizard" ];
  return (
    <ul>
      {pokemon.map(name => <li key={name}>{name}</li>)}
    </ul>
  );
}

你甚至可以使用我们方便的展开函数,通过使用Object.keys() 将整个参数列表放入一个对象中(请记住,我们仍然需要一个key)。

render () {
  let pokemon = {
    "Pikachu": {
      type: "Electric",
      level: 10
    },
    "Squirtle": {
      type: "Water",
      level: 10
    },
    "Bulbasaur": {
      type: "Grass",
      level: 10
    },
    "Charizard": {
      type: "Fire",
      level: 10
    }
  };
  return (
    <ul>
      {Object.keys(pokemon).map(name => <Pokemon key={name} {... pokemon[name]} />)}
    </ul>
  );
}

规则 #6:动态渲染 - React 三元运算符

在 React 中,你可以使用运算符来执行条件渲染,就像变量声明一样。 在规则 #1 中,我们查看了这方面,用于说明我们的狗是好是坏。 为一个句子中一个词的差异而创建一整行代码并不完全必要,但当它变成大型代码块时,很难找到这些?:

class SearchResult extends Component {
  render () {
    let { results } = this.props;
    return (
      <section className="search-results">
        {results.length > 0 &&
          results.map(index => <Result key={index} {... results[index] />)
        }
        {results.length === 0 &&
          <div className="no-results">No results</div>
        }
      </section>
    );
  }
}

或者,以真正的三元方式

class SearchResult extends Component {
  render () {
    let { results } = this.props;
    return (
      <section className="search-results">
        {results.length > 0
          ? results.map(index => <Result key={index} {... results[index] />)
          : <div className="no-results">No results</div>
        }
      </section>
    );
  }
}

即使有了我们整洁的结果映射,你也可以看到方括号的嵌套已经相当密集。 现在,想象一下,如果我们的渲染不仅仅只有一行。 它很快就变得不可读。 考虑一下另一种方法

class SearchResult extends Component {
  render () {
    let { results } = this.props;
    let outputJSX;
    if (results.length > 0) {
      outputJSX = (
        <Fragment>
          {results.map(index => <Result key={index} {... results[index] />)}
        </Fragment>
      );
    } else {
      outputJSX = <div className="no-results">No results</div>;
    }
    return <section className="search-results">{outputJSX}</section>;
  }
}

最终,代码长度大致相同,但有一个关键区别:在第一个例子中,我们快速地在两种不同的语法之间切换,这使得视觉解析变得繁琐和困难,而第二个例子只是简单的 JavaScript,使用一种一致的语言进行值分配,并在另一种语言中进行一行函数返回。

在这种情况下,经验法则是,如果你放在 JSX 对象中的 JavaScript 超过两个单词(例如object.property),那么它应该在return调用之前完成。

总结

语法的组合会变得混乱,而这些是我看到我的代码偏离轨道最明显的情况。 以下是所有这些都源于的基本概念,可以应用于此处未涵盖的任何情况

  • 使用 ES6 功能。 真的。 这里有很多很棒的功能可以使你的工作更轻松、更快,而且更少手动操作。
  • 只在=return的右侧编写 JSX。
  • 有时你需要在 JSX 中使用 JavaScript。 如果你的 JavaScript 不能放在一行上(比如.map() 函数或三元运算符),那么它应该提前完成。
  • 如果你的代码看起来像(<{`${()}`} />),那么你可能已经走得太远了。 从当前语句的最低级别开始,在它之前完成。