React
Deklaratywne widoki na skalę Internetu.
A co gdyby użytkownik był funkcją…?
@radekmie
Czym jest React?
Biblioteką do tworzenia interfejsów.
Tak, tylko biblioteką.
Tradycyjne MV*
Model | Kontroler | Widok |
---|---|---|
Dane | Logika interfejsu | Szablony |
Osobno?
React
Dane
Widoki
Komponenty
Akcje
Zmysły
<Application>
<Navigation>
<Logo />
</Navigation>
<Sidebar>
<Profile>
<Avatar of={42} />
<Bio text={bio} />
</Profile>
<Friends of={42} />
<Options of={42} />
</Sidebar>
<Timeline of={42} />
</Application>
<div class="application">
<div class="navigation">
<div class="logo">
<img src="./logo.png" alt="logo" />
</div>
</div>
<div class="sidebar">
<div class="profile">
<div class="avatar">
<img src="./avatars/42.png" alt="avatar" />
</div>
<p class="bio">
I'm a secret donut lover.
</p>
</div>
<div class="friends">...</div>
<div class="options">...</div>
</div>
<div class="timeline">...</div>
</div>
Deklaratywne widoki
const Timeline = props => {
const events = getEvents(props.of).map(event =>
<TimelineEvent key={event.id} event={event} />
);
return (
<div className="timeline">
{events}
</div>
);
}
Przykładowe komponenty
const Timeline = props => {
const events = getEvents(props.of).map(event =>
<TimelineEvent key={event.id} event={event} />
);
return (
<div className="timeline">
{events}
</div>
);
}
const TimelineEvent = props =>
<div className="event">
<h1>
{props.event.title}
</h1>
<Avatar of={props.event.author} />
<Schedule at={props.event.date} />
<p>
{props.event.description}
</p>
</div>
;
JSX?
const Timeline = props => {
const events = getEvents(props.of).map(event =>
React.createElement(TimelineEvent, {key: event.id, event: event})
);
return (
React.createElement('div', {className: 'timeline'},
events
)
);
}
const TimelineEvent = props =>
React.createElement('div', {className: 'event'},
React.createElement('h1', null,
props.event.title
),
React.createElement(Avatar, {of: props.event.author}),
React.createElement(Schedule, {at: props.event.date}),
React.createElement('p', null,
props.event.description
)
)
;
Na prawdę deklaratywne?
To, co zwracają nasze komponenty, to nie fragmenty DOM, tylko opis interfejsu.
React zajmuje tylko się urzeczywistnieniem tego opisu.
Jednokierunkowy przepływ danych
Komponent rodzic przekazuje dziecku
jakieś informacje (ang. props).
Jak dziecko komunikuje się ze światem?
Odwrócony przepływ danych
class Navigation extends React.Component {
onSearch = input => {
alert(`Search: ${input}`);
};
render() {
return (
<SearchBar onSearch={this.onSearch} />
);
}
}
class SearchBar extends React.Component {
onChange = event => {
this.props.onSearch(event.target.value);
};
render() {
return (
<input className="search" onChange={this.onChange} />
);
}
}
Stan
class Navigation extends React.Component {
state = {search: ''};
onSearch = input => {
this.setState({search: input});
};
render() {
return (
<SearchBar search={this.state.search} onSearch={this.onSearch} />
);
}
}
class SearchBar extends React.Component {
onChange = event => {
this.props.onSearch(event.target.value);
};
render() {
return (
<input className="search" onChange={this.onChange} />
);
}
}
Bez zmian!
Pytania?
GraphQL
Jedno API, by je wszystkie złączyć.
Język komunikacji między serwerem a klientem.
Maciek Stasiełuk
REST
REpresentational State Transfer


HTTP GET /book/1
{
"id": 1,
"title": "Hobbit",
"authorId": 100
}


HTTP GET /author/100
{
"id": 100,
"firstName": "John Ronald Reuel",
"lastName": "Tolkien"
}


HTTP GET /author/100/books
{
"books": [
{"id": 1, "title": "Hobbit"},
{"id": 2, "title": "Drużyna Pierścienia"},
{"id": 3, "title": "Dwie wieże"},
{"id": 4, "title": "Powrót króla"}
]
}


