===========
JSX and ES6
===========
TypeScript is a JavaScript superset with a compiler that enforces the types.
It's also, though, one of those sexy new JavaScript flavors that implement
"ES6"...actually, a family of modern JavaScript standards for more
productive programming. When combined with other tooling, it also supports
JSX, React's sorta-templating system, but with TypeScript semantics.
We glazed over the ES6 and JSX (that is, TSX) in previous steps. Let's take
more of a look.
Cleanup
=======
The previous steps added some stuff to show their topics. Let's clean up a
little bit.
Let's change ``App.test.tsx`` back to two simple tests:
.. code-block:: jsx
import { shallow } from 'enzyme';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
ReactDOM.unmountComponentAtNode(div);
});
it('renders the heading', () => {
const wrapper = shallow();
expect(wrapper.find('h1').text()).toBe('Hello React');
});
Also, our component in ``App.tsx``:
.. code-block:: jsx
import * as React from 'react';
class App extends React.Component {
public render() {
return (
Hello React
);
}
}
export default App;
Make sure the test runner is still running and watching. You can, though,
shutdown the ``start`` task. We don't need it regenerating the bundle and
updating the browser, duplicating what's happening in the Jest process.
Our two tests pass. Let's see some ES6 and TSX.
Classes and Fields
==================
As you can see, this component is using React's support for ES6 classes.
Our ``App`` component extends ``React.Component`` which has a constructor that
initializes a bunch of stuff. We'll see functional components later.
Let's do another approach at extracting the hardwired string from the
````. The ``render`` method can, of course, have scope. Let's define the
label there:
.. code-block:: jsx
public render() {
const label = 'Hello React';
return (
{label}
);
}
The ``const`` is used because we never intend to re-assign the label. Our
tests pass, so this change worked fine.
We can also move the ``label`` up to the class label as a field:
.. code-block:: jsx
class App extends React.Component {
public label = 'Hello React'
public render() {
return (
{this.label}
);
}
}
We had to change the ```` to use ``this.label``, to get the value off the
instance.
Arrow Functions
===============
ES6 introduced small, inline anonymous functions called "arrow functions".
The are incredibly useful and have come to dominate frontend frameworks. Let's
see them in action for click handlers. We'll start by showing something that
doesn't work until an arrow function saves the day.
We'll first do an inline click handler that displays a static string. Note
that JSX (and thus, TSX) map certain HTML attributes into first-class names,
such as ``onClick`` and ``className``, in its grammar, thus letting us
assign an expression (with ``{}``) instead of a string::
{this.label}
But this fails. Why? The expression is immediately evaluated, rather than
run when the event is fired. We need a way to assign something that will be
executed *later*, when the event is fired.
Arrow functions to the rescue! Try this instead::
alert('Hello World')}>{this.label}
What does this change do? It stores a function which is created on the fly
and stored "anonymously" in that scope. The ``()`` means this arrow function
needs no arguments. (It's actually passed an event, which we'll use in later
steps.) The function body is one line, so we don't need curly braces for a
block.
.. _bossy-tslint:
This is actually valid TypeScript, and would compile and run, but our picky
style linter complains. In React, defining functions on the fly is expensive
(when you're doing hundreds in a loop) and the TSLint community decided to
frown on that by default. We can override the default, though. Edit
``tslint.json`` to contain:
.. code-block:: json
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts"
]
},
"rules": {
"jsx-no-lambda": false
}
}
Restart your ``start`` script to get the linter to pick up the change.
Everything compiles fine and clicking on the ```` in the browser produces
an alert.
Move To a Method
================
Inline handlers aren't so smart, as they aren't easily testable. Plus, bossy
TSLint doesn't like them (for performance reasons.) Let's move the handler to
a component method:
.. code-block:: jsx
class App extends React.Component {
public label = 'Hello React'
public handleClick () {
alert('Hello World');
}
public render() {
return (
{this.label}
);
}
}
We referenced ``this.handleClick`` but we didn't call it. React will call it
later, when we actually click.
Clicking on the heading works well, so let's remove the
``"jsx-no-lambda": false`` rule from ``tslint.conf`` and restart the
``start`` script.
Let's have the alert display the label by changing it to
``alert(this.label);``. Uh-oh. Clicking on the heading produces a mile-long
traceback in the JavaScript console. The traceback mentions
``HTMLUnknownElement``. And that's the problem: the ``this`` in the
``handleClick`` method isn't the component instance, it is event. This is a
chronic problem in React programming, causing the ``.bind`` syntax.
Arrow functions, though, get the correct ``this``. We could change the handler
to the following::
this.handleClick}>{this.label}
...but we're back to the bossy TSLint complaint. Instead, we can bind the
arrow function to the component:
.. code-block:: typescript
public handleClick = () => {
alert(this.label);
}
Look at that freaky approach! Instead of a method, we are binding a dynamic
function to a class property. (Discussion below about the downsides.)
JSX
===
React brought innovation to the concept of templating languages by extending
JavaScript itself. Your templating is mixed directly into your JavaScript
file and component. TSX is the TypeScript flavor of JSX, with file extensions
ending in ``.tsx``.
The easiest way to see TSX in action? Go to your ```` and try to add
``class=""``. TypeScript itself has JSX/TSX support in the compiler and
gives a compiler error::
Property 'class' does not exist on type
'DetailedHTMLProps, HTMLHeadingElement>'.
Also, the IDE refuses to autocomplete on ``class``. It does, though,
autocomplete on ``className``, the JSX/TSX equivalent.
Accepting the autocomplete shows that the IDE fills in ``{}`` for an
attribute value instead of double-quotes. What's the difference? A
double-quote contains a regular string, whereas brackets contain JavaScript
expressions, which we saw above.
Note About Arrow Functions
==========================
Arrow functions look great on classes but behind the scenes they don't
really do what you think. Purists have pointed out the flaws (mockability,
subclassing, performance.) And yet, they remain a very popular solution to
binding in React and similar systems.