React App

Занятие 5

Профессия
Node.js & React.js developer
продвинутый курс

1

Routers

1

Routers

для чего они нужны?

const c = 13000;

if (c > 10000) {
  console.log('Вы не можете претендовать на льготу');
} else {
  console.log('Поздравляю, ваша льгота составит: ' +  c * 0.01)
}

1

Routers

для чего они нужны?

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('Вы за чертой бедности');
}

1

Routers

для чего они нужны?

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('Вы за чертой бедности');
}

1

Routers

так что же это такое?

state => {code}

Абстракция для ветвления в программе, которая выполняет код соответсвующий для переданного клиетом состояния.

1

Routers

свойства

Роутер - прозрачен для клиента

При одном и том же состоянии - роутер должен выполнять один и тот же код

req => {code}

({hostname, method, path, query}) => {code}

({req.path, req.user, req.user.isAdmin}) => {code}

1

Routers

свойства

В программе роутер может быть:

  • декларативным описанием связи состояния и исполняемого кода
  • декларация + исполняемый код (router handler)

1

Routers

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')
})

1

Routers

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'
}

1

Routers

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>

1

Routers

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
        ]
      }
    ]
  }
]

1

Routers

Universal Router

UniversalRouter.
  resolve(routes, { path, ...context })
⇒ Promise<any>

1

Routers

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

1

Routers

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

1

Routers

params

const routes = [
  {
    path: '/hello/:username',
    action: (ctx, { username }) => `Welcome, ${username}!`
  }
];

resolve(routes, { path: '/hello/john' })
  .then(result => console.log(result));
  // => Welcome, john!

1

Routers

context

const routes = [
  {
    path: '/hello',
    action(context) {
      return `Welcome, ${context.user}!`
    }
  }
];

resolve(routes, { path: '/hello', user: 'admin' })
  .then(result => console.log(result));
  // => Welcome, admin!

1

Routers

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!

1

Routers

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' });

1

Routers

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 />);
      },
    }
    ...
  ],
};

2

React App

React App

Client-server-universal matrix

 

2

React App

Universal App

 

2

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

React App

ReactApp Server => Universal App

 

2


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()
} 

React App

ReactApp Client => Universal App

 

2

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,
);

React App

renderToStaticMarkup

 

2

React App

renderToStaticMarkup

 

2

React App

Universal Routes

 

2

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"
    }
  }
}

React App

Зачем нужен INITIAL_STATE

 

2

<script>
  window.__INITIAL_STATE__ = {
    user: {
      name:"Steve Jobs", 
      avatar: "..."
    } 
  };
</script>

React App

Зачем нужен INITIAL_STATE

 

2

/games

db => games

<GamesPage games={games} />

SSR => str

res.send()


Server

/games

<GamesPage games={???} />


1) fetch('/api/v1/games')

2) INITIAL_STATE

Client

React App

Запрос fetch

 

2

/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

React App

Universal Routes

 

 

2

/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} />
    }
  }
}

React App

getData

 

2

// 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
}

React App

/game/:id

 

2

React App

Html.js

2

React App

getData

 

2

// 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)
}

React App

problem 1 ?

 

2

// 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)
}

React App

problem 2 ?

 

2

cosnt {game} = getData(ctx);

// fetch ('/api/v1/game/123')

const users = await game.getWinners();

React App

universal model ?

 

2

/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} />
    }
  }
}

React App

universal model ?

 

2

/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 })
);;

React App

universal model ?

 

2

...

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(...);
  }
}

...

3

2 way bingings

input & forms

3

Про практику

репозитории, хостинг и сложности

Игорь Суворов

Thanks!

any questions?

программист-предприниматель

React App

By Igor Suvorov

React App

* template

  • 805