HTTP GET /book/1
{
"id": 1,
"title": "Hobbit",
"authorId": 100
}


HTTP GET /authorWithBooks/100
{
"id": 100,
"firstName": "John Ronald Reuel",
"lastName": "Tolkien",
"books": [
{"id": 1, "title": "Hobbit"},
{"id": 2, "title": "Drużyna pierścienia"},
{"id": 3, "title": "Dwie wieże"},
{"id": 4, "title": "Powrót króla"}
]
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien"
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 320
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 279,
"publisher": "Longmans",
"year": 1937
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"authorId": 100
}


HTTP GET /book/1
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 279,
"publisher": "Longmans",
"year": 1937
}



{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 279,
"publisher": "Longmans",
"year": 1937,
"cover": "hard"
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 279,
"publisher": "Longmans",
"year": 1937,
"cover": "hard",
"imageUrl": "..."
}
{
"id": 1,
"title": "Hobbit",
"isbn": "9780582186552",
"author": "John Ronald Reuel Tolkien",
"pages": 279,
"publisher": "Longmans",
"year": 1937,
"cover": "hard",
"imageUrl": "...",
"thumbnailUrl": "..."
}










Język programowania
Protokół
Framework
Język zapytań
GraphQL


book (id: 1) {
id
title
}
{
"book": {
"id": 1,
"title": "Hobbit"
}
}

book(id: 1) {
id
title
author {
firstName
lastName
}
year
publisher
pages
}
{
"book": {
"id": 1,
"title": "Hobbit",
"author": {
"firstName": "John Ronald Reuel",
"lastName": "Tolkien"
},
"year": 1937,
"publisher": "Longmans",
"pages": 279
}
}



author(id: 100) {
firstName
lastName
books {
title
}
}
{
"author": {
"firstName": "John Ronald Reuel",
"lastName": "Tolkien",
"books": [
{
"title": "Hobbit"
},
{
"title": "Drużyna Pierścienia"
},
{
"title": "Dwie wieże"
},
{
"title": "Powrót króla"
}
]
}
}


book(id: 2) {
isbn
title
author {
lastName
books {
title
year
}
}
}
{
"book": {
"isbn": "9788324132614",
"title": "Drużyna Pierścienia",
"author": {
"lastName": "Tolkien",
"books": [
{
"title": "Hobbit",
"year": 1937
},
{
"title": "Drużyna Pierścienia",
"year": 2009
},
{
"title": "Dwie wieże",
"year": 1981
},
{
"title": "Powrót króla",
"year": 2002
}
]
}
}
}


mutation ($book: BookInput!) {
addNewBook(book: $book) {
id
title
pages
}
}
{
"addNewBook": {
"id": 5,
"title": "Silmarillion",
"pages": 367
}
}
Mutacje
const bookInput = {
title: 'Silmarillion',
publisher: 'Amber',
year: 2014,
pages: 367,
};
Zalety
- Brak problemu pobierania nadmiarowych, zbędnych danych
- Nie ma potrzeby wykonywania wielu zapytań żeby uzyskać wszystkie potrzebne dane
-
Szybki i niezależny rozwój aplikacji klienckich
- Silnie typowana schema działa jak "kontrakt" między klientem a serwerem
Zalety cd.
- Bardzo dokładna analityka użycia API
- Możliwość łączenia istniejących API (w tym RESTowych) w formie API Gateway
- Subskrypcje umożliwiające pobieranie danych w czasie rzeczywistym
- Bogaty i stabilny ekosystem open-source
type Book {
id: Int!
title: String!
isbn: String
author: Author
authorId: Int
pages: Int
publisher: String
year: Int
cover: CoverType
imageUrl(size: ImageSize): String
}
type Author {
id: Int!
firstName: String!
lastName: String!
books: [Book]
}
type Query {
author(id: Int!): Author
book(id: Int!): Book
books: [Book]
}
GraphQL SDL
Podstawowe typy
- String
- Boolean
- Int
- Float
- ID
Enumeracje
enum CoverType {
HARD
SOFT
}
Własne typy
type Author {
id: Int!
firstName: String
lastName: String!
}
type DateTime
enum ImageSize {
FULL
THUMB
}
Listy
- [String]
- [Int]
- [Book]
- ...
Fragmenty
Dyrektywy
Aliasy
Zmienne
Argumenty
book (id: 3) {
imageUrl (size: "THUMB")
}
Demo
Integracja z resztą stacka
React
import {Query} from 'react-apollo';
const query = gql`{
books {
id
title
author {
lastName
}
}
}`;
const BookListing = () => (
<Query query={query}>
{({data}) => (
<ul>
{data.books.map(({id, title, author}) => (
<li key={id}>{title} - {author.lastName}</li>
))}
</ul>
)}
</Query>
);
MongoDB
const resolvers = {
Query: {
author: (root, args) => {
return Authors.findOne({_id: args.id})
},
book: (root, args) => {
return Books.findOne({_id: args.id})
},
books: () => {
return Books.find()
},
}
};
Kto używa GraphQL produkcyjnie?











