
JavaScriptのundefined対策について、スマートな方法がないか試行錯誤しています。
例えば下記のList.jsの例では、msgList配列をmapでループする場合、msgListそのものがundefinedになり、エラーが発生する場合があります。
この対策をしようとした場合、もう一つ下の例のように、if文で変数ごとにチェックを入れる必要があります。
現在はこの方法で変数ごとに確認させていますが、どうにもこれ以外の綺麗な方法がないものでしょうか。
mapの際にundefinedならループしない、とかなんとか。。。
皆さんはどのようにエラーチェックをしていますか?
JavaScript
1// List.js 2class List extends Component { 3 render() { 4 const { children } = this.props; 5 let msgList = children['msg_list']; 6 7 return() { 8 <div> 9 // msgListがundefinedになる場合がある 10 msgList.map((msgRow, i) => { 11 <a>msgRow['msg']</a> 12 }) 13 </div> 14 } 15 } 16}
JavaScript
1// List.js 2class List extends Component { 3 render() { 4 const { children } = this.props; 5 // エラー対策 6 let msgList = (children['msg_list'] != undefined) ? children['msg_list'] : []; 7 8 return() { 9 <div> 10 msgList.map((msgRow, i) => { 11 <a>msgRow['msg']</a> 12 }) 13 </div> 14 } 15 } 16}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答7件
0
まずは、何が問題であるのかがはっきりさせることが重要であると考えられます。問題を細かく分けると次の三つに分かれると思います。
undefined
になってしまうことが問題undefined
になってしまうことを想定していなかったことが問題undefiend
になってしまうことは想定しているが、その場合の処理方法の書き方の問題
###1.と2.
少し単純化して関数に渡す場合を考えます。
JavaScript
1function listDouble(obj) { 2 const myList = obj.list; 3 return myList.map(x => x * 2); 4} 5 6const test1 = { 7 id: 0, 8 list: [2, 3, 5] 9}; 10 11const test2 = { 12 id: 0, 13}; 14 15console.log(listDouble(test1)); 16console.log(listDouble(test2));
さて、listDouble()
に渡されるオブジェクトについて、list
プロパティに数値の配列を持つ場合はうまくいきますが、test2
のように持たない場合はエラーになってしまいます。このエラーになる原因は、渡している引数があっていない(つまり1.)か、渡している引数を想定していない(つまり2.)であるかで対応が異なります。引数のオブジェクトについて、list
プロパティが数値の配列があることが必須であると考えるのであれば、修正するべきはtest2
の内容やtest2
を使って呼び出しているところです。逆に、list
プロパティは別に無くても良いと考えるのであれば、修正すべきは、そのような引数を想定していなかったlistDouble()
の中身です。あとは粛々と修正すれば良いだけとなります。
と、言いたいところですが、一つ問題があります。上は実行するまでそのような問題があるかどうかわからないということです。上は単純だからすぐにわかりますが、より複雑な処理になった場合、想定外の引数を渡してしまったり、渡される引数に想定漏れがあったりすることはよくあることです。これが顕在化するのは実行時のみなのであり、さらに特定の状況のみそうなるのであれば、見つかりにくいバグとなってしまいます。
でも、安心してください。これを実行する前から発見する方法はあります。それはJavaScriptに静的型付けをもたらすFlowまたはTypeScriptを使った場合です。
JavaScript
1// @flow 2 3// 数値の配列であるlistプロパティが必須の場合 4function listDouble(obj /*: {list: number[]} */) /*: number[] */ { 5 const myList = obj.list; 6 return myList.map(x => x * 2); 7} 8 9const test1 = { 10 id: 0, 11 list: [2, 3, 5] 12}; 13 14const test2 = { 15 id: 0, 16}; 17 18console.log(listDouble(test1)); 19console.log(listDouble(test2));
JavaScript
1// @flow 2 3// listプロパティはあっても無くても良い場合 4function listDouble(obj /*: {list?: number[]} */) /*: number[] */ { 5 const myList = obj.list; 6 return myList.map(x => x * 2); 7} 8 9const test1 = { 10 id: 0, 11 list: [2, 3, 5] 12}; 13 14const test2 = { 15 id: 0 16}; 17 18console.log(listDouble(test1)); 19console.log(listDouble(test2));
上はFlowでの例です。flow check
でチェックを行うと、一番目は渡しているtest2
がおかしい、二番目はundefined
を考慮していない、ということを示すエラーが表示されます。実際に実行しなくてもチェックできるため、事前に想定外だったところを修正できます。もう、undefined
だったらどうしようとか、undefined
であることを見逃していたらどうしようとか、悩む必要は無くなります。これが静的型付けの威力です。また、単に静的型付けであればいいというのではなく、重要なのはnull安全であるかどうかです。null安全で無い静的型付けなんてタコが無いタコ焼きと同じです。
###3.
渡す側を修正するのは良いですが、渡される側を修正する場合、つまり、undefined
でもいいよとする場合、どう書くべきなのでしょうか?JavaScriptでは、定義されていない、存在しない、未初期化である、何も無い、といった表現をするときundefined
とnull
のどちらかが使われます。そのため、「存在」と「非存在」にオブジェクトを分けるのであれば、「undefined
でもnull
でもない」と「undefined
かnull
である」という分け方になります。このような分け方で便利なのは、x != null
という記述です。
x != null
はx
がundefined
でもnull
でも無いときだけtrue
になります。これを使って先ほどのコードを修正しましょう。
JavaScript
1// @flow 2 3// listプロパティは数値の配列、または、無し。無い場合は空配列を返す。 4function listDouble(obj /*: {list?: number[]} */) /*: number[] */ { 5 const myList = obj.list != null ? obj.list : []; 6 return myList.map(x => x * 2); 7} 8 9const test1 = { 10 id: 0, 11 list: [2, 3, 5] 12}; 13 14const test2 = { 15 id: 0 16}; 17 18console.log(listDouble(test1)); 19console.log(listDouble(test2));
flow check
はエラーになりませんし、普通に実行してもエラーになりません。このように、先ほどの静的型付けにしても、その後のコードがundefined
にならない事を判断してくれます。
上の書き方はやや冗長に感じます。もっと簡単な書き方は無いのでしょうか?無い事は無いのですが、その場合はJavaScriptを捨ててなんらかのaltJSを使わなければなりません。たとえば、CoffeeScriptでは次のように書けます。
CoffeeScript
1listDouble = (obj)-> 2 myList = obj.list ? [] 3 myList.map (x) -> x * 2 4 5test1 = 6 id: 0 7 list: [2, 3, 5] 8 9test2 = 10 id: 0 11 12console.log(listDouble(test1)) 13console.log(listDouble(test2))
CoffeeScriptの?
は存在演算子と言われるものです。残念ながら、JavaScriptには存在しません。falsyのチェックに使う||
は完全な代替とはなりません。
投稿2017/11/01 12:30
総合スコア21751
0
前提
(1).Reactの利用を考慮して、それをふまえて何が出来そうか検討してみました。
(2).children['msg_list']
から、親コンポーネントのchildrenは単一であることを回答の前提としました。
解決策1
コンポーネントを使う側のコンポーネントで対象プロパティーに空配列を初期値として渡してあげる。
ちなみに、使われる側のコンポーネントのdefaultPropsでchildrenの初期値を設定しようと試みたところ、childrenの初期値は設定不可のようでした。
import React, { Component } from 'react'; import Sample from './Sample'; class App extends Component { render() { // 配列messagesを空配列で初期化 const obj = {numbers: [1, 2, 3], messages: []}; return <Sample>{obj}</Sample>; } } export default App;
import React, { Component } from 'react'; class Sample extends Component { static defaultProps = { //messageはSampleコンポーネントに渡していないpropsだけど、 //defaultPropsを設定することでrender時に表示される message: 'This is a single message.', // しかし、childrenに関してはdefaultPropsでchildren.messagesの初期値を設定しても // 初期値として適用されないので、以下の設定は意味がない children: { messages: ['message1', 'message2'] } }; render() { return ([ <p key={1}>numbers: {this.props.children.numbers.map(number => number)}</p>, <p key={2}>messages: {this.props.children.messages.map(message => message)}</p>, <p key={3}>message: {this.props.message}</p> ]) } } export default Sample;
解決策2
使われる側のコンポーネントの方で親コンポーネントから渡ってきたchildrenを利用して、初期値を設定したchildrenのコピーを生成し利用する。
import React, { Component } from 'react'; import Sample from './Sample'; class App extends Component { render() { // messagesを空配列で初期化しないバージョン const obj = {numbers: [1, 2, 3]}; return <Sample>{obj}</Sample>; } } export default App;
import React, { Component } from 'react'; class Sample extends Component { static defaultProps = { message: 'This is a single message.', }; render() { const defaultChildren = { messages: [], numbers: [] }; // shallow copyなのでchildrenのネストが深い場合は要注意 const children = Object.entries( this.props.children ).reduce((accumulator, child) => { const KEY = child[0]; const VALUE = child[1]; accumulator[KEY] = VALUE; return accumulator; }, defaultChildren); return ([ <p key={1}>numbers: {children.numbers.map(number => number)}</p>, <p key={2}>messages: {children.messages.map(message => message)}</p>, <p key={3}>message: {this.props.message}</p> ]) } } export default Sample;
解決策3
Object Destructuringで使われる方のコンポーネント側で対象プロパティーのデフォルト値を設定
import React, { Component } from 'react'; import Sample from './Sample'; class App extends Component { render() { // messagesを空配列で初期化しないバージョン const obj = {numbers: [1, 2, 3]}; return <Sample>{obj}</Sample>; } } export default App;
import React, { Component } from 'react'; class Sample extends Component { static defaultProps = { message: 'This is a single message.', // defaultPropsでchildren.messagesの初期値を設定しても初期値として適用されない children: { messages: ['message1', 'message2'] } }; render() { const { children } = this.props; // Object Destructuringで別途、デフォルト値を空配列として初期化 const { messages = []} = this.props.children; return ([ <p key={1}>numbers: {children.numbers.map(number => number)}</p>, <p key={2}>messages: {messages.map(message => message)}</p>, <p key={3}>message: {this.props.message}</p> ]) } } export default Sample;
出力イメージ
更新
解決策3を追記しました。
投稿2017/11/03 03:16
編集2017/11/03 08:57総合スコア2415
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
理想で語るなら、this.props.children.msg_list
で配列を期待しているのに、
変数が揃ってないのにrender
メソッドを叩き始めている時点で設計がおかしい。
例えば(a, b) => a / b;
という割り算の関数作ったとして、
引数bにStringや0を入れて動作しませんって騒がれても変な値入れんなって怒って終わりでしょ?
とはいうものの、現実ではいつの間にかundefinedになってしまう事は結構ある。
そういう時、JavaScriptは||
で括ればfalsyな値であれば右辺が入る。
JavaScript
1let msgList = children['msg_list'] || [];
つまり、こういう風に変更すれば簡易的な対応になるね。
Array以外のStringやNumber入れられたらエラー出るから全てのケースをハンドリングすることは難しい。
でも、それ以上を求めるなら関数叩く側の準備を怠らないように設計すれば十分。
※Reactだからpropの値を制御しきるのは難しいけど、Undefinedか配列かの二択くらいなら保証出来ると思うよ。
以下確認
JavaScript
1var children, msgList; 2 3children = {}; 4msgList = children['msg_list'] || []; 5console.log(msgList); // Array [] 6 7children = {msg_list: [123, 234]}; 8msgList = children['msg_list'] || []; 9console.log(msgList); // Array [ 123, 234 ]
投稿2017/11/01 06:58
編集2017/11/01 07:15総合スコア21409
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
単純な話、対象の配列を空の配列で初期化しとけばいいだけの話。それで解決。
投稿2017/11/01 07:25

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。


0
実際に使ったわけではありませんが、こういうのを思いついたことはあります。
javascript
1maybeArr = [1,2,3]; 2Object.assign([],maybeArr); 3/* 4[1,2,3] 5*/ 6maybeArr == Object.assign([],maybeArr); 7/* 8false 9*/ 10maybeArr = undefined; 11Object.assign([],maybeArr); 12/* 13[] 14*/
投稿2017/11/01 06:13
総合スコア37438
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
よい方法かどうか自信ないですが、
javascript
1// こういうのを用意しておく 2function orDefault(v, d) { 3 return (v === void 0) ? d : v; 4} 5 6... 7 8let msgList = orDefault(children['msg_list'], []);
投稿2017/11/01 05:54
総合スコア18404
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。