概要
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タグなんかは権限のないときはmousedown
でpreventDefault
して警告を出したい時なんかもあります。そんなときはこんな風にもかけます。
<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
はタイポではありません。