So - you used to be the javascript whizz-kid when we still bound events directly in the DOM, the master of jquery selection driven bindings, even a dabbler into other javascript transpilers/translators/pre-processors, and now you're suddenly feeling very out of touch with the entire eco-system?
Well, this article is for you (and so was I). Hopefully by spending less than an hour reading this, you'll be up to speed, and have saved some time compared to trying to piece these things together from various sources.
By the end, you can expect to have a basic understanding of how to set up a single page app (SPA) using the following trendy tech:
For your view layer (it is not a full framework, its for rendering HTML in a neat way), consisting of:
A cleaner/more useful language than plain old vanilla js (shout out to http://vanilla-js.com) consisting of:
An ES6+ to javascript transpiler (aka, compile from one language to another, transpile!) that will show you how to:
So, if you're here, I didn't scare (or bore) you away, so lets begin by setting up the directory.
If you're extremely lazy (what programmer isn't?), you can just start with the git repository here, as such:
git clone https://github.com/ahungry/js-2017 npm update
Which will create all the files needed to jump right into the step-by-step portion.
If you like doing things the hard way, you can set up the files as such - first, lets make the directory and pull in the pre-built React (we don't need to load it via npm or build from source, we aren't doing anything especially fancy, and this is closer to using it over a CDN anyways):
mkdir -p ./js-2017/src && cd ./js-2017
wget https://facebook.github.io/react/downloads/react-15.0.1.zip
unzip react-15.0.1.zip
cp -ar ./react-15.0.1/build ./
Then we will set up our Node dependencies, by initializing a new package.json file (how Node tracks dependencies - similar to composer.json in Composer (PHP) or pom.xml in Maven (Java) or system.asd in Quicklisp (Common Lisp)):
npm init -y npm install --save-dev webpack@beta babel-core babel-loader babel-preset-es2015 babel-preset-react
this may take awhile, as npm can be a little slow at times (look into the Node/npm replacement called yarn (https://yarnpkg.com/) if this bugs you).
Ok, before we get started on that, lets
Open a new file in the root of your project (assume you're in the directory js-2017) named index.html and put the following in it (quite a bit of this is copied from the React tutorial provided in the react-15.0.1.zip bundle):
<!DOCTYPE html> <!-- Webpack basic tutorial --> <!-- https://www.sitepoint.com/beginners-guide-to-webpack-2-and-module-bundling/ --> <html> <head> <meta charset="utf-8"> <title>Javascript for 2017</title> </head> <body> <h1>Basic Example with JSX and ES6 features using Webpack for modules</h1> <div id="clicker">Clicker goes here</div> <div id="container"> <p> To install React, follow the instructions on <a href="https://github.com/facebook/react/">GitHub</a>. </p> <p> If you can see this, React is <strong>not</strong> working right. If you checked out the source from GitHub make sure to run <code>grunt</code>. </p> </div> <h4>Example Details</h4> <p>This is written with JSX with Harmony (ES6) syntax and transformed for the browser using webpack.</p> <p> Learn more about React at <a href="https://facebook.github.io/react" target="_blank">facebook.github.io/react</a>. </p> <p> Learn more about Webpack at <a href="https://www.sitepoint.com/beginners-guide-to-webpack-2-and-module-bundling/" target="_blank"> sitepoint.com/beginners-guide-to-webpack-2-and-module-bundling/ </a>. </p> <script src="./build/react.js"></script> <script src="./build/react-dom.js"></script> <script src="dist/bundle.js"></script> </body> </html>
Now, you may notice dist/bundle.js doesn't exist yet - that's going to be the file built by webpack (using babel).
Same deal as before, for this, we're pretty much copy and pasting to get past this and get to the exciting stuff. In the file ./webpack.config.js, add the following:
// webpack.config.js const config = { context: __dirname + '/src', entry: './app.js', output: { path: __dirname + '/dist', filename: 'bundle.js' }, module: { rules: [{ test: /\.js$/, include: __dirname + '/src', use: [ { loader: 'babel-loader', options: { presets: [ ['react'], ['es2015', { modules: false }] ] } } ] }] } } module.exports = config
Lets run across it real fast - we're defining a const
(the new way
to create a 'variable' in ES6, however note that it is not quite
immutable as the term const would have you believe, nor is it
variable - we would use a let
for that).
We will talk about const/let/var a little bit later.
In this config file, you'll see it defines some basic pattern matches and include directories (all files in the ./js-2017/src directory that end in the .js extension), defines a rule that tells it to use a 'babel-loader' (the transpiler engine it will use) and some options to the loader (presets, or how it will know what transpiler rules to use - in this case, React preset is for JSX syntax, while es2015 is for the new ES6 syntax).
So, a quick divergence into const vs let vs var (the TLDR, avoid
var
unless you have a special case requiring the scoping it
provides, always prefer const over let, unless you require
re-assignment, as it gives a better indication of the intent/nature of what
you're setting into the variable):
So, in good old vanilla JS, we would typically always use a var
for
defining a variable, and in JS, the scoping is as such:
var name = 'Jimbo' // Think of me as 'A' function hiName () { var name = 'Jon' // Think of me as 'B' var name = 'Franklin' // Also think of me as 'B' alert('Hi ' + name) // This is going to print, 'Hi Franklin' } // When the scope ends, name goes back to its 'A' value, 'Jimbo' hiName() // Here the 'Hi Franklin' alert prints alert(name) // Here it prints 'Jimbo' function hiNameReplaced () { name = 'Bobby' // Think of me as 'C' alert('Hi ' + name) // This is going to print, 'Hi Bobby' } // When the scope ends, name is going to be set to its 'C' value ('Bobby')! hiNameReplaced() // Here the 'Hi Bobby alert prints alert(name) // Here it prints 'Bobby for (var i = 0; i < 2; i++) {}; alert(i) // Here it will print '2'
What? Well, without rescoping in the function context (by declaring
with a var
), any modification to the variable will change its bound
value in the next scope higher (up to the global scope). This is
typically referred to as lexical binding.
It will also allow the declared variable to escape a non-function
scope (see, the i
has escaped the for
loop scope).
It's easy to see how this could lead to an error if a programmer
accidentally left out a var
declaration.
Lets look at the previous example using the new let
declaration:
let name = 'Jimbo' // Think of me as 'A' function hiName () { let name = 'Jon' // Think of me as 'B' let name = 'Franklin' // Also think of me as 'B' - I'm going to error out with a redefinition error! alert('Hi ' + name) // But if I didn't (if prior line was removed), this is going to print, 'Hi Jon } // When the scope ends, name goes back to its 'A' value, 'Jimbo' hiName() // Here the 'Hi Jon' alert prints alert(name) // Here it prints 'Jimbo' function hiNameReplaced () { name = 'Bobby' // Think of me as 'C' alert('Hi ' + name) // This is going to print, 'Hi Bobby' } // When the scope ends, name is going to be set to its 'C' value ('Bobby')! hiNameReplaced() // Here the 'Hi Bobby alert prints alert(name) // Here it prints 'Bobby for (let i = 0; i < 2; i++) {}; alert(i) // Here it will throw an undefined variable error
So - let seems very close to var, right? Lexical scoping, similar keyword length (let and var, both 3 letters!).
For most usage, it is going almost identical - however where it
differs is that something that is let
is only able to be defined
once per scope, this helps reduce errors in code (the value itself can
be changed in the variable defined by let, it just cannot be re-let).
It also extends the function lexical scoping provided by var, and
grants it to all block enclosures (for
, if
, try
, catch
, etc.).
Oh cool, ES6 has immutable constants! Well, no, not quite - in ES6, a const functions just like let does in regards to scoping and redefinition. The main thing that separates it from let is that the variable itself cannot be redefined (it can be rescoped in blocks though!).
The reason it isn't immutable however, is that if your variable is an object (remember in JS, arrays are objects as well), the properties/values contained within can be modified without any issue.
Look at the following:
const a = 1 const a = 2 // This will throw an error about it already having been declared const b = [] b.push(1) // This will not error out, you'll now have b = [ 1 ]
Alright, so after that short divergence, lets get back to setting up our ES6 files.
We will set up our first entry point file, app.js with the following:
import Counter from './Counter' import ExampleApplication from './Example' var start = new Date().getTime(); setInterval(() => { ReactDOM.render( <ExampleApplication elapsed={new Date().getTime() - start} />, document.getElementById('container') ); }, 50); ReactDOM.render( <Counter />, document.getElementById('clicker') )
The two import files are to be defined soon, so at this point, all we're doing is introducing some JSX (a special syntax for creating DOM elements in JS - note it requires a transpiler to function, no browser natively supports it). Think of it is a very fancy macro from othre languages.
In this sample, we are having React create two new DOM elements that are defined in Counter.js and Example.js - one that re-renders every 50 milliseconds via setInterval, and one that renders once (but has a re-render routine in its class definition).
Both are very very popular in the JS ecosystem at the moment, so that alone may make them worth learning. React itself is a produce of Facebook, which again bolsters the product's credibility.
Given that, lets try to clarify these a little bit:
Without going too deep into all the performance capabilities React can provide to DOM rendering, it is essentially to the DOM what immutable functional programming is to programming languages.
Basically, it strives to ensure that the DOM is deterministic (given the same inputs / process on any given page, it will always produce the same results, no random state manipulation of DOM elements creating unpredictability).
Whether this saves development time / makes a product more maintainable is still a matter of opinion (I personally haven't seen enough React code re-use or gains over more traditional means to give a solid opinion - if you've seen some metrics on it, please comment below or email me).
Well, as mentioned, JSX is pretty close to a macro (think of a Common Lisp reader macro, or some C defmacro, but a bit fancier) - when the JSX parser sees the tags, it converts them to the appropriate document.createElement calls, and sets the attributes/nesting etc. as expected. It allows for what appears to be inline HTML/XML directly in a JS file.
Again, whether this is of net benefit or not remains to be seen (I think it makes it very difficult to judge the overall 'look' of a page by viewing snippets of code, when compared to viewing a more traditional template based DOM setup).
Just for good measure, these two snippets are equivalent (assuming you're loading your code through a JSX transpiler step):
const message = 'Hello world!' const jsxP = <p>{message}</p> // Notice no quotes or anything const jsxR = React.DOM.p(null, message) // The react syntax to make a tag
and in both cases, will produce roughly the following HTML (React will actually add a few attributes that it uses for tracking the DOM elements, but that isn't really relevant here):
<p>Hello world!</p>
So, we see we have an entry point that calls some custom ES6 classes that extend React classes in app.js.
Since our import lines were as such (in app.js):
import Counter from './Counter' import ExampleApplication from './Example'
this implies that we will be loading the files named Counter.js and Example.js relative to our app.js file (so, js-2017/src/Counter.js and js-2017/src/Example.js). It is possible to save files with the .jsx extension (and probably recommended for heavy JSX usage), but this complicates the webpack config, and requires imports to include the extensions.
So, lets take a look at Example.js:
class ExampleApplication extends React.Component { render() { const elapsed = Math.round(this.props.elapsed / 100); const seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' ); const message = `React has been successfully running for ${seconds} seconds.`; return <p>{message}</p>; } } export default ExampleApplication
One non-ES6 thing to realize is that the render
method being defined
is something React needs from us in our React component we are
extending. It tells React what to do (render) when the element needs
to be displayed to the DOM.
A few of the ES6 things going on here are:
So, remember the days of having to finagle with combining JS strings,
and running into issues with concatenation and numerics? (The +
is
used for both, and the loose typing can cause string concatenation
where addition is expected etc.).
Well, you can avoid that now with the ES6 backtick operator (any JS
expression can go in the ${...}
segment:
const name = 'Frank' const message = `Hello ${name}!` console.log(message) // Hello Frank!
ES6 provides an easy way to define a class. Lets check a very basic example:
class Foo { bar() { console.log('It worked!') } } // Is very roughly equivalent to this vanilla JS (ignoring constructors/new keyword) var Foo = { bar: function() { console.log('It worked!') } }
Wait, didn't vanilla JS already have classes? I know I saw the new
keyword used before…
function Foo () { this.bar = function() { console.log('It worked!') } } var Foo = new Foo()
So, the way of doing it like that in vanilla JS is that it is
basically defining a function that when invoked with the new
keyword
will return an object with properties initialized to what is defined
in the this
context (and also execute the code defined in the function).
So, back to the syntax - in addition to creating a new object with the class syntactic sugar, we are extending the React.Component object, this ensures that we inherit (basically, in JS context, just adding new properties to an object - JS still doesn't have inheritance as thought of in other languages) the React object, and get access to some React methods we will discuss in the Counter.js example.
This is the new age way of handling modules (if you've tried out Node in the past, you are likely familiar with require and module.exports calls - this is quite similar, with some extra features to it).
In particular, the line:
export default ExampleApplication
sets the 'default' object exported from this file to be whatever is stored in that variable (in this case, our class object).
When we eventually call:
import ExampleApplication from './Example'
we are grabbing whatever was set as the 'default' in the export, and
binding it to a variable called ExampleApplication (it's just
coincidence these two names match, we could have said
import Blub from './Example'
and have access to our
ExampleApplication class/object in the Blub variable).
Now lets look at Counter.js:
class Counter extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } handleClick(event) { this.setState({ count: this.state.count + 1 }) } render() { return <button onClick={ (e) => this.handleClick(e) }>Clicked me {this.state.count} times!</button> } } export default Counter
This introduces 2 more new ES features, the ES6 class constructor, and the arrow function.
So, the constructor is simply a method defined, that when invoking a new class instance, will be run as such:
class Abc { constructor(n) { console.log(n) } foo() { console.log('I am the parent') } } class Xyz extends Abc { constructor(n) { super(n) } bar() { super.foo() console.log('And I am the child') } } const parent = new Abc(1) // Will console.log '1' const child = new Xyz(2) // Will console.log '2' child.bar() // Will console.log 'I am the parent', 'And I am the child'
by way of extending the class, it allows you to call the
parent class constructor, by using the super
method call
(so, in this case, it calls React.Component.constructor).
You can also call other parent methods by using super.methodName().
Ok, this is pretty sweet, a nice concise way to define an inline function / lambda / closure / anonymous function:
const myFn = function (a, b) { return a + b } // Is equivalent to a lambda/closure/anonymous function: const myFnArrow = (a, b) => { return a + b } // Which, when given a single expression in the body, is equivalent to a statement // with an inferred return value: const myTinyFn = (a, b) => a + b // and when used with a single argument, can be simplified even further to this expression: const myMicroFn = a => a + 1
So, if you've been following along, you may have tried loading index.html in your browser, and noticed that nothing good is happening. That's because you haven't transpiled the code yet! (and as of this writing, no major browsers natively support all of ES6 or JSX).
So, hop back to your command line, and run the following:
npm run build
and notice a new file is generated if all went well (no syntax errors etc.) in dist/bundle.js - this is your transpiled file. It is your entire application stored in one JS file you can now load in almost any current browser and have function without any issue (just make sure to load in the React build files included in the repo, or pull in from a CDN).
You can also use the command:
npm run start
which will cause webpack to actively watch/listen for changes in your JS files, and re-build as they are changed. This makes it very easy to program JS in the traditional fashion of saving changes, and reloading in the browser.
So, have any comments? Feel free to discuss using Disqus!