Приложение Sportsbet для iOS и веб-сайт созданы на основе одной и той же кодовой базы. Наша компания создает продукты и функции для наших клиентов, которые реализуются для обеих этих платформ одной и той же командой разработчиков.

Мы разделяем весь наш код, кроме пользовательского интерфейса - эта часть реализована независимо как react-dom и react-native. Из этого общего кода у нас есть логика того, как должен вести себя конкретный маршрут, а именно, при одном и том же веб-URL веб-сайт и приложение iOS должны отображать одну и ту же функцию.

Тем самым он позволяет нашим цифровым рекламным акциям, маркетинговым кампаниям и социальным ссылкам использовать один URL-адрес, гарантируя, что бизнесу будет комфортно знать, что веб-сайты и приложение iOS будут обрабатывать эту единственную ссылку одинаково.

На нашем веб-сайте для маршрутизации используется response-router. В нашем приложении для iOS используется реагирующая навигация. Итак, как сделать так, чтобы две разные системы обрабатывали URL-адреса одинаково? Или, в качестве альтернативы, как вы можете заставить приложение iOS использовать веб-URL?

Определите маршруты

У нас есть общий файл в каждом модуле (модуль в этом контексте означает папку, предназначенную для основной области функций в приложении). Он содержит список экспортированных констант, которые будут использоваться каждым маршрутизатором. (Помните, что у нас есть response-router и response-navigation).

// src/modules/sports/sports_routes.ts
export const sportsRootPath = "/betting"
export const sportsClassExactPath = `${sportsRootPath}/:class/`
...

Обратите внимание на использование переменных пути (например, :class) - они будут полезны позже. Мы извлечем их и передадим в качестве параметров в response-navigation.

У нас намного больше маршрутов, чем в этом примере, некоторые из них имеют 8 переменных пути и совпадений с регулярными выражениями - но этого должно быть достаточно для демонстрации.

Используйте маршруты в response-router для Интернета

Это легкая часть.

// web/index.tsx 
import { BrowserRouter } from "react-router-dom"
import { Route, Switch } from "react-router"
<BrowserRouter>
  <Switch>
    ... 
    <Route
      path={`${linkBase}${sportsClassExactPath}`}
      exact={true}
      render={ourWebScreenForSportsClass(props)}
    />
    ...
  </Switch>
</BrowserRouter>

Отлично - это веб-сортировка.
Запросы к https://www.sportsbet.com.au/betting/australian-rules вернут содержимое ourWebScreenForSportsClass(props) - и реквизиты будут включать.

{
  class: "australian-rules"
}

Узнайте, как React Navigation поддерживает маршруты

React Navigation поддерживает маршрутизацию через статические строки. Если вы помните, что создание a NavigationContainer происходит один раз (через crateAppContainer(...)), вам необходимо знать маршруты заранее - это затрудняет поддержку динамических маршрутов или маршрутов, содержащих параметры, как в нашем примере выше.

Мы определяем нашу навигацию в iOS через сложное расположение stackNavigator и switchNavigator. В следующем фрагменте показано наше определение экрана SportsClass (версия веб-маршрута для iOS, приведенная выше).

// deep inside a createStackNavigator function implementation...
...
},
"/SportsClassScreen": (props: NavigationScreenProps) => {
  if (!SportsClassScreen) {
    SportsClassScreen = React.lazy(() => import("../../../sports/components_ios/class/sports_class_screen").then(imported => {
      return { default: imported.SportsClassScreen }
    }))
}
return (
  <Suspense fallback={null}>
    <SportsClassScreen {...props} liveServClient={liveServClient} />
  </Suspense>
)
},
...

При такой настройке единственный способ добраться до SportsClassScreen - перейти к “/SportsClassScreen”, например. this.props.navigation.navigate("/SportsClassScreen"). Это нормально, если у вас есть статические маршруты, для которых не нужны параметры, но мы так не работаем. Наши приложения очень динамичны. Содержимое наших экранов получено из API-интерфейсов, и эти API-интерфейсы предоставляют общие маршруты для ресурсов, например веб-адреса.

Таким образом, при таком способе работы с React Navigation у нас не было бы возможности поддерживать маршрутизацию во время выполнения и обработку параметров. Этот пример - только один из примерно 150 различных динамических маршрутов, которые должны поддерживать наши приложения. Учитывая тот факт, что наша маршрутизация по своей сути неизвестна, нам нужно было улучшить реагирующую навигацию.

Улучшение React Navigation для поддержки динамических маршрутов

Когда вы создаете StackNavigator, у вас есть возможность обезвредить объект маршрутизатора навигатора.

Нам нужна возможность превращать строку, к которой мы перемещаемся,
, например, "/betting/australian-rules"
, во что-то, что использует наш "/SportsClassScreen" маршрут в нашем навигаторе стека, и получать те же реквизиты, что и сеть:

{
  class: "australian-rules"
}

Итак, нам нужен какой-то сопоставитель, который будет искать известные шаблоны в строке и возвращать маршрут React Navigation для перехода, и было бы здорово, если бы он также поддерживал динамические параметры.

