Когда я начал изучать React, я познакомился с React Router и узнал, как эта библиотека может помочь нам в веб-приложении.

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

Прежде чем включать это в свое приложение, я создал приложение React, чтобы проверить свою идею и протестировать только это поведение. Давай проверим.

Создание нового приложения React

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

Довольно легко проверить и протестировать решение по отдельности.

Чтобы создать новое приложение React, следуйте инструкциям в официальной документации.

npx create-react-app test-router-sreen
cd test-router-sreen
npm start / yarn start

Давайте начнем создавать простой JSX, в нашем «приложении» будет всего три компонента: устройство / мобильное устройство / приложение, последний - наш индекс.

В своем тестовом приложении я использовал Material UI (начните здесь), потому что это библиотека, с которой я люблю работать, но вы можете использовать только HTML и вставить CSS, если хотите.

Тест приложения

Идея этого приложения состоит в том, чтобы иметь экран с кнопкой «Показать мобильный», и когда пользователь выполняет действие «щелкнуть» по этой кнопке, компонент отображается с сообщением: «Я отображается только на мобильном устройстве» и при перемещении размера экрана кнопка меняется на «Показать устройство», затем при нажатии на кнопку отображается сообщение «Я обрабатываюсь только на устройстве».

//App
import React, { Component } from 'react'
import Button from '@material-ui/core/Button'
import Hidden from '@material-ui/core/Hidden'
class App extends Component {
  render() {
    return (
     <div className="App">
      <Hidden xsDown>
       <Button variant="contained" color="primary">
         Show Device
       </Button>
       </Hidden> 
       <Hidden smUp>
        <Button variant="contained" color="secondary">
          Show Mobile
        </Button>
       </Hidden>
      </div>
     ); 
    }
   }
export default App;

Я применил пользовательский интерфейс Hidden of Material для отображения / скрытия кнопки в точках останова.

Компоненты Device и Mobile были созданы очень просто.

//Device//
import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
class Device extends Component {
  render() {
    return (
      <div>
        <Typography component="h2" variant="h1" gutterBottom>
          I am rendered only on Device :)
        </Typography>
      </div>
    );
  }
}
export default Device;
//Mobile//
import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
class Mobile extends Component {
  render() {
    return (
      <div>
        <Typography component="h2" variant="h1" gutterBottom>
          I am rendered only on Mobile :)
        </Typography>
      </div>
    );
  }
}
export default Mobile;

Запуск React Router

Теперь задача заключается в следующем:

  1. Чтобы показать компонент "Мобильный" при нажатии кнопки "Мобильный" или для отображения компонента "Устройство" при нажатии кнопки "Устройство", изменив URL-адрес на / mobile или / device .
  2. При перемещении размера экрана с отображаемым компонентом маршрут должен автоматически переключаться между / mobile ’на индексирование« / »или / device’ на индексирование «/».

Чтобы использовать React Router в нашем проекте, мы используем инструкции из документации для установки пакета.

npm install react-router-dom
or
yarn add react-router-dom

Прежде всего, давайте создадим маршрут внутри кнопок Mobile и Device. Мы не можем забыть импортировать зависимости.

//App
...
import 
{ BrowserRouter as Router, 
Route, 
Link } from "react-router-dom"
import Mobile from './Mobile'
import Device from './Device'

И включите Route в код с Link на кнопках.

class App extends Component {
  render() {
    return (
     <Router>
      <div className="App">
      <Hidden xsDown>
       <Link to='/mobile'>
        <Button variant="contained" color="primary">
          Show Device
        </Button>
       </Link>
      </Hidden> 
      <Hidden smUp>
       <Link to='/mobile'>
        <Button variant="contained" color="secondary">
          Show Mobile
        </Button>
       </Link>
     </Hidden>
     <Route path="/device" component={Device} />
     <Route path="/mobile" component={Mobile} />
    </div>
   </Router>
  ); 
 }
}
export default App;

Хорошо, маршрут для компонентов привязан к кнопке, когда мы нажимаем на рендеринг компонента и меняем путь, но сейчас отсутствует важный момент. Во время изменения размера экрана измените маршрут, но не отобразите фактический компонент.

Чтобы увидеть событие изменения размера на экране и отправить действие, используйте пакет React Resize Detector.

Установка…

npm i react-resize-detector
// or
yarn add react-resize-detector

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

Добавление перенаправления при импорте response-router-dom и React Resize Detector.

//App
...
import 
{ BrowserRouter as Router, 
Route, 
Link,
Redirect } from "react-router-dom"
import ReactResizeDetector from 'react-resize-detector'
...

Сначала мы включаем the<ReactResizeDetector /> в дерево DOM и проверяем точку останова UI материала для мобильных устройств, где изменение кнопки с устройства на мобильное устройство меньше ширины 600 пикселей.

Используя эту информацию, добавьте константу с правилом изменения размера, используя тернарный оператор и Redirect do Router, чтобы изменить маршрут в соответствии с с из размера экрана. Мы создали функцию CheckIfRedirect.

const CheckIfRedirect = ({width, height}) => {
  const path = window.location.pathname
  console.log('resize', width, height, path)
if ((width < 600 && path === "/device") || (width >= 600 && path === "/mobile")) {
    return <Redirect to={{pathname: "/"}} />
  } else {
    return <></>
  }
}

В соответствии с этим правилом он специфичен, когда ширина меньше 600 пикселей, а путь '/ device' или с больше или равен 600 пикселей и путь '/ mobile ', а затем перенаправить на путь: ' / ' в данном случае - это наш индекс без определенного компонента.

