Our team recently decided to adapt react.js in our new product, so I start to research this new library. Apparently frontend develop has advanced lately, web components are introduced, and build(webpack) and packaging tools(npm) are being added like backend development.
In this page, I would walk through the process of building a modern webapp using react.js and webpack.
I assume you are using Linux/Mac, and have http://nodejs.org/Node.js installed.
Let's initialize the project:
mkdir new-react-project cd new-react-project npm init
The above commands create package.json file, it defines the meta information of the project.
The project we are going to build requires react.js(obviously) and jquery(mostly for ajax request), so we add them to the project:
npm install --save react jquery
We also need to use webpack dev tool for this project, so we do this:
npm install -g webpack webpack-dev-server
Additionally, react uses optional XML-like syntax JSX, so we need a special loader to compile JSX to plain javascript:
npm install --save-dev jsx-loader
Now we create `webpack.config.js` file, which tells webpack how to build this project, see inline comments for explaination:
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
publicPath: 'http://localhost:9002/assets'
},
module: {
loaders: [
{
test: /\.jsx$/,
// harmony enable ES6 features
loader: 'jsx-loader?insertPragma=React.DOM&harmony'
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
}
}
Now we want to have an index page showing "hey yo" alert on load: index.html
<html>
<body>
<div id='content'></div>
<script type="text/javascript" src="http://localhost:9002/assets/bundle.js"></script>
</body>
</html>
Obviously, we don't have bundle.js yet, the idea of using webpack is pack all your resources in this bundle.js. Remember we added "index.js" in webpack.config.js as entry file? This file is the main script, let's add the alert here to it: index.js
alert("hey yo");
Start dev server:
webpack-dev-server --port 9002
Open http://localhost:9002, you now get the alert.
But what's the point of doing all these? You may ask, it looks complicated!
Well, the purpose of this is enabling web components, if you're a backend development, you might find ant and maven are your good friends, they take an manifest (build.xml or pom.xml) file, and grab all the libraries and frameworks you need, pack everything in a war file, webpack serves the same purpose.
Before we dive into web components in react.js, let me show you an example of customized module first.
We all know is a better practice to put separated modules in separate places, it's good for unit testing and help us assemble larger webapp. So now, instead of put alert in index.js, we are going to create alert.js, so all pages include this js will have the alert. alert.js
module.exports = function() {
alert("hey yo");
}
and in index.js, we just do:
(require('./alert'))();
Refresh the page, then you got the same alert, you can easily create other modules and require them in index.js, let try a little more complex task, grab the latest sydney weather, then show it on index page: create weather.js:
'use strict'
var JQ = require('jquery')
module.exports = function() {
JQ(document.body).append('<div id="weather">Loading</div>');
JQ.getJSON( "http://api.openweathermap.org/data/2.5/weather?q=Sydney,NSW,Australia", function( data ) {
JQ('#weather').html('');
JQ('#weather').append('<h1>'+data.name+'</h1>');
var weather = data.weather[0];
var linktoicon = 'http://openweathermap.org/img/w/' + weather.icon + '.png';
JQ('#weather').append('<img src="'+linktoicon+'" />');
JQ('#weather').append('<span>'+weather.description+'</span>');
});
}
And in index.js:
//(require('./alert'))();
(require('./weather'))();
Yeah, that works well.
Let's take a look at React.js, I won't repeat what's react.js here as there are plenty resources available, just outline a few key advantages of it here:
well, that's boring :) just show me the code :)
Let's create a classic(boring) hello world component. JSX is XML-like javascript superset, consider it's the javascript you can directly write html instead quote them: Hello.jsx:
'use strict'
var React = require('react');
module.exports = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
rename index.js to index.jsx, so we can use jsx syntax to make our life easier, don't forget to change webpack.config.js's entry script accordingly:
var React = require('react');
var HelloTag = require('./Hello');
React.render(<HelloTag name="Dongsheng Cai" />, document.getElementById('content'))
Isn't it annoying to keep refreshing the page to see changes? Well, react.js does provide the tool to auto-refresh when code changes, simple add below line to index.html:
<script src="http://localhost:9002/webpack-dev-server.js"></script>
Note, you must use the full url here.
So I'm going to refactor the weather component, so that we can do this in the index.jsx:
React.render(<Weather />, document.getElementById('weather'))
It will also include search box, so I can query weather of other regions.
First rename weather.js to Weather.jsx, we are going to use a little JSX syntax here:
'use strict'
var JQ = require('jquery')
var React = require('react');
module.exports = React.createClass({
defaultState: {
name: 'Sydney',
icon: 'http://openweathermap.org/img/w/01d.png',
description: ''
},
getInitialState: function() {
return this.defaultState;
},
componentWillMount: function() {
this.loadWeather();
},
loadWeather: function() {
JQ.getJSON( "http://api.openweathermap.org/data/2.5/weather?q=Sydney,NSW,Australia", function( data ) {
var weather = data.weather[0];
var result = {};
result.name = data.name;
result.icon = this.getIcon(weather.icon);
result.description = weather.description;
this.setState(result);
}.bind(this));
},
getIcon: function(code) {
if (!code) {
code = '01d';
}
return 'http://openweathermap.org/img/w/' + code + '.png';
},
render: function() {
return <div>
<h1>{this.state.name}</h1>
<img src={this.state.icon} />
<p>{this.state.description}</p>
</div>;
}
});
We implemented two buildin callbacks here: componentWillMount and getInitialState, componentWillMount is being called before rendering the contents, it's perfect for fetching remote data, getInitialState initialize the component state, in this particular example, state is the weather information, which includes city name, weather icon and description.
We don't have a search box yet, it's going to be a sub-component, add these lines to render method:
render: function() {
var valueLink = {
value: this.state.searchname,
requestChange: this.handleChange
};
return <div>
<input name="cityname" type="text" valueLink={valueLink} />
<h1>{this.state.name}</h1>
<img src={this.state.icon} />
<p>{this.state.description}</p>
</div>;
}
Note, we add an input element, which has valueLink prop, it helps use create Two-Way binding with state, requestChange will try to call this.handleChange method to trigger text change event, in this method we will call the loadWeather method to update the weather, please see the full Weather.jsx:
'use strict'
var JQ = require('jquery')
var React = require('react');
module.exports = React.createClass({
defaultState: {
name: 'Sydney',
icon: 'http://openweathermap.org/img/w/01d.png',
description: ''
},
getInitialState: function() {
return this.defaultState;
},
componentWillMount: function() {
this.loadWeather();
},
loadWeather: function(city) {
var cityname = 'Sydney,NSW,Australia';
if (city) {
cityname = city;
}
JQ.getJSON( "http://api.openweathermap.org/data/2.5/weather?q=" + cityname, function( data ) {
console.info(data);
if (data.cod && data.cod == '404') {
return;
}
var weather = data.weather[0];
var result = {};
result.name = data.name;
result.icon = this.getIcon(weather.icon);
result.description = weather.description;
this.setState(result);
}.bind(this));
},
getIcon: function(code) {
if (!code) {
code = '01d';
}
return 'http://openweathermap.org/img/w/' + code + '.png';
},
handleChange: function(e) {
this.loadWeather(e);
},
render: function() {
var valueLink = {
value: this.state.searchname,
requestChange: this.handleChange
};
return <div>
<input name="cityname" type="text" valueLink={valueLink} />
<h1>{this.state.name}</h1>
<img src={this.state.icon} />
<p>{this.state.description}</p>
</div>;
}
});
You can also find an improved version of this component with delayed ajax search: https://bitbucket.org/dcai/react.js-example_weather/src/039ffd18375a5bd80d53129ce01edb6228df5215/Weather.jsx?at=master