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:
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(<App/>, div);
ReactDOM.unmountComponentAtNode(div);
});
it('renders the heading', () => {
const wrapper = shallow(<App/>);
expect(wrapper.find('h1').text()).toBe('Hello React');
});
Also, our component in App.tsx
:
import * as React from 'react';
class App extends React.Component {
public render() {
return (
<div>
<h1>Hello React</h1>
</div>
);
}
}
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
<h1>
. The render
method can, of course, have scope. Let’s define the
label there:
public render() {
const label = 'Hello React';
return (
<div>
<h1>{label}</h1>
</div>
);
}
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:
class App extends React.Component {
public label = 'Hello React'
public render() {
return (
<div>
<h1>{this.label}</h1>
</div>
);
}
}
We had to change the <h1>
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:
<h1 onClick={alert('Hello World')}>{this.label}</h1>
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:
<h1 onClick={() => alert('Hello World')}>{this.label}</h1>
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.
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:
{
"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 <h1>
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:
class App extends React.Component {
public label = 'Hello React'
public handleClick () {
alert('Hello World');
}
public render() {
return (
<div>
<h1 onClick={this.handleClick}>{this.label}</h1>
</div>
);
}
}
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:
<h1 onClick={() => this.handleClick}>{this.label}</h1>
…but we’re back to the bossy TSLint complaint. Instead, we can bind the arrow function to the component:
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 <h1>
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<HTMLAttributes<HTMLHeadingElement>, 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.