(last pun, I promise!)
Web UI/visualization developer and consultant
Member of the Vue.js team
Educator passionate about learning, but even more passionate about productivity
HTML
CSS
JS
TodoList
HTML
CSS
JS
Todo
HTML
CSS
JS
Button
HTML
CSS
JS
...but which component system?
"My biggest frustration is the constant migration from tool to tool or framework to framework. Never any time to master one before something better comes along."
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
</head>
<body>
<div>{{ greeting }} World</div>
<script>
new Vue({
el: 'body',
data: {
greeting: 'Hello'
}
})
</script>
</body>
</html>
Hello world!
React
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var App = React.createClass({
getInitialState: function () {
return {
greeting: 'Hello'
}
},
render: function () {
return <div>{ this.state.greeting } World</div>
}
})
ReactDOM.render(
<App/>,
document.getElementById('app')
)
</script>
</body>
</html>
and with React...
<!DOCTYPE html>
<html>
<head>
<title>Todos</title>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
</head>
<body>
<input v-model="newTodoText" @keyup.enter="addTodo">
<ul>
<li v-for="(index, todo) in todos">
{{ todo.text }}
<button @click="removeTodo(index)">X</button>
</li>
</ul>
<script>
new Vue({
el: 'body',
data: {
newTodoText: '',
todos: []
},
methods: {
addTodo: function () {
if (this.newTodoText) {
this.todos.push({ text: this.newTodoText })
this.newTodoText = ''
}
},
removeTodo: function (index) {
this.todos.splice(index, 1)
}
}
})
</script>
</body>
</html>
Simple todo app
React
<!DOCTYPE html>
<html>
<head>
<title>Todos</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var App = React.createClass({
getInitialState: function () {
return {
newTodoText: '',
todos: []
}
},
render: function () {
return (
<div>
<input
value={this.state.newTodoText}
onChange={this.updateNewTodoText}
onKeyUp={this.addTodo}
/>
<ul>
{
this.state.todos.map(function (todo, index) {
return (
<li>
{ todo.text }
{' '}
<button
onClick={
function () {
this.removeTodo(index)
}.bind(this)
}
>X</button>
</li>
)
}.bind(this))
}
</ul>
</div>
)
},
updateNewTodoText: function (event) {
this.setState({
newTodoText: event.target.value
})
},
addTodo: function (event) {
if (event.which === 13 && this.state.newTodoText) {
this.setState({
todos: this.state.todos.concat([{
text: this.state.newTodoText
}]),
newTodoText: ''
})
}
},
removeTodo: function (index) {
this.setState({
todos: this.state.todos.filter(function (_, todoIndex) {
return todoIndex !== index
})
})
}
})
ReactDOM.render(
<App/>,
document.getElementById('app')
)
</script>
</body>
</html>
and with React...
<!DOCTYPE html>
<html>
<head>
<title>Reusable Components</title>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
</head>
<body>
<p>
It will have been 2 minutes since I loaded this page in
<counter
:initial-value="120"
:increment-by="-1"
:stop-at="0"
></counter>
seconds.
</p>
<script>
Vue.component('counter', {
template: '<span>{{ number }}</span>',
props: {
initialValue: {
type: Number,
default: 0
},
incrementBy: {
type: Number,
default: 1
},
interval: {
type: Number,
default: 1000
},
stopAt: {
type: Number
}
},
data: function () {
return {
number: this.initialValue
}
},
created: function () {
this.counter = setInterval(function () {
this.number += this.incrementBy
if (this.stopAt === this.number) {
clearInterval(this.counter)
}
}.bind(this), this.interval)
},
beforeDestroy: function () {
clearInterval(this.counter)
}
})
new Vue({ el: 'body' })
</script>
</body>
</html>
Reusable components
React
<!DOCTYPE html>
<html>
<head>
<title>Reusable Components</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var Counter = React.createClass({
propTypes: {
initialValue: React.PropTypes.number,
incrementBy: React.PropTypes.number,
interval: React.PropTypes.number,
stopAt: React.PropTypes.number
},
getDefaultProps: function () {
return {
initialValue: 0,
incrementBy: 1,
interval: 1000
}
},
getInitialState: function () {
return {
number: this.props.initialValue
}
},
componentWillMount: function () {
this.counter = setInterval(function () {
this.setState({
number: this.state.number + this.props.incrementBy
})
if (this.props.stopAt === this.state.number) {
clearInterval(this.counter)
}
}.bind(this), this.props.interval)
},
componentWillUnmount: function () {
clearInterval(this.counter)
},
render: function () {
return <span>{ this.state.number }</span>
}
})
var App = React.createClass({
render: function () {
return (
<p>
It will have been 2 minutes since I loaded this page in
{' '}
<Counter
initialValue={120}
incrementBy={-1}
stopAt={0}
/>
{' '}
seconds.
</p>
)
}
})
ReactDOM.render(
<App/>,
document.getElementById('app')
)
</script>
</body>
</html>
and with React...
Build on web standards, don't reinvent them
<template>
<button class="btn btn-{{ kind }}">
<slot></slot>
</button>
</template>
<script>
export default {
props: {
kind: {
validator (value) {
return ['primary', 'warning'].indexOf(value) !== -1
},
required: true
}
}
}
</script>
<style lang="scss" scoped>
$text-color: #fff;
$primary-bg-color: #0074d9;
$warning-bg-color: #d5a13c;
.btn {
color: $text-color;
&.btn-primary {
background: $primary-bg-color;
&:hover {
background: lighten($primary-bg-color, 20%);
}
}
&.btn-warning {
background: $warning-bg-color;
&:hover {
background: lighten($warning-bg-color, 20%);
}
}
}
</style>
React
React... reinventing them
import { Component } from 'react'
import Radium from 'radium'
import color from 'color'
@Radium
class Button extends Component {
static propTypes = {
kind: React.PropTypes
.oneOf(['primary', 'warning'])
.isRequired
}
render () {
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
)
}
}
const buttonDesign = {
textColor: '#fff',
types: {
primary: {
backgroundColor: '#0074d9'
},
warning: {
backgroundColor: '#d5a13c'
}
}
}
const styles = {
base: {
color: buttonDesign.textColor,
},
primary: {
background: buttonDesign.primary.backgroundColor,
':hover': {
background: color(buttonDesign.primary.backgroundColor).lighten(0.2).hexString()
}
},
warning: {
background: buttonDesign.warning.backgroundColor,
':hover': {
background: color(buttonDesign.warning.backgroundColor).lighten(0.2).hexString()
}
},
}
<template>
<button @click="count += 1">Add to count</button>
<button @click="items.push(count)">Save count</button>
<div>Count: {{ count }}</div>
Saved counts:
<ul>
<li v-for="item in items">Saved: {{ item }}</li>
</ul>
</template>
<script>
export default {
data () {
return {
count: 0,
items: []
}
}
}
</script>
Speed and ease of optimization
this is already optimized - no special measures taken
React
import { Component } from 'react'
import { Map, List } from 'immutable'
import { immutableRenderDecorator } from 'react-immutable-render-mixin'
@immutableRenderDecorator
export default class Counter extends Component
constructor (props) {
super(props)
this.state = {
data: Map({
count: 0,
items: List()
})
}
this.handleCountClick = ::this.handleCountClick
this.handleAddItemClick = ::this.handleAddItemClick
},
handleCountClick () {
this.setState(({data}) => ({
data: data.update('count', v => v + 1)
}))
},
handleAddItemClick () {
this.setState(({data}) => ({
data: data.update('items', list => list.push(data.get('count')))
}))
},
render () {
const { data } = this.state
return (
<div>
<button onClick={this.handleCountClick}>Add to count</button>
<button onClick={this.handleAddItemClick}>Save count</button>
<div>Count: {data.get('count')}</div>
Saved counts:
<ul>
{data.get('items').map(item =>
<li>Saved: {item}</li>
)}
</ul>
</div>
)
}
})
React with immutable data and
custom shouldComponentUpdate
In-browser devtools
With time travel!
Hot module reloading
Is anyone actually using it in production?
Official support for practically required libs (like for routing and state management)
github.com/vuejs/vue-router
github.com/vuejs/vuex
github.com/vuejs/vue-cli
github.com/vuejs/vue-validator
github.com/vuejs/vue-rx
...
Ajax
Routing
Flux State
CLI Generator
Devtools
Linting
Touch Events
Observables
...
Is the future stable?