3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-31

概要

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はタイポではありません。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?