blob: f4eedbfe3232fe712c20127ee843ad8212d453a8 [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.10">
<title>Chess Game Tutorial (Part 2)</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
<link rel="stylesheet" href="./asciidoctor.css">
<!DOCTYPE html>
<!-- ************* Favicon ************-->
<link rel="icon" href="../../images/favicon.ico" />
<link rel="icon" type="image/png" href="../../images/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="../../images/favicon-16x16.png" sizes="16x16" />
<!-- ************* Back-to-top JQuery ************* -->
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
<!-- ************* Prism.js Syntax Highlighter ******-->
<link href="../../styles/prism.min.css" rel="stylesheet"/>
<script src="../../scripts/prism.js"></script>
<!-- ************* Styles ************* -->
<link rel="stylesheet" type="text/css" href="../../styles/n4js-adoc.css">
<!-- ****************** NavBar ****************** -->
<div id="menubar">
<div class="banner">
<a href="../../index.html"><img id="logo" src="../../images/n4js-logo.png" alt="N4JS Language and IDE"></a>
</div>
<ul>
<li><a href="../../downloads.html"></i>Download</a></li>
<li><a href="../../community.html"></i>Community</a></li>
<li><a href="../index.html">Documentation</a></li>
</ul>
</div>
<button id="tocbutton">TOC</button>
</head>
<body class="article">
<div id="header">
<h1>Chess Game Tutorial (Part 2)</h1>
<div id="toc" class="toc">
<div id="toctitle">Jump to topic</div>
<ul class="sectlevel1">
<li><a href="#_overview">Overview</a></li>
<li><a href="#_source_code">Source code</a></li>
<li><a href="#_redux_architecture_with_react">Redux Architecture with React</a></li>
<li><a href="#_connect_react_to_redux">Connect React to Redux</a></li>
<li><a href="#_store_chess_game_state_in_redux_store">Store Chess Game State in Redux Store</a></li>
<li><a href="#_test_game_rules_with_mangelhaft">Test Game Rules with Mangelhaft</a></li>
<li><a href="#_run_the_test">Run the test</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="_overview">Overview</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In part 1, we have discussed how to develop a chess game app with React and JSX in N4JS. There, we have stored the application state in the React component Game&#8217;s state. As applications become larger, however, the mix of application state and UI makes the application hard to comprehend and more difficult to test.</p>
</div>
<div class="paragraph">
<p>As a result, in large applications, <a href="https://redux.js.org/">Redux</a> - an implementation of <a href="https://facebook.github.io/flux/">Flux architecture</a> also created by Facebook - is often used to organize the application code. Redux strictly allows the data to flow in only one direction as shown in the following diagram. Also, Redux is UI agnostic and can be used in conjunction with any UI library. In this tutorial, we will extract the entire game state out of React components and store it in a Redux store.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_source_code">Source code</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The source code of the chess application can be found at <a href="https://github.com/Eclipse/n4js-tutorials/tree/master/chess-react-redux"><code>n4js-tutorials/chess-react-redux^</code></a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_redux_architecture_with_react">Redux Architecture with React</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In this section, we will review several key concepts of Redux when used with React. You should consult official Redux documentation for more details. When using React with Redux, we normally store the application state in Redux store instead of React components' state. As a result, React components become stateless UI elements and they simply render the UI using the data retrieved from the Redux store. In a Redux architecture, data flows strictly in one direction. The following diagram graphically depicts the action/data flow in a React + Redux app.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="images/redux-architecture.svg" alt="Redux architecture" width="70%">
</div>
</div>
<div class="paragraph">
<p>The action/data flow in the diagram can be roughly understood as follows:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>When a user interaction is triggered on the React component (e.g. button clicked, text field edited etc.), an action is created. The action describes the changes needed to be updated in the application state. For instance, when a text field is edited, the action created may contain the new string of the text field.</p>
</li>
<li>
<p>Then the action is dispatched to the Redux store whereby the Redux store stores the application state, usually as a hierarchical tree of state.</p>
</li>
<li>
<p>The reducers take the action and the current application state and create an updated application state.</p>
</li>
<li>
<p>If the changes in the application state are to a certain React component, they are forwarded into the component in form of props. The change in props causes the component to re-render.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_connect_react_to_redux">Connect React to Redux</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In a React + Redux app, we have stateless React components rendering UI elements on the one side. On the other side, we have a Redux store that stores our application state. We need to establish a connection between them.</p>
</div>
<div class="paragraph">
<p>In particular, when the user interaction is triggered on a React component, we need to turn this user interaction into actions that are then processed by reducers to create a new application state in the Redux store. Conversely, when the application state in the Redux store is changed, we need to inform the React components that are interested in the change so that they can re-render.
Fortunately, the npm library react-redux can establish exactly this connection for us.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="images/react-redux-connection.svg" alt="Redux architecture" width="70%">
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_store_chess_game_state_in_redux_store">Store Chess Game State in Redux Store</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Recall that in the previous implementation, the game state is stored as <em>state</em> in the React component Game. We move state out of Game component and store it in a Redux instead. In this section, we will provide some hints on the implementation. The reader should study the source code for the detailed implementation.</p>
</div>
<div class="paragraph">
<p>In Game component, we define two mapping functions needed by <code>react-redux</code> to connect Game component with the Redux store.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-typescript" data-lang="typescript">/**
* Map Redux state to Game's props
*/
function mapStateToProps(state: StoreState): GameProps {
return {
history: state.history,
stepNumber: state.stepNumber,
whiteIsNext: state.whiteIsNext,
pickedSquare: state.pickedSquare,
validDestinations: state.validDestinations
}
}
/**
* Map Game's events to Redux actions
*/
function mapDispatchToProps(dispatch: {function(ReduxAction): any} ) {
return {
createPickSquareAction: (pos: Coordinate) =&gt; {
dispatch(createPickSquareAction(pos))
},
jumpToStep: (step: number) =&gt; {
dispatch(createJumpToStepAction(step))
}
}
}
export public const ConnectedGame = connect(mapStateToProps, mapDispatchToProps)(Game);</code></pre>
</div>
</div>
<div class="paragraph">
<p>Whenever a square is clicked, an instance of <code>PickSquareAction</code> is created. A redux action has be a subclass of <code>ReduxAction</code>. Note that, react-redux requires that every action has a <code>type</code>. In N4JS, we can use the fully qualified name of the class for this purpose. Hence, we define a base class <code>ActionBase</code> for all actions.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-typescript" data-lang="typescript">/**
* Base Redux action. Its type is the FQN of the class
*/
export public class ~ActionBase implements ReduxAction {
@Override
public type: string = this.constructor.n4type.fqn;
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>PickSquareAction</code> class and function creating an instance of that class are very simple</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-typescript" data-lang="typescript">/**
* Pick square action
*/
export public class PickSquareAction extends ActionBase {
/**
* The coordinate of the picked square
*/
public coord: Coordinate;
public constructor(@Spec spec:~i~this) {}
}
/**
* Create a PickSquareAction
*/
export public function createPickSquareAction(pos: Coordinate): PickSquareAction {
return new PickSquareAction({ coord: pos });
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>A reducer modifies the state in the Redux store based on an action. For the purpose of this application, we define a single reducer that is interested in all actions. An implementation of the reducer chooses the actions it is interested and returns a new state from the old one based on the action.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-typescript" data-lang="typescript">/**
* For this demo application, this reducer is interested in all actions.
*/
export public function reducer(state: StoreState, action: ReduxAction): StoreState {
if (!state) {
state = initialState;
}
const { history, stepNumber, whiteIsNext } = state;
switch (action.type) {
case PickSquareAction.n4type.fqn: // This reducer is interested in this action
...
case JumpToStepAction.n4type.fqn: // This reducer is interested in this action
...
default:
// Irrelevant action, return the old state
return state;
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>When the reducer returns a new state, that state is passed through the function <code>mapStateToProps</code> which selects the props that are relevant to Game component. The Game component then re-renders with the updated props.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_test_game_rules_with_mangelhaft">Test Game Rules with Mangelhaft</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Since the focus of the tutorials is on N4JS, we have not discussed test yet. In a real world project, however, it is extremely important to have tests, especially unit tests, to assure the correctness of the application. The good news is, unit tests for N4JS applications can be written very conveniently in our xUnit-based test framework Mangelhaft that provides assertion methods resembling JUnit&#8217;s familiar to Java developers.</p>
</div>
<div class="paragraph">
<p>For instance, we will write a Mangelhaft test that tests the implementation of the reducer. In particular, we test that given the initial arrangement of the chess board, if an action <code>PickSquareAction</code> is created for the coordinate <code>(0,1)</code> (i.e. the left white knight is picked), the valid destinations are updated correctly in the new state. One possible way of expressing this test in Mangelhaft is as follows:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-typescript" data-lang="typescript">export public class ReducePickSquareActionTest {
private state: StoreState;
@Before
public init() {
this.state = initialState;
}
@Test
public knightValidDestinationsTest() {
const pickSquare = new Coordinate({row: 0, col: 1});
const pickLeftWhiteKnight = createPickSquareAction(pickSquare);
const newState = reducer(this.state, pickLeftWhiteKnight);
Assert.equal(newState.validDestinations.length, 2, 'Must have 2 valid destinations');
for (const validDest of newState.validDestinations) {
Assert.equal(validDest.row - pickSquare.row, 2, 'Wrong row');
Assert.equal(Math.abs(validDest.col - pickSquare.col), 1, 'Wrong column');
}
}
...
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Note that the way we are testing the game logics is completely UI-agnostic and no React components are involved at all! This is thanks to the decoupling of game logics from UI with the help of Redux.</p>
</div>
<div class="paragraph">
<p>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/themes/prism.min.css"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/prism.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/components/prism-typescript.min.js"></script>
</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_run_the_test">Run the test</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can execute the test cases either on the command line or in the N4JS Eclipse IDE.
On the command line, run the tests by the defined npm script:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">npm run test</code></pre>
</div>
</div>
<div class="paragraph">
<p>To run the tests in the IDE, open the test file and right-click somewhere in its editor.
In the context menu select "Run as" &#8594; "Test in Node.js".</p>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
</div>
</div>
<!DOCTYPE html>
<div class="Grid social" style="color:#d5dfea">
<div class="Cell Cell--2-12 m-Cell--withMargin">
<h2>Quick Links</h2>
<ul>
<li><a href="../downloads.html">Download</a></li>
<li><a href="index.html">Documentation</a></li>
<li><a href="https://github.com/eclipse/n4js/">Source</a></li>
<li><a href="https://github.com/eclipse/n4js/issues">Issues</a></li>
</ul>
</div>
<div class="Cell Cell--2-12 m-Cell--withMargin">
<br/><br/>
<ul>
<li><a href="https://www.eclipse.org/forums/index.php/f/365/">Forum</a></li>
<li><a href="http://n4js.blogspot.de/">Blog</a></li>
<li><a href="https://dev.eclipse.org/mailman/listinfo/n4js-dev">Mailing List</a></li>
<li><a href="https://projects.eclipse.org/projects/technology.n4js">Eclipse Project Page</a></li>
<li><a href="https://twitter.com/n4jsdev">Tweets by n4jsdev</a></li>
</ul>
</div>
<div class="Cell Cell--2-12 m-Cell--withMargin">
<br/><br/>
<ul>
<li><a href="http://www.eclipse.org/">Eclipse Home</a></li>
<li><a href="http://www.eclipse.org/legal/privacy.php">Privacy Policy</a></li>
<li><a href="http://www.eclipse.org/legal/termsofuse.php">Terms of Use</a></li>
<li><a href="http://www.eclipse.org/legal/copyright.php">Copyright Agent</a></li>
<li><a href="http://www.eclipse.org/legal/">Legal</a></li>
</ul>
</div>
<div style="clear: both; height: 0; overflow: hidden;"></div>
</div>
<script>
// Toggle the table of contents
$( "button#tocbutton" ).click(function() {
if ($("#tocbutton").css('right') == '25px') {
$( "#tocbutton" ).animate({right: '215px'},"slow");
$( "#toc.toc2" ).animate({right: '0'},"slow");
}
else {
$( "#tocbutton" ).animate({right: '25px'},"slow");
$( "#toc.toc2" ).animate({right: '-13rem'},"slow");
}
});
</script>
<script type="text/javascript">
// Create a back to top button
$('body').prepend('<a href="#" class="back-to-top">Back to Top</a>');
var amountScrolled = 300;
$(window).scroll(function() {
if ( $(window).scrollTop() > amountScrolled ) {
$('a.back-to-top').fadeIn('slow');
} else {
$('a.back-to-top').fadeOut('slow');
}
});
$('a.back-to-top, a.simple-back-to-top').click(function() {
$('html, body').animate({
scrollTop: 0
}, 700);
return false;
});
</script>
</body>
</html>