添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Typing everything in TypeScript comes with a lot of surprises because oftentimes the error you’re given is cryptic. Sometimes the error doesn’t say “you’re missing X”. It mostly only infers “You are not conforming to these requirements”.  For this reason, you have to actually use your analytic skills to figure out the problem root problem.

One thing that does help to debug a TypeScript problem is to understand what you’re expecting and what you’re actually encountering. In my case I was seeing an error of “undefined” coming from a type definition where I never specified anything about undefined. That was the biggest clue as to what was going on.

const songStatusReducer: Reducer<songstatusstate songstatusaction=""> = (state: SongStatusState, action: SongStatusAction) => { switch (state.status) { case 'INIT': if (action.type === "SET_SONGIDS") { let songIndicatorMap = new Map() action.songIds.forEach((value) => songIndicatorMap.set(value, false)) return { status: "READY", currentPlayingSong: null, songIndicatorMap break; case 'READY': if (action.type === "TOGGLE") { if (state.currentPlayingSong !== action.songId) { for (let key in state.songIndicatorMap) { state.songIndicatorMap.set(key, false) state.songIndicatorMap.set(action.songId, true) state.currentPlayingSong = action.songId return {...state} if (state.currentPlayingSong === action.songId) { let currentSongStatus = state.songIndicatorMap.get(action.songId) state.songIndicatorMap.set(action.songId, !currentSongStatus) return {...state} break; <p>So this reads as, “The type <i>Reducer</i> takes two generic type parameters, <i>S</i> and <i>A</i>. The type passed in as <i>S</i> will be used for the <i>prevState</i> function argument and the return type of the function. The type passed in for A will be used for the function argument <i>action</i>.”</p><p>One thing I realized here for the <i>Reducer</i> type is that a return type is part of the requirement of making that type valid. My reducer <b>didn’t</b> specify a return type. So that’s what I did next. Now my reducer signature looked like the following:</p> </div> <xmp>const songStatusReducer: Reducer<songstatusstate songstatusaction=""> = (state: SongStatusState, action: SongStatusAction): SongStatusState => { <p>This will make sense when you realize that in Javascript <span style="letter-spacing: -0.4px;">a default value will be returned</span><span style="letter-spacing: -0.4px;"> if you don’t add a return statement to a function. The default that is returned is </span><i style="letter-spacing: -0.4px;">undefined</i><span style="letter-spacing: -0.4px;">. So basically I wasn’t covering a situation in my reducer where it was possible to return a value of </span><i style="letter-spacing: -0.4px;">undefined</i><span style="letter-spacing: -0.4px;">.</span></p><p>The fix was simple and I had two options. Option #1 was to just add a return value passing back the previous state. Option #2 was to halt the execution of the function and therefore not even allowing a return value to happen. That’s possible by throwing an <i>Error </i>object. I chose this path because I consider that if my reducer is given a status that is invalid, that status should be rejected.</p><p>Given that, here’s the final reducer. Notice the error I throw and where I throw it:</p> </div> let songIndicatorMap = new Map() action.songIds.forEach((value) => songIndicatorMap.set(value, false)) return { status: "READY", currentPlayingSong: null, songIndicatorMap break; case 'READY': if (action.type === "TOGGLE") { if (state.currentPlayingSong !== action.songId) { for (let key in state.songIndicatorMap) { state.songIndicatorMap.set(key, false) state.songIndicatorMap.set(action.songId, true) state.currentPlayingSong = action.songId return {...state} if (state.currentPlayingSong === action.songId) { let currentSongStatus = state.songIndicatorMap.get(action.songId) state.songIndicatorMap.set(action.songId, !currentSongStatus) return {...state} break; throw new Error('Invalid status')