Pytania?
MongoDB
Old-school NoSQL.
Baza danych w języku Internetu.
@radekmie
NoSQL
Grafowe |
Klucz-wartość |
Kolumnowe |
Dokumentowe |
Neo4j |
Redis |
Neo4j |
Neo4j |
Redis |
Cassandra |
Neo4j |
Redis |
Cassandra |
MongoDB |
Dokumenty
_id
BSON
- cały JSON
- dane binarne
- daty
- wyrażenia regularne
- wyspecjalizowane typy liczbowe
CRUD
db.createCollection('example')
db.createCollection('example')
db.getCollection('example').insertOne({
key: 'value',
and: {objects: ['are', 1337]}
})
db.createCollection('example')
db.getCollection('example').insertOne({
key: 'value',
and: {objects: ['are', 1337]}
})
db.getCollection('example').insertMany([
{key: 17},
{key: 20},
{key: 42},
{key: [1, 2, 3]}
])
CRUD
db.getCollection('example').findOne()
{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}
CRUD
db.getCollection('example').find()
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
"key" : 17.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
"key" : 20.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"key" : 42.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
"key" : [ 1.0, 2.0, 3.0 ]
}]
CRUD
db.getCollection('example').find({key: 42})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"key" : 42.0
}]
db.getCollection('example').find({key: 42})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"key" : 42.0
}]
db.getCollection('example').find({key: {$gt: 17}})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
"key" : 20.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"key" : 42.0
}]
CRUD
db.getCollection('example').find({
key: {$in: ['value', 17]}
})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
"key" : 17.0
}]
CRUD
db.getCollection('example').find({key: /va..e/})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}]
db.getCollection('example').find({key: /va..e/})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}]
db.getCollection('example').find({key: /.*/})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value",
"and" : {
"objects" : [
"are",
1337.0
]
}
}]
CRUD
db.getCollection('example')
.find({}, {key: 1})
.sort({key: -1})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : "value"
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"key" : 42.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
"key" : 20.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
"key" : 17.0
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
"key" : [
1.0,
2.0,
3.0
]
}]
CRUD
db.getCollection('example')
.find()
.sort({key: -1})
.skip(2)
.limit(1)
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
"key" : 20.0
}]
CRUD
$, $all, $and, $bitsAllClear, $bitsAllSet, $bitsAnyClear, $bitsAnySet, $comment, $elemMatch, $elemMatch, $eq, $exists, $expr, $geoIntersects, $geoWithin, $gt, $gte, $in, $jsonSchema, $lt, $lte, $meta, $mod, $ne, $near, $nearSphere, $nin, $nor, $not, $or, $regex, $size, $slice, $text, $type, $where
CRUD
db.getCollection('example')
.updateOne({key: 'value'}, {$set: {key: 2}})
db.getCollection('example').findOne({key: 2})
{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : 2.0,
"and" : {
"objects" : [
"are",
1337.0
]
}
}
CRUD
db.getCollection('example')
.updateOne({key: 2}, {$inc: {key: 10}})
db.getCollection('example').findOne({key: 12})
{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : 12.0,
"and" : {
"objects" : [
"are",
1337.0
]
}
}
CRUD
db.getCollection('example')
.updateOne({key: 12}, {$addToSet: {'and.objects': 1}})
db.getCollection('example').findOne({key: 12})
{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : 12.0,
"and" : {
"objects" : [
"are",
1337.0,
1.0
]
}
}
CRUD
db.getCollection('example')
.updateOne({key: 12}, {$pull: {'and.objects': 1}})
db.getCollection('example').findOne({key: 12})
{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"key" : 12.0,
"and" : {
"objects" : [
"are",
1337.0
]
}
}
CRUD
db.getCollection('example')
.updateMany({}, {$set: {x: Math.PI}})
db.getCollection('example').find({}, {x: 1})
[{
"_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
"x" : 3.14159265358979
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
"x" : 3.14159265358979
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
"x" : 3.14159265358979
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
"x" : 3.14159265358979
}, {
"_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
"x" : 3.14159265358979
}]
CRUD
$, $[<identifier>], $[], $addToSet, $bit, $currentDate, $each, $inc, $max, $min, $mul, $pop, $position, $pull, $pullAll, $push, $rename, $set, $setOnInsert, $slice, $sort, $unset
CRUD
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').find()
[]
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').find()
[]
db.getCollection('example').drop()
Agregacje
db.getCollection('likes').distinct('userId')
[
"vq13u8ey9d",
"tb8m0ovqrp",
"zn3ye8enyz",
"4rqmk6pg0h",
"oslff18b7u",
...
]
Agregacje
db.getCollection('likes').aggregate([
{$match: {date: {$gte: new Date(2016, 0, 1)}}},
{$group: {_id: {$year: '$date'}, likes: {$sum: 1}}},
{$sort: {likes: -1}}
])
db.getCollection('likes').aggregate([
{$match: {date: {$gte: new Date(2016, 0, 1)}}},
{$group: {_id: {$year: '$date'}, likes: {$sum: 1}}},
{$sort: {likes: -1}}
])
[{
_id: 2017.0,
likes: 87291.0
}, {
_id: 2016.0,
likes: 53197.0
}, {
_id: 2018.0,
likes: 17234.0
}]
Agregacje
$addFields, $bucket, $bucketAuto, $collStats, $count, $currentOp, $facet, $geoNear, $graphLookup, $group, $indexStats, $limit, $listLocalSessions, $listSessions, $lookup, $match, $out, $project, $redact, $replaceRoot, $sample, $skip, $sort, $sortByCount, $unwind
Agregacje
$abs, $add, $addToSet, $allElementsTrue, $and, $anyElementTrue, $arrayElemAt, $arrayToObject, $avg, $ceil, $cmp, $concat, $concatArrays, $cond, $dateFromParts, $dateFromString, $dateToParts, $dateToString, $dayOfMonth, $dayOfWeek, $dayOfYear, $divide, $eq, $exp, $filter, $first, $floor, $gt, $gte, $hour, $ifNull, $in, $indexOfArray, $indexOfBytes, $indexOfCP, $isArray, $isoDayOfWeek, $isoWeek, $isoWeekYear, $last, $let, $literal, $ln, $log, $log10, $lt, $lte, $map, $max, $mergeObjects, $meta, $millisecond, $min, $minute, $mod, $month, $multiply, $ne, $not, $objectToArray, $or, $pow, $push, $range, $reduce, $reverseArray, $second, $setDifference, $setEquals, $setIntersection, $setIsSubset, $setUnion, $size, $slice, $split, $sqrt, $stdDevPop, $stdDevSamp, $strLenBytes, $strLenCP, $strcasecmp, $substr, $substrBytes, $substrCP, $subtract, $sum, $switch, $toLower, $toUpper, $trunc, $type, $week, $year, $zip
Pytania?
IT PopCorn - Architektura nowoczesnych aplikacji internetowych.
By Maciej
IT PopCorn - Architektura nowoczesnych aplikacji internetowych.
- 795