夢のかけら

Railsエンジニアの技術ブログ

【React】コンポーネントを再利用できるようにするリファクタリング

f:id:lampler:20210530073141p:plain 例えばこのような2つのコンポーネントがある。これらのコンポーネントの問題点は何だろうか?

親コンポーネント

state = {
  users: [{ id: 1, name: 'rin' }, { id: 2, name: 'sin' }]
}

render () {
  <div className="container">
    <ListGroup items={this.state.users} />
  <div>
}

子コンポーネント

const ListGroup = ({items}) => {
  return (
    <ul className="list-group">
      {items.map(item =>(
        <li key={item.id} className="list-group-item">
          {item.name}
        </li>
      ))}
    </ul>
  )
}

答え:再利用できないこと

コンポーネントは再利用してナンボである。今回の例において何が良くないかと言うと、コンポーネントが親からもらうオブジェクトの属性を決め打ちしてしまっていると言うである。つまり<li key={item.id} className="list-group-item"> {item.name}という箇所である。itemのプロパティidではなく_idだったら、nameではなくuserNameだったら、再利用できないよね?という話である。上のコードは子と親が密結合なのである。

リファクタリング

親から子にpropsとしてデータを渡す時に、オブジェクトの属性名も一緒に渡す。 子ではブラケット記法([])をつかって動的に属性を変更する。こうすることによって、子コンポーネントの再利用が容易になる。

親コンポーネント

state = {
  users: [{ id: 1, name: 'rin' }, { id: 2, name: 'sin' }],
  languages: [
    { number: 1, langName: 'JavaScript'}, 
    { number: 2, langName: 'typeScript' }
  ]
}

render () {
  <div className="container">
    <ListGroup items={this.state.users} textProperty="name" valueProperty="id"/>
    <ListGroup items={this.state.languages}  textProperty="langName" valueProperty="number"/>
  <div>
}

子コンポーネント

const ListGroup = ({items, textProperty, valueProperty}) => {
  return (
    <ul className="list-group">
      {items.map(item =>(
        <li key={item[valueProperty]} className="list-group-item">
          {item[textProperty]}
        </li>
      ))}
    </ul>
  )
}

ListGroupコンポーネントを再利用できるようになりました!

defaultPropsを設定してさらに見やすく

親から子へpropsでデータを渡す数は少ない方がいい。純粋に見やすくなる。  <ListGroup items={this.state.users} textProperty="name" valueProperty="id"/> ListGroupに渡すオブジェクトのほとんどがname,idプロパティを持っているとしよう。 こんな時はdefalut propsとして親の方で明示しなくても、子がデータを受け取ることができる。

親コンポーネント

state = {
  users: [{ id: 1, name: 'rin' }, { id: 2, name: 'sin' }],
  languages: [{ number: 1, langName: 'JavaScript'}, { number: 2, langName: 'typeScript' }]
}

render () {
  <div className="container">
    <ListGroup items={this.state.users} /> // 属性を渡さない
    <ListGroup items={this.state.languages}  textProperty="langName" valueProperty="number"/>
  <div>
}

子コンポーネント

const ListGroup = ({items, textProperty, valueProperty}) => {
  return (
    <ul className="list-group">
      {items.map(item =>(
        <li key={item[valueProperty]} className="list-group-item">
          {item[textProperty]}
        </li>
      ))}
    </ul>
  )
}

// 追加
// デフォルトでtextProperty = 'name'のように親からデータを受け取る
ListGroup.defaultProps = {
  textProperty: 'name',
  valueProperty: 'id'
}