Информацию window.location.pathname можно найти, просматривая консоль браузера.

Итак, разделив код внутри функции App на другую функцию, мы назвали Body.

const Body = () => (
  <div className="App">
    <Hidden xsDown>
      <Link to='/device'>
        <Button variant="contained" color="primary">
            Show Device
        </Button>
      </Link>
    </Hidden>
    <Hidden smUp>
      <Link to='/mobile'>
        <Button variant="contained" color="secondary">
            Show Mobile
        </Button>
      </Link>
    </Hidden>
    <Route path="/device" component={H1Device} />
    <Route path="/mobile" component={H1Mobile} />
  </div>
)

Чтобы упорядочить наш код, внутри приложения мы включим маршрутизатор только в Body, theReactResizeDetector и эту новую функцию CheckIfRedirect.

class App extends Component {
  render() {
    return (
      <Router>
        <>
          <Body />
          <ReactResizeDetector handleWidth handleHeight>
            <CheckIfRedirect />
          </ReactResizeDetector>
        </>
      </Router>
    );
  }
}

В итоге полный код нашего приложения:

import React, { Component } from 'react'
import Button from '@material-ui/core/Button'
import Hidden from '@material-ui/core/Hidden'
import { BrowserRouter as Router, Route, Link, Redirect } from "react-router-dom"
import Mobile from './Mobile'
import Device from './Device'
import ReactResizeDetector from 'react-resize-detector'
const Body = () => (
  <div className="App">
    <Hidden xsDown>
      <Link to='/device'>
        <Button variant="contained" color="primary">
            Show Device
        </Button>
      </Link>
    </Hidden>
    <Hidden smUp>
      <Link to='/mobile'>
        <Button variant="contained" color="secondary">
            Show Mobile
        </Button>
      </Link>
    </Hidden>
    <Route path="/device" component={H1Device} />
    <Route path="/mobile" component={H1Mobile} />
  </div>
)
const CheckIfRedirect = ({width, height}) => {
  const path = window.location.pathname
console.log('resize', width, height, path)
if ((width < 600 && path === "/device") || (width >= 600 && path === "/mobile")) {
    return <Redirect to={{pathname: "/"}} />
  } else {
    return <></>
  }
}
class App extends Component {
  render() {
    return (
      <Router>
        <>
          <Body />
          <ReactResizeDetector handleWidth handleHeight>
            <CheckIfRedirect />
          </ReactResizeDetector>
        </>
      </Router>
    );
  }
}
export default App;

Конечный результат

На изображении ниже можно увидеть, как кнопка меняется с Устройство на Мобильное.

И нажатие на кнопку изменение пути.

Тестируем наше приложение

Очень важно написать тест, чтобы удостовериться, что все работает должным образом.

Я нашел информацию о том, как протестировать это приложение, в документации React-Testing-Examples. Еще одна библиотека, которая мне очень помогает, - это Библиотека тестирования DOM.

Установка…

npm install react-testing-library
or
yarn add react-testing-library

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

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { render, waitForElement } from 'react-testing-library'
const renderComponent = () => (
  render(
    <App />
  )
)
test('renders without crashing', async () => {
  const { getByText } = renderComponent();
await waitForElement(() => getByText('Show Device'));
});

Теперь тестируем событие, чтобы щелкнуть по кнопке и отобразить сообщение о том, что браузер настроен на размер устройства.

Чтобы имитировать щелчок, мы добавляем fireEvent, передавая сообщение, которое должно быть отображено в виде строки.

...
import { render, waitForElement, fireEvent } from 'react-testing-library'
...
test('click on show device', async () => {
  const { getByText } = renderComponent();
fireEvent.click(getByText('Show Device'));
  await waitForElement(() => getByText('I am rendered only on Device:)'));
});

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

Для этого мы включаем функцию, используя global.innerWidth в качестве стандартного размера для устройства (1024 пикселя) и добавляя global.dispachEvent для отправки нового события изменения размера.

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

...
afterEach(() => {
  global.innerWidth = 1024
  global.dispatchEvent(new Event('resize'))
});
test('click on show device', async () => {
  const { getByText } = renderComponent();
fireEvent.click(getByText('Show Device'));
  await waitForElement(() => getByText('I am rendered only on Device:)'));
});
test('click on show mobile', async () => {
  // Change the viewport to 500px.
  global.innerWidth = 500;
// Trigger the window resize event.
  global.dispatchEvent(new Event('resize'));
const { getByText } = renderComponent();
  fireEvent.click(getByText('Show Mobile'));
  await waitForElement(() => getByText('I am rendered only on mobile ;)'));
});
test('resize to mobile', async () => {
  const { getByText } = renderComponent();
  // Change the viewport to 500px.
  global.innerWidth = 500;
// Trigger the window resize event.
  global.dispatchEvent(new Event('resize'));
// wait react component render after event dispatch
  await waitForElement(() => getByText('Show Mobile'));
  fireEvent.click(getByText('Show Mobile'));
  await waitForElement(() => getByText('I am rendered only on mobile ;)'));
});

В конце концов…

Это одно из решений для использования React Router и изменения пути в соответствии с изменением размера экрана, полезно в приложении, где необходимо изменить маршрут и отображаемый компонент, переходя с мобильного на устройство (или наоборот).

Я знаю, что существуют другие способы получить тот же конечный результат с использованием разных инструментов. А вы, как разработать свой код, чтобы создать что-то подобное? Для меня приятно получать разные мнения и технический опыт, чтобы делиться знаниями. :)

Вы можете прочитать этот пост на португальском здесь.