React组件重构实践

基础应用

一个可以搜索、删除书籍的单页面:


image.png

基础代码以create react app生成,使用ES6的语法特征进行重构。

基础代码

整个页面不做任何组件拆分,只有一个BookListing组件:

const list=[
  {
    title: 'Road to React',
    url: 'https://facebook.github.io/react/',
    author: 'Jordan Walke',
    num_comments: 3,
    points: 4,
    objectID: 0,
  },
  ...
];

const isSearched = function(searchTerm){
  return function(item) {
    return item.title.toLowerCase().includes(searchTerm.toLowerCase());
  }
}
export default class BookListing extends Component {

  constructor(props) {
    super(props);
    this.state = {
      list,
      searchTerm: ''
    };

    this.search = this.search.bind(this);
    this.dismiss = this.dismiss.bind(this);
  }

  search(e) {
    this.setState({
      searchTerm: e.target.value
    });
  }

  dismiss(objectID) {
    let updatedList = this.state.list.filter(item => item.objectID !== objectID);
    this.setState({
      list: updatedList
    });
  }

  render() {
    return (
      <div>
        <form>
          Search <input
            type="text"
            onChange={e => this.search(e)}
          >
          </input>
        </form>
        <div>
          {this.state.list.filter(isSearched(this.state.searchTerm))
            .map(item =>
              <div key={item.objectID} className="table-row">
                <span style={{ width: '40%' }}>
                  <a href={item.url}>{item.title}</a>
                </span>
                <span style={{ width: '30%' }}>{item.author}</span>
                <span style={{ width: '10%' }}>{item.num_comments}</span>
                <span style={{ width: '10%' }}>{item.points}</span>
                <span style={{ width: '10%' }}>
                  <button
                    type="button"
                    onClick={() => this.dismiss(item.objectID)}
                  >
                    Dismiss
            </button>
                </span>
              </div>
            )}
        </div>
      </div>
    )
  }
}

重构实践

  • ES6箭头函数

isSearched函数可以使用ES6的箭头函数语法进行重构,改写后:

const isSearched = searchTerm => item => item.title.toLowerCase().includes(searchTerm.toLowerCase())

仅用一行就可以完成同样的功能。

  • 解构

ES6支持对象解构,render函数中的state可以解构,简化链式调用的长度:

render() {
  const {list, searchTerm} = this.state;
  // ...
  {list.filter(isSearched(searchTerm))
    .map(item => 
      <div>
        {/* ... */}
      </div>
  )}
}
  • 组件拆分

BookListing组件过于复杂,可以拆分成几个职责更单一的组件,提取Search,Table组件,对应于搜索框和列表:

export default class Search extends Component {
  render() {
    const {value, search, children} = this.props;
    return (
      <form>
        Search <input
          type="text"
          onChange={search}
        >
        </input>
      </form>
    )
  }
} 
export default class Table extends Component {
  render() {
    const { list, pattern, onDismiss } = this.props;
    return (
      <div>
        {list.filter(isSearched(searchTerm))
          .map(item =>
            <div key={item.objectID} className="table-row">
              <span style={{ width: '40%' }}>
                <a href={item.url}>{item.title}</a>
              </span>
              <span style={{ width: '30%' }}>{item.author}</span>
              <span style={{ width: '10%' }}>{item.num_comments}</span>
              <span style={{ width: '10%' }}>{item.points}</span>
              <span style={{ width: '10%' }}>
                <button
                  type="button"
                  onClick={() => this.dismiss(item.objectID)}
                >
                  Dismiss
                </button>
              </span>
            </div>
          )}
      </div>
    )
  }
}

改写顶层组件如下:

export default class extends Component {
  //...

  render() {
    <div className="page">
      <Search
        value={searchTerm}
        onChange={this.search}
      />
      <Table
        list={list}
        pattern={searchTerm}
        onDismiss={this.dismiss}
      />      
    </div>
  }
}

button也可以抽取成为单独的组件:

export default class Button extends Component {
  render() {
    return(
      <button
        type="button"
        onClick={onClick}
      >
        {value}
      </button>
    )
  }
}
  • 使用children属性

React组件的props提供了children属性,代表组件的子元素,可以对Search,Button组件使用:

<Search
  value={searchTerm}
  onChange={this.search}
>
  Search
</Search>
// use children
export default class Search extends Component {
  render() {
    const {value, onChange, children} = this.props;
    return (
      <form>
        {children} <input
          type="text"
          onChange={onChange}
        >
        </input>
      </form>
  }
}
  • 引入无状态组件

以上提取的3个组件,基本都不需要维护自身状态,可以改写为无状态组件:

export default function Search(props) {
  const { value, onChange, children } = props;
  return (
    <form>
      {children} <input
        type="text"
        value={value}
        onChange={onChange}
      />
    </form>
  )
}

另外两个组件也可以做同样的处理。无状态组件除了语法简洁、便于测试等优点外,无状态的特性使得它没有React组件的生命周期管理,因此在渲染时相比于React组件有更好的性能。

  • 更纯的函数式组件

props可以通过解构直接当做函数参数,然后直接加上箭头函数进行改造,省掉函数内部的props参数解析,并且直接去掉return关键字:

export default ({ value, onChange, children }) =>
  <form>
    {children} <input
      type="text"
      value={value}
      onChange={onChange}
    />
  </form>

以上处理也同样适用于另外两个提取的组件。

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