React 代码风格指南 【翻译】

原文链接:React Code Style Guide
阅读大概需要15分钟

我使用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 标签属性和内容
  • 可重复性高

什么时候不应该使用扩展函数:

  • 动态属性
  • 需要数组或对象格式的属性
  • render 中需要内置的标签

规则 #3:三个属性的规则

假设你有三个或三个以上的属性,然后在实例和 render 函数中,都写在一行。
如果只有一行属性也还好:

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开发,有一些方式可以让大量的属性看起来更清晰。

假设我们要开发一款地图APP,需要保存一堆当前位置的地址和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函数:

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函数:

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

非常的清晰!React也有非常好的方式确保这些对象属性存在,并确保时特定的类型,通过JavaScript中没有的 PropTypes属性来实现,这是一种很棒的OOP的实现。

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

在很多的HTML文件中,我们会写很多同样的代码,尽管他们只有一点点差别。这也是React最初产生的原因,你可以通过把属性放在一个对象中,然后返回一个复杂、动态的HTML代码块,而不需要重复编写代码块。

JavaScript已经有一种方式实现这个目标:arrays!
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特性,坦白来说,ES6中有很多很棒的特性可以让你的工作变得更轻松、快捷,编写更少的代码。
  • =return右边只使用 JSX。
  • 有时候需要在JSX 中使用JavaScript。如果你的JavaScript不能在一行内(像.map()函数或者三元操作符这样)完成,最好在使用之前处理好。
  • 如果你的代码开始像<{`${()}`} />这样,你可能已经离代码规范很遥远了。这种低层次的处理应当在当前语句之前就处理好。

译者有话说:
虽然作者洋洋洒洒总结了六条挺实用的代码规范,但还是在评论区“被怼了”,我觉得评论区提出的意见也非常好,所以在下面也贴一下,也加了点自己的理解。
1)箭头函数的参数:
原文中:

let Dog = (breed, color, goodOrBad) =>
My {color} dog is {goodOrBad}

更推荐:

let Dog = ({breed, color, goodOrBad}) =>
My {color} dog is {goodOrBad}

把参数写成对象的方式,可以忽略参数次序和省略参数,更为灵活,其实还可以给参数一些默认值。
2)const VS let:
对于从props传递过来的属性,其实是 immutable,不建议更改,默认使用 const 来声明,除非一些特殊的情况,不得不使用 let。
3)关于条件表达式:
原文:

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 = No results;
    }
    return <section className="search-results">{outputJSX}</section>;
  }
}

推荐:

class SearchResult extends Component {
  render () {
    const { results } = this.props

    const Results = () => <Fragment>
      {results.map(index => <Result key={index} {...results[index]} />)}
    </Fragment>

    const NoResults = () => No results

    return <section className="search-results">
      {
        results.length
          ? Results()
          : NoResults()
      }
    </section>
  }
}

推荐写法其实逻辑更清晰,对条件表达式更好的应用,不建议在render 中写太多的条件判断语句。
4)对象遍历:
Object.entries貌似比Object.keys方法更优雅。
Object.entries(obj).map(([key, val]) => This value is {val})


译者还是新手,文中有翻译不合理的地方,欢迎大家提建议。

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