Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@MasamotoMiyata

reactで同じ条件で複数のプロパティーを分岐する

概要

reactのJSXの中で条件分岐に使う便利ツールです。

問題提起

react+jsxで、例えばあるボタンを、ユーザーの権限が無い時使用不能にし、かつクラスをつけて見た目も変えたいとします。多分こんな感じのコードになるんじゃないでしょうか?

<button
  className={classNames('btn btn-primary', {deny: can('manage', 'guest')})}
  onClick={e => {
    if(can('manage', 'guest')){
      this.handleSendButton()
    } else {
      this.showAclError()
    }
  }}
>
  送信
</button>

このコードの問題点は同じ条件分岐can('manage', 'guest')が二箇所に書かれていることです。

これを一つのIF文で分岐することも可能ですが・・・

{can('manage', 'guest') ? (
  <button
    className="btn btn-primary deny"
    onClick={e => this.handleSendButton()}
  >
    送信
  </button>
) : (
  <button
    className="btn btn-primary"
    onClick={e => this.handleSendButton()}
  >
    送信
  </button>
)}

今度はタグや子要素が二つ複製されて変更が面倒になります。

解決策

下記のようにかけるツールを作りました。

<button
  {..._if(can('manage', 'guest'), {
    onClick: e => this.handleSendButton()
  }).else({
    onClick: e => this.showAclError(),
    className: 'deny'
  }).any({
    className: v => classNames(v, 'btn btn-primary')
  }).yield()}
>送信</button>

_if()の最初の引数がtrueだったら2番目の引数のオブジェクトが、falseだったらelse()のオブジェクトがプロパティーに採用されます。any()には前段のif/else/(elsif)で採用された値を引数に関数が呼び出され、その返り値がプロパティーの値になります。関数を使って値を加工しつつマージするようなイメージです。

selectタグなんかは権限のないときはmousedownpreventDefaultして警告を出したい時なんかもあります。そんなときはこんな風にもかけます。

<select
  {..._if(can('manage', 'guest'), {
    onChange: e => this.handleChangeSelect(e)
  }).else({
    onMouseDown: e => this.showAclError(),
    onChange: () => {}, //valueがあってonChangeがないと警告がでるので
    className: 'deny'
  }).any({
    className: v => classNames(v, 'form-control')
  }).yield()}
  value={this.props.selected}
>
  <option value="foo">foo</option>
</select>

実装はこんな感じです。今回例にはあげてませんがelsifもあります。

class ConditionalObject
{
  constructor(condition, props){
    this.conditions = []

    this.conditions.push({
      condition: condition,
      props: props
    })

    this.anyObject = {}
  }

  elsif(condition, props){
    this.conditions.push({
      condition: condition,
      props: props
    })
    return this
  }

  else(props){
    this.conditions.push({
      condition: true,
      props: props
    })
    return this
  }

  any(props){
    this.anyObject = props
    return this
  }

  _processAny(targetObject){
    const props = {...targetObject}

    for(var key in this.anyObject){
      // anyは関数。決定したpropsを引数に呼び出し上書きします。
      props[key] = this.anyObject[key](props[key])
    }

    return props
  }

  yield(){
    const result = this.conditions.find(cond => cond.condition)
    return this._processAny(result.props)
  }
}


export default function _if(condition, props){
  return new ConditionalObject(condition, props)
}

単純に、条件分岐で返すオブジェクトを変更する仕組み、ということでしょうか。classNameを想定してanyは関数を渡すようにしたところが唯一の工夫ですかね。

関係ないけどanyの名前は悩みました。merge, default finally both eitherとか・・・anyも含め正直どれもピンとこなかったのでタイプ量でanyにしたのが正直なところです。ネイティブな人だったら何にするんだろうか?あと念のためですがelsifはタイポではありません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?