Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした