Занятие 5
18 Mar 2017
Профессия
Node.js & React.js developer
продвинутый курс
для чего они нужны?
const c = 13000;
if (c > 10000) {
console.log('Вы не можете претендовать на льготу');
} else {
console.log('Поздравляю, ваша льгота составит: ' + c * 0.01)
}
для чего они нужны?
const c = 13000;
if (c > 100000) {
console.log('Вы богаты');
} else if (c > 50000) {
console.log('Вы не очень богаты');
} else if (c > 20000) {
console.log('Вы не бедны');
} else if (c > 5000) {
console.log('Вы бедны');
} else {
console.log('Вы за чертой бедности');
}
для чего они нужны?
const c = req.body.c;
switch(getStatus(c)) {
case 'rich':
console.log('Вы богаты');
break;
case 'notrich':
console.log('Вы не очень богаты');
break;
case 'notpoor':
console.log('Вы не бедны');
break;
case 'poor':
console.log('Вы бедны');
break;
default:
console.log('Вы за чертой бедности');
}
так что же это такое?
state => {code}
Абстракция для ветвления в программе, которая выполняет код соответсвующий для переданного клиетом состояния.
свойства
Роутер - прозрачен для клиента
При одном и том же состоянии - роутер должен выполнять один и тот же код
req => {code}
({hostname, method, path, query}) => {code}
({req.path, req.user, req.user.isAdmin}) => {code}
свойства
В программе роутер может быть:
Express.js
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage')
})
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage')
})
Sails.js
// config/routes.js
module.exports.routes = {
'get /signup': { view: 'conversion/signup' },
'post /signup': 'AuthController.processSignup',
'get /login': { view: 'portal/login' },
'post /login': 'AuthController.processLogin',
'/logout': 'AuthController.logout',
'get /me': 'UserController.profile'
}
React Router
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
React Router
const routes = [
{ component: Root,
routes: [
{ path: '/',
exact: true,
component: Home
},
{ path: '/child/:id',
component: Child,
routes: [
path: '/child/:id/grand-child',
component: GrandChild
]
}
]
}
]
Universal Router
UniversalRouter.
resolve(routes, { path, ...context })
⇒ Promise<any>
resolve
import { resolve } from 'universal-router';
const routes = [
{
path: '/one',
action: () => 'Page One'
},
{
path: '/two',
action: () => `Page Two`
}
];
resolve(routes, { path: '/one' })
.then(result => console.log(result));
// => Page One
nested
const routes = {
path: '/admin',
children: [
{
path: '/', // www.example.com/admin
action: () => 'Admin Page'
},
{
path: '/users',
children: [
{
path: '/', // www.example.com/admin/users
action: () => 'User List'
},
{
path: '/:username', // www.example.com/admin/users/john
action: () => 'User Profile'
}
]
}
]
};
resolve(routes, { path: '/admin/users/john' }).then(result => console.log(result));
// => User Profile
params
const routes = [
{
path: '/hello/:username',
action: (ctx, { username }) => `Welcome, ${username}!`
}
];
resolve(routes, { path: '/hello/john' })
.then(result => console.log(result));
// => Welcome, john!
context
const routes = [
{
path: '/hello',
action(context) {
return `Welcome, ${context.user}!`
}
}
];
resolve(routes, { path: '/hello', user: 'admin' })
.then(result => console.log(result));
// => Welcome, admin!
async
const routes = [
{
path: '/hello/:username',
async action({ params }) {
const resp = await fetch(`/api/users/${params.username}`);
const user = await resp.json();
if (user) return `Welcome, ${user.displayName}!`;
}
}
];
resolve(routes, { path: '/hello/john' })
.then(result => console.log(result));
// => Welcome, John Brown!
middlewares
const routes = [
{
path: '/',
async action({ next }) {
console.log('middleware: start');
const child = await next();
console.log('middleware: end');
return child;
},
children: [
{
path: '/hello',
action() {
console.log('route: return a result');
return 'Hello, world!';
}
}
]
}
];
resolve(routes, { path: '/hello' });
lego-starter-kit/v2
export default {
path: '/cabinet',
action({ next, page }) {
page.pushTitle('Cabinet')
.layout(Cabinet);
return next();
},
children: [
{
path: '/',
async action({ page }) {
return page.pushTitle('All games')
.component(<GamesPage />);
},
},
{
path: '/profile',
action({ page }) {
return page.pushTitle('Profile')
.layout(null)
.component(<ProfilePage />);
},
}
...
],
};
Client-server-universal matrix
Universal App
express => req.token =>
=> db => user => req.user
req.query.id => db => game(id)
const rootState = {user}
const uapp = new Uapp({rootState})
universalrouter.resolve(routes, {uapp}) =>
component => render => str
res.send(str);
Server
const rootState = window.__ROOT_STATE__
const uapp = new Uapp({rootState})
history.listen(ClientApp.render);
ClientApp.render()
// ClientApp.render()
universalrouter.resolve(routes, {uapp}) =>
component
ReactDom.render(component)
// click menu
history.push('/game/123')
// ClientApp.render()
universalrouter.resolve(routes, {uapp}) =>
component
ReactDom.render(component)
Client
ReactApp Server => Universal App
expressApp.use('*', ssr);
function ssr(req, res) {
const props = {
path: req.path,
domain: req.domain,
hostname: req.hostname,
query: req.query,
app: this,
uapp: this.uapp
...
}
const route = await UniversalRouter.resolve(this.getUniversalRoutes(), props);
...
return component.renderToStaticMarkup()
}
ReactApp Client => Universal App
const rootState = window.__ROOT_STATE__;''
const uapp = new Uapp({rootState})
history.listen(this.render);
// render
const props = {
path: window.location.pathname,
hostname: window.location.hostname,
query: qs.parse(window.location.search),
app: this,
uapp: this.uapp
...
}
const route = await UniversalRouter.resolve(this.getUniversalRoutes(), props);
ReactDOM.render(
component,
this.container,
);
renderToStaticMarkup
renderToStaticMarkup
Universal Routes
export default {
path: '/game/:id',
async action({ app, uapp }) {
if (__SERVER__) {
// app.models & app.db
}
if (__CLIENT__) {
// 1) __ROOT_STATE__
// 2) fetch -> "/api/v1/game/:id"
}
}
}
Зачем нужен INITIAL_STATE
<script>
window.__INITIAL_STATE__ = {
user: {
name:"Steve Jobs",
avatar: "..."
}
};
</script>
Зачем нужен INITIAL_STATE
/games
db => games
<GamesPage games={games} />
SSR => str
res.send()
Server
/games
<GamesPage games={???} />
1) fetch('/api/v1/games')
2) INITIAL_STATE
Client
Запрос fetch
/src/utils/fetch/fetch.server.js
import { host } from 'lego-starter-kit/config';
function localUrl(url) {
if (url.startsWith('//')) {
return `https:${url}`;
}
if (url.startsWith('http')) {
return url;
}
return `http://${host}${url}`;
}
function localFetch(url, options) {
return fetch(localUrl(url), options);
}
export default localFetch;
Server
/games
???
fetch('/api/v1/games')
Client
Universal Routes
/uapp/routes/game/index.js
import getData from './getData';
export default {
path: '/game/:id',
async action(ctx) {
const data = getData(ctx);
return {
title: 'Game',
component: <GamePage game={data.game} />
}
}
}
getData
// getData.client.js
export default ({uapp}) => {
return uapp.rootState && uapp.rootState.pageData
}
// getData.server.js
export default async ({app, uapp, params}) => {
if (!uapp.rootState) uapp.rootState = {}
const { Game } = app.models
const game = await Game.findById(params.id)
uapp.rootState.pageData = {
game,
}
return uapp.rootState
}
/game/:id
Html.js
getData
// getData.server.js
export default async ({app, uapp, params}) => {
const game = await Game.findById(params.id)
...
}
// getData.client.js
export default ({uapp, params}) => {
const data = uapp.rootState.pageData;
if (params.id == data.game.id) return data;
return {game: await fetch('/api/v1/game/' + params.id )}
}
// /api/v1/game
express.get('/game/:id', req => {
return Game.findById(params.id)
}
problem 1 ?
// getData.server.js
export default async ({app, uapp, params}) => {
const game = await Game.findById(params.id)
...
}
// getData.client.js
export default ({uapp, params}) => {
const data = uapp.rootState.pageData;
if (params.id == data.game.id) return data;
return {game: await fetch('/api/v1/game/' + params.id )}
}
// /api/v1/game
express.get('/game/:id', req => {
return Game.findById(params.id)
}
problem 2 ?
cosnt {game} = getData(ctx);
// fetch ('/api/v1/game/123')
const users = await game.getWinners();
universal model ?
/uapp/routes/game/index.js
export default {
path: '/game/:id',
async action({uapp, params}) {
const { Game } = uapp.umodels;
const game = await Game.findById(params.id)
const users = await game.getWinners()
return {
title: 'Game',
component: <GamePage game={game} users={users} />
}
}
}
universal model ?
/umodels/Game/Game.server.js
export default ctx => ({
universalActions: ['getGames', 'find', 'findOne'],
deleteAll() {
return Game.remove({});
}
getGames() {
const { Game } = ctx.models;
return Game.find();
},
find(...args) {
const { Game } = ctx.models;
return Game.find(...args);
},
findOne(...args) {
const { Game } = ctx.models;
return Game.findOne(...args);
},
});
/api/api.server.js
import { createRoute, createSocketNamespace }
from 'universal-model';
const models = ctx.umodels;
expressApp.all('/universal',
createRoute({ ...ctx, models })
);
expressApp.ws('/universal',
createSocketNamespace({ ...ctx, models })
);;
universal model ?
...
export default (uapp) => {
return {
redirectTo(id) {
window.location = '/games/' + id;
},
...createClientActions({
api: uapp.api,
model: 'Game',
actions: ['getGames', 'find'],
format: [GameClient],
}),
...createClientActions({
api: uapp.api,
model: 'Game',
actions: ['findOne', 'findById'],
format: GameClient,
}),
};
};
/umodels/Game/Game.client.js
import { createClientActions } from 'universal-model';
class GameClient {
constructor(json) {
Object.assign(this, json);
}
getImage() {
return `https://static.mgbeta.ru${this.coverImage}`;
}
getWinners() {
return fetch(...);
}
}
...
input & forms
репозитории, хостинг и сложности
any questions?
программист-предприниматель