When Minesweeper shipped with Windows 3.1 it wasn’t simply to entertain. The addictive little game masked an ulterior motive: introducing users to the right button of the mouse. The two-button mouse is old news by now, but Minesweeper remains just the right size for a different introduction.
Over the next fifteen minutes, we’ll build a Minesweeper clone (github) that uses TypeScript, React, and Redux to build a robust browser application. It’s a simple, synchronous project, but it’s enough to introduce patterns that use Redux and TypeScript to help tame the complexity of much larger applications.
No time for configuration—the clock is ticking! We can use create-react-app-typescript to set up a project with sensible defaults. Fine tuning can come later.
$ create-react-app minesweeper — scripts-version=react-scripts-ts
$ cd minesweeper
$ yarn install
$ yarn add redux react-redux
$ yarn add @types/react-redux
Next, we’re going to cheat (but only a little). While we’ll use React for the frontend UI and Redux for persistence, we can save time by borrowing an existing model of the game itself.
This particular implementation happens to be friendly, local, and synchronous, but it’s easy to imagine moving
MinesweeperAPI to the far end of an HTTP request. Its behavior would remain the same in either case: given the current state of the
Game and a request to reveal or flag a single cell, the
MinesweeperAPI will return a new state updated to reflect the consequences of the request.
Next, we’ll need to tell TypeScript about the “shape” of the
Game. This consists of
Options—the size of the board and how many mines are hidden on it—a
moveCount, and several indices comprising the state of the board. The finished type declaration documents this nicely:
We’ve got a game—we just need somewhere to put it. We’ll prepare Redux to store and update the state of the
Game by mirroring the public
MinesweeperAPI in typed actions of our own:
Action union, the program won’t compile.
We’ll get even more benefit from static typing inside the store. One of the challenges of scaling Redux is the complexity of increasingly “deep” reducers, accessors, and state. By declaring their shape preemptively, the TypeScript compiler can warn us about typos, missing parameters, and broken references.
To keep things simple, we’ll build the store around the existing
Game type from the domain model. Even with this relatively flat data-structure, we’ll sleep easier at night with TypeScript’s assurance that the rest of the app is following the rules.
Break it! Try accessing
action.options within the
'REVEAL_LOCATION' branch, and check out the resulting compiler error.
Speaking of “check out”, the clock! Time’s tight, but we still have time for a bit of civic beautification.
To get things going, let’s
connect the Redux store to the component tree by wrapping a
document.getElementById('root') as HTMLElement
Next, we’ll scaffold a root application component and
connect it to the provided
TypeScript adds several new facets here. The
mapDispatchToProps functions passed into
connect each have their own type declarations, whose intersection represents the component’s own
Props. When the dust settles,
<App /> will receive the entire Redux
Game plus the three available actions and a
grid array to simplify rendering. In return for the added ceremony, we get type-safety all the way down: unknown actions, malformed props, and undefined references will now all trigger errors at compile time.
<App /> is wired into the store but isn’t much to look at yet. To fix that, let’s create a component representing a single cell on the grid:
The one thing the compiler won’t infer nicely are new data crossing the application boundary. When a cell is clicked, for instance, we need to tell TypeScript what sorts of events an independent event handler is prepared to receive:
The compiler can now verify that we’re handling the event correctly. If we pass on a
KeyboardEvent or attach the handler to something other than a
<button />, we’ll be greeted with an error at compile time.
We’re nearly there! We need two more things to get the game running inside the
<App />: a
componentDidMount hook to create the initial game state, and a
render method to put the
CellComponent grid on the screen.
Pencils down. 14:59. Tack on a beautiful design, and there it is: statically-verified Minesweeper in just under 15 minutes. We’ve left plenty of details for other TypeScript tutorials, but the patterns introduced here can carry over to projects of all sizes and shapes.
The finished game isn’t the prettiest thing. No tests, suboptimal rendering, and plenty of opportunity for the design team, but we’re finally to the part where TypeScript shines brightest. As the project evolves, all the assumptions embodied in our type declarations will be preserved for reference—and backed by a friendly warnings anytime something breaks the rules.
Check out the finished project on Github!
Create your free account to unlock your custom reading experience.