Создайте новый файл. Его цель - сопоставить URI экранным именам и извлечь параметры.

// router.ts
import { match, matchPath } from "react-router"
import URI from "urijs"
const staticRouteMap = [
...
{
  screen: "/SportsClassScreen",
  paths: [{
    path: sportsClassExactPath, // "/betting/:class/"
    loadData: loadSportsClassRouteData // function to fetch data
  }]
},
...
]
export function mapPathToScreen(path) {
  const pathUri = new URI(path)
  // Remove the protocol and domain from the url if present
  if (pathUri.origin()) {
    path = path.replace(pathUri.origin(), "")
  }
  const length = staticRouteMap.length
  for (let i = 0; i < length; i++) {
    const route = staticRouteMap[i]
    for (let j = 0; j < route.paths.length; j++) {
      const routesPathObject = route.paths[j]
      const match = matchPath(path, {
        path: routesPathObject.path,
        exact: true
      })
      if (!!match) {
        let loadData = null
        if (routesPathObject.loadData) {
          loadData = () => routesPathObject.loadData(match)
        }
        const result = {
          key: route.screen,
          params: {
            ...match.params, // this is where we get the URL params
            originalPath: path
          },
          loadData // provide the load data function back
        }
        return result
      }
    }
  }
  return null
}

Итак, учитывая строку

"www.sportsbet.com.au/betting/australian-rules"

когда мы прогоняем его через mapPathToScreen, мы получаем

const test = mapPathToScreen("www.sportsbet.com.au/betting/australian-rules")
console.log(test) 
{
  key: "/SportsClassScreen",
  params: {
    class: "australian-rules",
    originalPath: "/betting/australian-rules"
  },
  loadData: function()
}

Отлично. Теперь пора обмануть React Navigation в маршрутизации к “/SportsClassScreen” всякий раз, когда ему говорят о маршрутизации к “www.sportsbet.com.au/betting/someSportName” - И дать ему некоторые параметры, чтобы экран мог отображать динамический контент. Мы - подлые чертишки.

const navigator = createStackNavigator({
  yourTremendouslyComplexNavigationRules
})
const oldRouter = navigator.router
// strap in, here we go...
navigator.router = {
  ...oldRouter,
  getStateForAction: (action, state) => {
    if (action.routeName) {
      // turn the web url into a screen that we can navigate to
      const mappingResult = mapPathToScreen(action.routeName)
      if (!!mappingResult) {
        const {
          key,
          params,
          loadData
        } = mappingResult
        // routeName used to be a URI, turn it into a known screen                  
        action.routeName = key
        // append the params found in `mapPathToScreen`
        action.params = {
          ...action.params,
          ...params
        }
      }
    }
    /**
    Deception time - tell React Navigation to perform the  
    original routing but now your `action` object resembles
    something that it is capable or routing to
    **/
    return oldRouter.getStateForAction(action, state)
  }
}
return createAppContainer(navigator)

Теперь ваш навигатор по стеку должен иметь возможность выполнять маршрутизацию на разные экраны на основе ваших существующих строк навигации, а также через список параметров динамической маршрутизации, которые вы можете расширять по мере роста функциональности вашего приложения.

Поддержка диплинков на динамические маршруты

Наш веб-сайт поддерживает множество различных путей, и мы хотим, чтобы наше приложение для iOS им соответствовало. Мы используем Универсальные ссылки в нашем приложении, чтобы наши клиенты могли запускать приложение, когда они нажимают на поддерживаемый нами веб-URL - примером может быть наш любимый https: //www.sportsbet. com.au/betting/australian-rules »- нажатие на него должно открыть приложение iOS и направить пользователя к компоненту SportsClassScreen (и это действительно так, попробуйте, если у вас установлено наше приложение).

Следуйте руководству, чтобы включить универсальные ссылки.

Если вы обновите навигатор стека, как описано выше, ваше приложение теперь поддерживает маршрутизацию на основе URI. Не стесняйтесь переходить к URI, который вызвал запуск приложения - и ваш router.ts файл заставит React Navigation перенаправить на ваш экран вместо этого :)

// yourTopmostMountedComponent.tsx
// assuming that navigation is available on this component
componentDidMount() {
  Linking.addEventListener("url", this.handleIncomingLink)
}
componentWillUnmount() {
  Linking.removeEventListener("url", this.handleIncomingLink)
}
private const handleIncomingLink = (link) => {
  const uri = new URI(link.url)
  const intendedRoute = uri.path()
  this.props.navigation.navigate(intendedRoute)
}

Практически без каких-либо манипуляций с входящей ссылкой, теперь мы можем перейти к согласованному пути на основе URI, который использовался для запуска нашего приложения.

Теперь ваше приложение может реагировать на универсальные ссылки, и, более того, оно может переходить по вашим маршрутам, определенным в вашем stackNavigator, на основе вашего веб-URI.

Маркетинговые кампании теперь могут отправлять веб-URL, и ваша кодовая база может обрабатывать его, независимо от того, является ли интерфейсный канал веб-браузером или у них установлено ваше приложение.