Краткое описание характеристик:

  • Компоненты пользовательского интерфейса потоковой передачи (через генераторы)
  • Асинхронная обработка маршрута
  • Простая интеграция с Redux, MobX и т. Д.
  • Рендеринг на стороне сервера
  • Мощные вложенные маршруты
  • Избегайте повторного внедрения функций, которые уже существуют в HTML / JS.
  • Минимальная кривая обучения

Репозиторий GitHub: https://github.com/nsoap-official/sailboat

NSOAP - Соглашение о маршрутизации для JavaScript

Один из главных уроков успеха React заключается в том, что привязка к базовому языку программирования делает фреймворк более мощным, чем в противном случае, при этом оставаясь легким для изучения. NSOAP (протокол доступа к объектам с собственным синтаксисом) принимает этот урок близко к сердцу; и определяет соглашение об URL-адресах для доступа к методам и объектам JavaScript локально (маршрутизация на стороне клиента) или удаленно (маршрутизация на стороне сервера).

Если вы знаете JavaScript, вы уже знаете соглашение NSOAP.

Давайте посмотрим на базовый пример NSOAP в действии для маршрутизации на стороне сервера.

//Consider the simple web service below
const myRoutes = {  
  greet(name) => `Hello ${name}`,
  math: {
    sum(a, b) => a + b,
    zero: 0,
    asyncSum(a, b) => Promise.resolve(a + b)
  },
}
/*
  NSOAP routes for this service will look like this:
  /greet(jes) returns "Hello jes"
  /math.sum(10,20) returns 30
  /math.zero returns 0
  /math.asyncSum(10,20) also returns 30
*/

Поскольку это всего лишь JavaScript, вы можете делать интересные вещи, например создавать цепочки маршрутов.

const myRoutes = {
  async getCustomer(id) {
    const customer = await getCustomerFromDatabase(id);
    return {
      index: customer,
      async details() {
        const customerDetails = await customer.getDetails(id);
        return customerDetails;
      } 
    }
  }
};
/*
  NSOAP URLs:
  /getCustomer(100) returns a customer object
  /getCustomer(100).details returns customer details
*/

Хорошо, вот вкратце NSOAP. Чтобы узнать больше, см. Документацию к NSOAP Express Router. Давайте начнем с официального маршрутизатора NSOAP для React - Sailboat.

Установка

Установите Sailboat из npm.

npm install sailboat

Начиная

В предыдущих примерах обработчики маршрутов возвращали в качестве результата строки или числа. Маршрутизация для React проста; просто верните компоненты React UI из обработчиков.

Начнем с домашней страницы, которая находится по адресу «/».

import React from "react";
import { Router, navigateTo } from "sailboat";
const Home = (
  <div>
    <h1>Welcome to Sailboat</h1>
    <p>You are on the home page.</p>
  </div>
);
const myApp = {
  index: <HomePage />
}
//Load the home page when rendered.
navigateTo("/");
ReactDOM.render(Router(myApp), mountNode);

Хорошо, это было просто. Давайте теперь создадим страницу, которая отображает сумму двух чисел, переданных через URL. Согласно соглашению NSOAP, ваш URL-адрес будет иметь вид «/ sum (10,20)». Или, если вы хотите использовать параметры, вы можете использовать «/ sum (x, y)? X = 10 & y = 20».

Мы также представим здесь компонент под названием «Ссылка», который переходит к URL-адресу при нажатии. Он отображает тег Anchor с его обработчиком кликов, вызывающим функцию navigateTo, описанную ранее, и устанавливает URL-адрес в адресной строке браузера.

import { Router, navigateTo } from "sailboat";
const Link => props => (
  <a href="#" onClick={() => navigateTo(props.href)}>
    {props.children}
  </a>
);
const HomePage = props => (
  <div>
    <p>
      <Link href="/sum(10,20)">
        Sum of 10 and 20
      </Link>
    </p>
  </div>
);
const Sum = props => (
  <div>Sum is `${props.a + props.b}`</div>
)
const myApp = {
  index: <HomePage />,
  sum: (a,b) => <Sum a={a} b={b} />
}
ReactDOM.render(Router(myApp), mountNode);

Это тоже было не так уж и сложно. Давайте посмотрим, как мы можем создавать более сложные пользовательские интерфейсы. Мы собираемся представить альтернативный синтаксис для объявления маршрутов. Это делает нашу маршрутизацию более выразительной.

//This...
const myApp = {
  index: <HomePage />,
  sum: (a,b) => <Sum a={a} b={b} />
}
//... is the same as
const myApp = {
  index: () => [HomePage],
  sum: (a, b) => [Sum, { a, b }]
};
ReactDOM.render(Router(myApp), mountNode);

Для чего это нужно? Дочерние маршруты. Читать дальше.

Дочерние маршруты

Давайте теперь внесем реальную сложность в нашу маршрутизацию. Наша цель - определить эти три маршрута.

  1. / team (teamId) - возвращает компонент TeamPage
  2. /team(teamId).player(jerseyNumber) - возвращает PlayerComponent внутри TeamPage
  3. /team(teamId).player(jerseyNumber).game(gameId) - возвращает GameComponent внутри PlayerComponent внутри TeamPage

Родительские маршруты, такие как / team (10), должны вызываться сами по себе, а также вместе с дочерними компонентами. например: /team(10).player(2).game(23)

У Sailboat для этого есть сокращенный синтаксис:

const myApp = {
  team: teamId => [
    TeamPage, //Component
    { teamId }, //Props
    { //Child routes
      player: jerseyNumber => [
        PlayerComponent, //Component
        { jerseyNumber }, //Props
        { //Child routes
          game: gameId => [GameComponent, { gameId }]
        }
      ]
    }
  ]
};
ReactDOM.render(Router(myApp), mountNode);

Вот как вы определяете маршруты с помощью Sailboat. Обратите внимание, что не было необходимости определять «индексные» маршруты для сопоставления только с родителем.

Асинхронные обработчики и потоковая передача

Что, если вы хотите, чтобы страница команды отображалась только после получения всех данных команды? Давай попробуем.

const myApp = {
  async team(teamId) {
    const team = await getTeamFromDatabase();
    return [
      TeamPage,
      { team },
      {
        async player(jerseyNumber) {
          const player = await team.getPlayer(jerseyNumber);
          return [
            PlayerComponent,
            { player },
            {
              game: gameId => [GameComponent, { gameId }]
            }
          ];
        }
      }
    ];
  }
};
ReactDOM.render(Router(myApp), mountNode);

Небольшая проблема с приведенным выше кодом заключается в том, что страница обновляется только после завершения всех асинхронных вызовов. В идеале вы должны показывать счетчик (индикаторы «загрузка…»), ожидая прибытия данных. Верно?

Парусник позволяет делать это с помощью генераторов. Вот переписанная функция player (). (Часть кода удалена для краткости).

{
  //....
  async *player(jerseyNumber) {
    //show a spinner
    yield <Spinner />;
    const player = await team.getPlayer(jerseyNumber);
    //show the real thing
    return [
      PlayerComponent,
      { player },
      {
        game: gameId => [GameComponent, { gameId }]
      }
    ];
  }
};

Он отображает Spinner при извлечении данных. Как только данные становятся доступными, он отображает фактический PlayerComponent.

Кстати, вы можете продолжать потоковую передачу HTML, даже не возвращаясь. Следующий маршрут передает секунды.

{
  //....
  async *seconds() {
    let counter = 0;
    while(true) {
      yield <div>${counter} seconds have passed.</div>
      await sleep(1);
      counter++;
    }
  }
}

Автоматически работает с Redux и т.п.

Поскольку обработчики маршрутов в Sailboat - это простые функции, он автоматически работает с библиотеками управления состоянием, такими как Redux.

В следующем примере изменение маршрута вызывает выполнение действия. Действие могло вызвать изменение состояния и, таким образом, повторную визуализацию пользовательского интерфейса.

//Callable as /getTeam(245)
const myApp = {
  getTeam(teamId) {
    actions.loadTeam(teamId);
  }
};

Несколько экземпляров маршрутизатора

Sailboat возвращает обычный компонент React, который можно использовать так же, как и любой другой компонент React. Совершенно нормально иметь несколько экземпляров Sailboat Router на одной странице.

import { Router } from "sailboat";
const routes1 = {
  customers: { index: <CustomersPage /> }
}
const routes1 = {
  orders: { index: <OrdersPage /> }
}
const Customers = Router(routes1);
const Order = Router(routes2);
const App =
  <div>
    <Customers />
    <Orders />
  </div>;
ReactDOM.render(<App />, mountNode);

Должна быть возможность встраивать экземпляры Sailboat в приложение, управляемое другим маршрутизатором, таким как React Router, или даже внутри приложения Angular или Backbone.

Рендеринг на стороне сервера

При рендеринге на сервере метод navigateTo должен вызываться перед renderToString (). Вот пример с ExpressJS.

import { Router, navigateTo } from "sailboat";
const myRoutes = { 
  //....omitted for brevity
};
router.get("*", (req, res) => {
  navigateTo(req.url).then(() => {
    const content = ReactDOMServer.renderToString(Router(myRoutes));
    res.render("index", { title: "Sail", data: false, content });
  });
});

Если вы выполняли рендеринг в DOM, вы могли бы вызвать navigateTo после вызова ReactDOM.render ().

Парусник против React Router

Sailboat отличается от React Router (и большинства других) тем, что использует NSOAP в качестве соглашения для определения маршрутов. Это дает вам асинхронные маршруты, потоковую передачу компонентов, простую интеграцию с другими библиотеками, знакомый синтаксис JS и большую гибкость. Все из коробки.

А как насчет вложения маршрутов внутри таких компонентов, как React Router v4?

Что вы можете. Оставил читателю в качестве упражнения.

Не нравится точечная запись?

Вы можете использовать «/» вместо «.» при доступе к свойствам объекта с помощью параметра useSlash. Это позволяет вам получить доступ к URL-адресу «/team.player.game» как «/ team / player / game».

//omitted for brevity
ReactDOM.render(Router(myApp, { useSlash: true }), mountNode);

История браузера

Используйте API HTML5.

Примеры приложений

Перейти на Площадку для парусников.