質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

7回答

10986閲覧

JSのundefined対策について

TakakiKuwabara

総合スコア38

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

2グッド

1クリップ

投稿2017/11/01 05:22

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}
tanat, Lhankor_Mhy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答7

0

まずは、何が問題であるのかがはっきりさせることが重要であると考えられます。問題を細かく分けると次の三つに分かれると思います。

  1. undefinedになってしまうことが問題
  2. undefinedになってしまうことを想定していなかったことが問題
  3. 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では、定義されていない、存在しない、未初期化である、何も無い、といった表現をするときundefinednullのどちらかが使われます。そのため、「存在」と「非存在」にオブジェクトを分けるのであれば、「undefinedでもnullでもない」と「undefinednullである」という分け方になります。このような分け方で便利なのは、x != nullという記述です。

x != nullxundefinedでも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

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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
HayatoKamono

総合スコア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
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

undefinedのチェックは最初から想定していればさほど問題ないでしょう

javascript

1if(typeof a==="undefined") a=1; 2console.log(a); 3try{b}catch(e){b=2;} 4console.log(b);

投稿2017/11/01 07:20

yambejp

総合スコア114843

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

単純な話、対象の配列を空の配列で初期化しとけばいいだけの話。それで解決。

投稿2017/11/01 07:25

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/11/01 12:53

reactならdefaulPropsを適切に設定すりゃ良い話
guest

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

Lhankor_Mhy

総合スコア36115

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問