React
class MyComponent extends React.Component { /* ... */ }
render(document.body, <MyComponent/>);
Angular
@Component({ selector: 'my-component' /* ... */ })
export class MyComponent { /* ... */ }
<my-component></my-component>
VueJS
Vue.component('my-component', { /* ... */ });
<my-component></my-component>
Encapsulated elements defined as JS classes
Runs natively in the browser:
No library code needed 🎉
True style encapsulation
Web Standard is here to stay
import { html, render } from 'lit-html';
const template = ({ title, greeting }) => html`
<h1>${title}</h1>
<p>${greeting}</p>
`;
render(template({
title: "Talking about rendering",
greeting: "Welcome, Webworkers"
}), document.body);
const simple = `Hello, World!`;
const multiline = `
<ul>
<li>One thing</li>
<li>another thing</li>
</ul>
`;
const withExpression = `
Your lucky number is
${Math.floor(Math.random() * 10) + 1}
`;
const tagged = tag`Hello ${somevar}!`;
LIVE
const tag = (parts: string[], ...values: any[]) => {
console.log(parts, values);
return values.length;
};
const interpolationsCount = tag`Hello, ${'World'}!`; // = 1
const parse = (parts, ...values) => {
console.log(parts, values);
return parseImpl(parts, ...values);
// Returns DocumentFragment made from template string
};
const template = (name) =>
parse`<h1>Hello ${name}!</h1>`;
document.body.append(template("Webworker"));
<template>
<template id="demo">
<h1>Hello, World!</h1>
</template>
const fragment =
document.querySelector('#demo').content.cloneNode(true);
const template = (title, greeting) => html`
<h1>${title}!</h1>
<p>${greeting}</p>
`;
<template>
<h1>{{}}</h1>
<p>{{}}</p>
</template>
<h1> </h1>
<p> </p>
<h1>Talking about rendering</h1>
<p>Welcome, Webworkers</p>
<h1>Rendering efficiently</h1>
<p>Like a pro</p>
const template = (todos) => html`
<h2>Todos</h2>
${todos.map(todo => html`
<div class="todo" id="todo-${todo.id}">
${todo.text}
</div>
`)}
`;
let inputValue = "";
const template = html`
<input value=${inputValue}
on-change=${e => inputValue = e.target.value}>
`;
{
todos: {
'todo-1': {
id: 'todo-1',
title: "Listen to cool web talk at sipgate",
priority: 3,
completed: false,
},
'todo-2': {
id: 'todo-2',
title: "Do the laundry",
priority: 1,
completed: true,
},
},
}
{
type: 'ADD_TODO',
payload: {
id: 'todo-3',
title: "Buy groceries",
priority: 2,
completed: false,
},
}
(State, Action) => State
Array.prototype.reduce(reducer, inital);
function reduce(state = { todos: {} }, action) {
// ...
}
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
// ...
default:
return state;
}
}
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
const todos = {
...state.todos,
[action.payload.id]: action.payload
};
// ...
default:
return state;
}
}
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
const todos = {
...state.todos,
[action.payload.id]: action.payload
};
return { ...state, todos };
default:
return state;
}
}
store.getState()
store.dispatch(action)
store.subscribe(listener)
Task: Render a list of Todos (yaaaay! 🙌)
<todo-list>
#shadow-root (open)
<h2>Todos</h2>
<todo-item todo-id="todo-1"></todo-item>
<todo-item todo-id="todo-3"></todo-item>
<h3>Completed Todos</h3>
<todo-item todo-id="todo-2"></todo-item>
</todo-list>
MapStateToProps Function
const mapStateToProps = (state) => ({
remainingTodos: Object.values(state.todos)
.filter(todo => !todo.completed)
.sort((a, b) => b.priority - a.priority),
completedTodos: Object.values(state.todos)
.filter(todo => todo.completed),
});
lit-html example
const renderTodo = (todo) => html`
<todo-item todo-id="${todo.id}"></todo-item>
`;
const render = (props) => html`
<h2>Todos</h2>
${props.remainingTodos.map(renderTodo)}
<h3>Completed Todos</h3>
${props.completedTodos.map(renderTodo)}
`;
Passing Properties
const renderTodo = (todo) => html`
<todo-item todo-id="${todo.id}"></todo-item>
`;
const mapStateToProps = (state, ownProps) => ({
todo: state.todos[ownProps.todoId],
});
Task: Render a single Todo-Item
<todo-item>
#shadow-root (open)
<p>Watch cool web talk at sipgate</p>
<button>✅</button>
</todo-item>
MapDispatchToProps Function
const mapDispatchToProps = (dispatch, ownProps) => ({
complete: (todoId) => dispatch({
type: 'COMPLETE_TODO',
payload: todoId,
}),
});
const props = {
...stateProps,
...dispatchProps,
};
lit-html example
const render = (props) => html`
<p>${props.todo.title}</p>
<button on-click=${() => props.complete(props.todo.id)}>
✅
</button>
`;
Middlewares
const middleware = store => next => action => {
// ...
};
Middlewares
const middleware = store => next => action => {
next(action); // call next middleware
// ...
};
Middlewares
const middleware = store => next => action => {
next(action); // call next middleware
if (action.type !== 'FETCH_TODOS') {
return;
}
// ...
};
Middlewares
const middleware = store => next => action => {
next(action); // call next middleware
if (action.type !== 'FETCH_TODOS') {
return;
}
const dispatchTodo = todo => store.dispatch({
type: 'ADD_TODO',
payload: todo,
});
fetch(/* ... */)
.then(resp => resp.json())
.then(todos => todos.forEach(dispatchTodo))
};
🏁 Summing up
mapStateToProps
mapDispatchToProps
const SearchBar = (props) => html`<!-- ... -->`;
const mapStateToProps = (state) => ({
text: state.router.query.s || '',
});
const mapDispatchToProps = {
changeText: changeTrackSearchInput,
eraseText: eraseTrackSearchInput,
};
customElements.define('search-bar', connect(
mapStateToProps,
mapDispatchToProps,
SearchBar,
));
const Base = connect(
mapStateToProps,
mapDispatchToProps,
PartyTrack,
);
const PartyTrack = withProps(Base, {
playing: Boolean,
trackid: String,
});
customElements.define('party-track', PartyTrack);
Follow along: