Предпосылки

Глава 1 | Привет, виртуальный мир

Сфера действия этой главы

В предыдущей главе мы смогли познакомиться с React VR и отобразить красивую панорамную фотографию Horseshoe Bend.

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

В конце этой главы мы создадим интерактивное приложение VR под названием Panoramic Road Trip. Это приложение позволит нам выбрать пункт назначения из меню и посетить его на 360 градусов.

Надеюсь, вы так же взволнованы, как и я. Давайте начнем.

Инкапсуляция данных

Как и стандартный React, React VR позволяет нам инкапсулировать данные в компонент либо с помощью props, либо state. Мы рассмотрим каждый пример, чтобы увидеть это в действии.

Реквизит

свойства используются для данных, которые доступны только для чтения. Другими словами, он используется для данных, которые не будут изменены. В настоящее время наш компонент HelloVirtualWorld в нашем файле index.vr.js содержит текст, доступный только для чтения:

<Text> Hello Virtual World </Text>

Давайте заменим это реквизитом.

Во-первых, давайте создадим новый компонент под названием NestedMessage:

class NestedMessage extends React.Component {
render() {
    return (

    );
  }
};

После этого вырежьте компонент Text в HelloVirtualWorld и вставьте его в ответ NestedMessage:

class NestedMessage extends React.Component {
render() {
    return (
      <Text
        style={{
          backgroundColor: '#777879',
          fontSize: 0.8,
          fontWeight: '400',
          layoutOrigin: [0.5, 0.5],
          paddingLeft: 0.2,
          paddingRight: 0.2,
          textAlign: 'center',
          textAlignVertical: 'center',
          transform: [{translate: [0, 0, -5]}],
        }}>
        Hello Virtual World
      </Text>
    );
  }
};

Теперь давайте вложим NestedMessage и передадим опору в компонент HelloVirtualWorld:

export default class HelloVirtualWorld extends React.Component {
render() {
    return (
      <View>
        <Pano source={asset('horseshoe-bend.jpg')}/>
        <NestedMessage message={"Hello Nested Message"}/>
      </View>
    );
  }
};

Затем мы можем использовать эту опору message в качестве текста:

<Text style={{...}}>{this.props.message}</Text>

Если вы сделали перерыв между главами, откройте наш проект в командной строке и запустите:

npm start

Теперь мы должны увидеть опору сообщения, отображаемую в виде текста:

Идеально! Это было просто.

Состояние

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

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

Перво-наперво вы можете удалить опору сообщения:

<NestedMessage/>

Затем давайте добавим конструктор с this.state, определенный в нашем компоненте NestedMessage:

class NestedMessage extends React.Component {
  constructor() {
    super();
    this.state = {};
  }
  
  //render function here
};

Затем мы можем добавить свойство в наше состояние под названием message:

this.state = {message: "Hello State Message"};

Наконец, мы изменим это. props .message на это. state .message:

{this.state.message}

Как и следовало ожидать, теперь мы можем видеть наше сообщение, которое контролируется местным государством:

Милая! Теперь попробуем обновить сообщение.

Чтобы обновить сообщение, мы добавим setTimeout в наш конструктор , который установит новое состояние с обновленным сообщением через 5 секунд:

class NestedMessage extends React.Component {
  constructor() {
    super();
    this.state = {message: "Hello State Message"};
    setTimeout(() => {
      this.setState({ message: "Hello Updated Message" });
    }, 5000);
  }
  //render function here
};

Обновите localhost, и мы увидим, что все работает как положено:

Холодные бобы! Прежде чем продолжить, не забудьте удалить setTimeout в нашем конструкторе.

Обработка событий

props и state были такими же, как мы привыкли в стандартном React.

Обработка событий также работает аналогично, но будут новые концепции.

Во-первых, какие типы событий доступны?

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

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

В документации выше вы увидите, какие события мы можем обрабатывать: onLongPress и onPress.

Нажатие относится к событию сенсорного экрана. У меня нет компьютера с сенсорным экраном, поэтому нам нужно найти способ обойти это.

В React VR есть компонент VrButton. В отличие от кнопки HTML, она не имеет внешнего вида по умолчанию и должна быть обернута вокруг другого компонента, когда мы хотим фиксировать события, подобные кнопкам.

Например, в нашем компоненте «Текст» есть только события для прессы. Если бы мы обернули его вокруг VrButton, мы могли бы получить эффект щелчка по нашему тексту и запуска обработчика событий, даже если событие было технически захвачено в VrButton.

Это сложно визуализировать, не видя кода, так что давайте перейдем к нему.

Давайте проверим это в нашем компоненте NestedMessage.

Сначала мы импортируем VrButton:

import {
  AppRegistry,
  asset,
  Pano,
  Text,
  View,
  VrButton
} from 'react-vr';

Затем давайте обернем компонент VrButton вокруг нашего текущего компонента Text:

<VrButton>
  <Text style={{...}}>
  {this.state.message}
  </Text>
</VrButton>

Кроме того, теперь у нас есть компонент Text и компонент VrButton в нашей функции рендеринга NestedComponent. Следовательно, нам нужно обернуть их вокруг самого внешнего компонента ‹View›, как если бы мы обернули внешний элемент ‹div› в стандартном React:

<View>
  <VrButton>
    <Text style={{...}}>
    {this.state.message}
    </Text>
  </VrButton>
</View>

Затем мы добавим метод, который будет обрабатывать событие onClick. В этом методе мы хотим обновить локальное состояние новым сообщением:

//constructor here
handleClick () {
  this.setState({ message: "Updated Message" });
}

Теперь мы хотим настроить событие trigger в открывающем теге нашего компонента VrButton:

<VrButton 
  onClick={this.handleClick.bind(this)}
>
  <Text>Click me!</Text>
</VrButton>

Обратите внимание, что мы связываем this, чтобы мы могли вызывать наши методы обработки событий с помощью this.handleClick.

Сохраните изменения и обновите локальный хост.

Woohoo! Мы провели первое мероприятие в React VR!

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

Крючки жизненного цикла

Этот шаг действительно прост. Давайте просто протестируем, используя жизненный цикл React, чтобы обновить наше сообщение:

//constructor here
componentDidMount () {
  this.setState({ message: "Mounted" });
}

Мы должны увидеть, что это работает без проблем:

Здесь никаких изменений, так что давайте продолжим.

Условный рендеринг

Давайте также протестируем условный рендеринг в React VR.

Во-первых, давайте добавим к нашему состоянию логический флаг под названием showMessage и установим для него значение true по умолчанию :

this.state = {message: "Hello State Message", showMessage: true};

Затем давайте сохраним это в переменной в нашей функции рендеринга:

render() {
  const showMessage = this.state.showMessage;
  //return here  
}

Затем давайте вернем отображение нашего сообщения, если showMessage истинно, и вернем пустой текстовый компонент (ничего не показывать), если showMessage имеет значение false:

return (
  <View>
    {showMessage ? (
      <VrButton onClick={this.handleClick.bind(this)}>
        <Text
          style={{
            backgroundColor: '#777879',
            fontSize: 0.8,
            fontWeight: '400',
            layoutOrigin: [0.5, 0.5],
            paddingLeft: 0.2,
            paddingRight: 0.2,
            textAlign: 'center',
            textAlignVertical: 'center',
            transform: [{translate: [0, 0, -5]}],
          }}
        >
        {this.state.message}
        </Text>
      </VrButton>
    ) : (
      <Text></Text>
    )}
  </View>
);

Наконец, давайте обновим showMessage до false в только что созданном жизненном цикле:

componentDidMount () {
  this.setState({ showMessage: false });
}

Теперь мы должны увидеть, что сообщение не появляется после монтирования нашего компонента:

Макет Flexbox

Создание любого пользовательского интерфейса требует организации макета. Используя сеточную систему, такую ​​как react-grid-system, которую я описываю здесь, вы организуете пользовательский интерфейс в строки и столбцы.

Например, предположим, что мы хотим отобразить следующий раздел:

Что касается строк и столбцов, этот раздел будет состоять из 1 строки и 2 столбцов:

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

В системе сетки вы должны определить ширину каждого столбца.

Например, сеточная система Bootstrap позволяет вам определять ширину столбцов таким образом, чтобы предоставить вам все следующие параметры для строки:

Понимание основ Flexbox

Макет Flexbox в React VR работает немного иначе, поскольку он предназначен для того, чтобы сделать макет более гибким.

Давайте объясним эти концепции Flexbox, используя несколько хороших наглядных пособий.

В Flexbox мы начинаем с контейнера с заданной шириной:

Затем мы должны указать направление нашего контейнера (если мы хотим, чтобы контейнер был строкой или столбцом).

Вот пример направления строки:

Вот пример направления столбца:

Допустим, мы указали строку, Flexbox незаметно рисует горизонтальную линию главной оси следующим образом:

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

Продолжая наш пример строки, давайте добавим 3 элемента:

По умолчанию Flexbox умещает все элементы в одну строку контейнера. Поэтому, если бы мы хотели добавить еще один блок к нашему контейнеру, показанному выше, все блоки уменьшились бы, так что все они уместились:

Когда основная ось рисуется с помощью Flexbox, существует поперечная ось, которая проходит перпендикулярно основной оси:

Если бы мы указали направление столбца, основная ось будет проходить вертикально, а поперечная ось - горизонтально.

Собирая все вместе, вот финальная визуализация нашей строки:

На этом завершаются основные принципы Flexbox. Однако есть еще кое-что, что нужно осветить.

Давайте обсудим это подробнее, поскольку мы используем макет Flexbox для отображения 5 текстовых компонентов в столбце в нашем виртуальном мире.

Создание нового проекта

К концу этой главы мы создадим новый проект для приложения VR, которое мы хотим создать, под названием Панорамная поездка.

В этом новом проекте мы начнем с добавления наших 5 текстовых компонентов в столбец с использованием макета Flexbox.

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

Затем запустите следующее:

react-vr init PanoramicRoadTrip

После этого добавьте папку проекта в Atom и откройте index.vr.js.

Реализация макета Flexbox

Давайте начнем с удаления компонента Text по умолчанию, чтобы у нас осталось:

<View>
  <Pano source={asset('chess-world.jpg')}/>
</View>

Затем скопируйте Horsehoe-bend.jpg из папки static_assets в HelloVirtualWorld в папку static_assets в нашем новом проекте. .

После этого обновите источник компонента Pano:

<Pano source={asset('horseshoe-bend.jpg')}/>

Теперь мы готовы приступить к работе с Flexbox.

Под нашим компонентом Pano нам нужно добавить внешний компонент View, который будет контейнером Flex для всех наших текстовых компонентов:

<View>
  <Pano source={asset('horseshoe-bend.jpg')}/>
  <View>
  </View>
</View>

Затем давайте определим ширину нашего компонента View:

<View
  style={{
    width: 2
  }}
>
</View>

Единицы измерения в стиле React VR указаны в метрах. Мы указываем, что наш контейнер Flex имеет ширину 2 метра.

Мы хотим сделать этот контейнер столбцом, поэтому мы можем добавить значение flexDirection:

<View
  style={{
    width: 2,
    flexDirection: 'column'
  }}
>
</View>

На данный момент наш контейнер View выглядит под капотом:

Затем мы хотим указать, как мы хотим выровнять и выровнять наши элементы в этом контейнере столбца. Здесь нам придется выйти за рамки основ Flexbox, которые мы рассмотрели ранее.

Свойство для выравнивания наших элементов - itemAlign.

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

Есть 4 возможных значения для выравнивания наших элементов с Flexbox в React VR: stretch, flex-start, center или flex-end.

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

Например, элементы в контейнере столбца будут растягиваться следующим образом:

Обратите внимание, что высота этого элемента будет указана вручную. Кроме того, в Flexbox по умолчанию используется stretch.

flex-start означает, что элементы будут располагаться в начале поперечной оси. Это будет самая левая точка столбца и самая верхняя точка строки.

Допустим, мы указали вручную ширину для элемента, который составляет 50% контейнера столбца. Элемент можно было бы выложить так:

flex-end разместит элементы в конце поперечной оси:

center будет располагать элементы по центру поперечной оси:

Расположение элемента по главной оси определяется свойством justify-content.

В React VR это может иметь 3 возможных значения: flex-start, space-around или space-between.

flex-start означает, что элементы будут размещены, начиная с верхней части главной оси.

В контейнере столбца, если элемент был выровнен по центру поперечной оси и выровнен с помощью flex-start, мы увидим следующее:

space-between означает, что элементы будут выровнены равномерно по главной оси с первым элементом в начале и последним элементом в конце:

space-around означает, что элементы будут выровнены равномерно по главной оси и равное пространство вокруг начала и конца:

Обсудив выравнивание и выравнивание элементов в стороне, давайте вернемся к нашему коду, который на данный момент выглядит следующим образом:

Во-первых, давайте укажем, что мы хотим, чтобы наш компонент View имел выравнивание stretch:

<View
  style={{
    width: 2,
    flexDirection: 'column',
    alignItems: 'stretch'
  }}
>
</View>

Затем давайте обосновываем наш контент с помощью flex-start:

<View
  style={{
    width: 2,
    flexDirection: 'column',
    alignItems: 'stretch',
    justifyContent: 'flex-start'
  }}
>
</View>

Затем добавьте преобразование с отрицательным значением Z, чтобы наш столбец отображался на расстоянии 5 метров:

<View
  style={{
    width: 2,
    flexDirection: 'column',
    alignItems: 'stretch',
    justifyContent: 'flex-start',
    transform: [{translate: [0, 0, -5]}]
  }}
>
</View>

Наконец, мы можем закончить стилизацию для нашего компонента View, поместив источник макета, который разместит этот контейнер в вертикальном и горизонтальном центре нашего виртуального мира:

<View
  style={{
    width: 2,
    flexDirection: 'column',
    alignItems: 'stretch',
    justifyContent: 'flex-start',
    transform: [{translate: [0, 0, -5]}],
    layoutOrigin: [0.5,0.5]
  }}
>
</View>

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

У нас будут текстовые компоненты для 5 состояний:

  • Аризона
  • Нью-Гемпшир
  • Калифорния
  • Гавайи
  • Техас
<View
  style={{
    flex: 1,
    width: 2,
    flexDirection: 'column',
    alignItems: 'stretch',
    layoutOrigin: [0.5, 0.5],
    transform: [{translate: [0, 0, -5]}]
  }}
>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>Arizona</Text>
  </View>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>New Hampshire</Text>
  </View>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>California</Text>
  </View>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>Hawaii</Text>
  </View>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>Texas</Text>
  </View>
</View>

В приведенный выше код мы включаем несколько простых стилей для наших текстовых полей (компоненты просмотра с вложенным текстовым компонентом) и нашего текста (текстовые компоненты).

Если мы перейдем к нашему локальному хосту и обновим его, мы увидим, что теперь он отображается как:

Потрясающие!

Итак, как это снова работает в соответствии с Flexbox?

Итак, мы определили столбец, поэтому направление главной оси идет вниз:

Поперечная ось проходит горизонтально.

Выравнивание элемента было задано как stretch, поэтому наши элементы расширяются до полной ширины контейнера, которая составляла 2 метра (поперечная ось).

Наш контент был оправдан с помощью flex-end, поэтому наши элементы располагались, начиная с верхней части главной оси.

Вот визуализация текстовых полей по отношению к нашему столбцу:

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

Давайте добавим оболочку этого нового компонента в наш index.vr.js:

class TextBoxes extends React.Component {
  render() {
    return (
      <View>
        
      </View>
    )
  }
}

Затем мы можем скопировать текстовые поля с их вложенным текстом в самый внешний компонент View, показанный выше:

class TextBoxes extends React.Component {
  render() {
    return (
      <View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>Arizona</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>New Hampshire</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>California</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>Hawaii</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>Texas</Text>
        </View>
      </View>
    )
  }
}

Наконец, давайте вложим этот новый компонент TextBoxes в наш компонент PanoramicRoadTrip:

export default class PanoramicRoadTrip extends React.Component {
  render() {
    return (
      <View>
        <Pano source={asset('Arizona.jpg')}/>
        <View
          style={{
            flex: 1,
            width: 2,
            flexDirection: 'column',
            alignItems: 'stretch',
            layoutOrigin: [0.5, 0.5],
            transform: [{translate: [0, 0, -5]}]
          }}
        >
          <TextBoxes/>
        </View>
      </View>
    );
  }
};

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

Завершение нашего панорамного путешествия

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

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

Давай перейдем к делу.

Передача реквизита

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

Для этого мы можем начать с создания объекта в функции рендеринга компонента PanoramicRoadTrip:

export default class PanoramicRoadTrip extends React.Component {
  render() {
    const states = {
      Arizona: "Arizona", 
      NewHampshire: "New Hampshire",
      California: "California",
      Hawaii: "Hawaii",
      Texas: "Texas"
   }
  //return here
}

Затем мы можем передать этот объект компоненту TextBoxes, чтобы все состояния были доступны как свойства:

<TextBoxes states={states}/>

Теперь мы можем использовать эти реквизиты для вставки текста для наших состояний:

class TextBoxes extends React.Component {
  render() {
    return (
      <View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.Arizona}</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.NewHampshire}</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.California}</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.Hawaii}</Text>
        </View>
        <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
          <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.Texas}</Text>
        </View>
      </View>
    )
  }
}

Добавление заголовка

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

Во-первых, мы можем добавить оболочку для нового компонента:

class Title extends React.Component {
  render() {
    return (
      <View>
      
      </View>
    )
  }
}

Затем давайте добавим текстовый компонент, который будет отображать заголовок:

<View>
  <Text 
    style={{
      fontSize: 0.2, 
      textAlign: 'center', 
      color: "#CF3C7E"
    }}
  >
  </Text>
</View>

Поскольку мы хотим, чтобы значение нашего заголовка обновлялось, нам нужно создать локальное состояние, которое определяет значение заголовка:

class Title extends React.Component {
  constructor() {
    super();
    this.state = {title: "Panoramic Road Trip"};
  }
  //render function
}

Наконец, давайте введем title, как определено в состоянии out в компоненте Text:

<Text style={{fontSize: 0.2, textAlign: 'center', color: "#CF3C7E"}}>
{this.state.title}
</Text>

Теперь, когда наш компонент Title определен, давайте разместим его прямо над нашим компонентом TextBoxes в нашем компоненте PanoramicRoadTrip:

<Title/>
<TextBoxes states={states}/>

Если мы проверим наш локальный хост и обновим, теперь мы должны увидеть следующее:

Случайный выбор панорамы

На этом этапе мы хотим добавить ловушку жизненного цикла, когда монтируется наш компонент. В этом хуке жизненного цикла мы выберем случайное число от 1 до 5. В зависимости от результата будет выбран штат (например, Калифорния) и будет визуализирована соответствующая панорамная фотография.

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

Перво-наперво, загрузите все фотографии и добавьте их в папку static_assets нашего проекта Panoramic Road Trip.

Вы заметите, что файлы фотографий названы в честь штатов.

Затем давайте добавим в наш компонент PanoramicRoadTrip следующий конструктор:

constructor() {
  super();
  this.state = {selectedState: ""};
}

selectedState - Аризона, Нью-Гэмпшир, Калифорния, Гавайи или Техас.

Это будет обновлено в ловушке жизненного цикла, где мы выбираем случайное состояние для выбора.

Давайте теперь добавим этот крючок жизненного цикла:

//in PanoramicRoadTrip component
componentDidMount() {
    const random = Math.floor((Math.random() * 5) + 1);
    let randState;
    switch(random) {
      case 1:
        randState = "Arizona";
        break;
      case 2:
        randState = "New Hampshire";
        break;
      case 3:
        randState = "California";
        break;
      case 4:
        randState = "Hawaii";
        break;
      case 5:
        randState = "Texas";
        break;
    }
    this.setState({ selectedState: randState});
}

В этом коде мы говорим: «Эй! Когда наше приложение монтируется, давайте получим случайное число от 1 до 5. В зависимости от этого случайного числа давайте обновим наше selectedState ».

Затем мы можем использовать это выбранное состояние для выбора нашей панорамной фотографии, поскольку они имеют одинаковое название. Нам просто нужно будет добавить «.jpg» следующим образом:

<Pano source={asset(this.state.selectedState + '.jpg')}/>

Сохраните свой код и попробуйте обновить локальный хост. Мы должны каждый раз получать случайную панораму:

Добавление нашего обработчика событий

Следующим шагом будет добавление нашего обработчика событий, который в конечном итоге обновит панорамную фотографию.

Нам нужна функция, которая вызывается щелчком по текстовым полям в нашем компоненте TextBoxes, который затем обновляет свойство selectedState в нашем PanoramicRoadTrip составная часть.

Напомним, ранее мы обернули компонент VrButton вокруг компонента Text, чтобы захватить событие onClick. В нашем случае нам нужно событие onClick, захваченное при щелчке компонента View (наши текстовые поля). Нам нужно будет снова использовать VrButton.

Во-первых, давайте добавим к нашему импорту VrButton:

import {
  AppRegistry,
  asset,
  Pano,
  Text,
  View,
  VrButton
} from 'react-vr';

Затем в нашем компоненте TextBoxes давайте обернем компоненты VrButton вокруг текстовых полей.

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

Вот пример одного из них:

<VrButton>
  <View style={{ margin: 0.1, height: 0.3, backgroundColor: '#CF3C7E'}}>
    <Text style={{fontSize: 0.2, textAlign: 'center'}}>{this.props.states.Arizona}
    </Text>
  </View>
</VrButton>

Далее у нас есть код для захвата события onClick и вызова функции.

Давайте подумаем над этой логикой.

Мы хотим изменить панорамную фотографию щелчком одного из текстовых полей.

Активная панорамная фотография управляется свойством локального состояния selectedState в компоненте PanoramicRoadTrip.

Однако VrButton, который мы хотим захватить событие onClick, находится в другом компоненте, называемом TextBoxes.

Итак, как мы можем обновить selectedState, если он контролируется состоянием, которое находится в другом компоненте, чем то, в котором мы фиксируем событие?

Во-первых, нам нужно написать функцию в PanoramicRoadTrip, которая будет обновлять selectedState при вызове:

export default class PanoramicRoadTrip extends React.Component {
  constructor() {
    super();
    this.state = {selectedState: ""};
  }
  stateClicked () {
    //add code here
  }
   
  //more stuff here
}

В этой функции мы хотим обновить selectedState в зависимости от того, какая кнопка нажата.

Следовательно, при вызове этой функции мы передадим параметр, который сообщит нам, какой VrButton был нажат, и мы сможем действовать соответствующим образом:

stateClicked (selection) {
  //add more code here
}

Передаваемый параметр selection будет иметь число от 1 до 5. Это число будет определять, в каком текстовом поле (с названием состояния) был сделан щелчок.

Следовательно, мы можем использовать оператор switch для обновления selectedState в соответствии со значением параметра selection:

stateClicked (selection) {
    let newState;
    switch(selection) {
      case 1:
        newState = "Arizona";
        break;
      case 2:
        newState = "New Hampshire";
        break;
      case 3:
        newState = "California";
        break;
      case 4:
        newState = "Hawaii";
        break;
      case 5:
        newState = "Texas";
        break;
    }
    console.log(newState); //log the result
    this.setState({ selectedState: newState});
}

Прохладный! Теперь, чтобы иметь возможность вызывать эту функцию щелчком наших кнопок VrButton в компоненте TextBoxes, мы передаем ее компоненту TextBoxes в качестве свойства prop:

//in PanoramicRoadTrip component
<TextBoxes 
  stateClicked={this.stateClicked.bind(this)} 
  states={states}
/>

Теперь наши VrButtons могут вызывать эту функцию, выполнив:

//in TextBoxes component
<VrButton onClick={this.props.stateClicked}>

Нам нужно немного изменить это. Мы хотим передать параметр, поскольку stateClicked будет обновлять selectedState в зависимости от того, что передано.

Чтобы передать параметр, мы можем сделать это для всех наших кнопок VrButton:

<VrButton onClick={() => this.props.stateClicked(1)}>
//...
<VrButton onClick={() => this.props.stateClicked(2)}>
//...
<VrButton onClick={() => this.props.stateClicked(3)}>
//...
<VrButton onClick={() => this.props.stateClicked(4)}>
//...
<VrButton onClick={() => this.props.stateClicked(5)}>

Резюмируем:

  • Мы написали функцию под названием stateClicked, которая обновляет свойство selectedState локального состояния в компоненте PanoramicRoadTrip на основе входящего параметра.
  • selectedState определяет, какая панорама отображается в нашем приложении.
  • Функция stateClicked должна находиться в компоненте PanoramicRoadTrip, чтобы обновить selectedState, поскольку это свойство в локальном состоянии PanoramicRoadTrip.
  • Мы хотим вызвать функцию stateClicked из нашего компонента TextBoxes, который содержит VrButtons, которые могут захватывать, когда щелкают текстовые поля (содержащие имена различных состояний).
  • Чтобы это стало возможным, мы передаем функцию stateClicked как prop.
  • Текстовые поля в компоненте TextBoxes вызывают функцию stateClicked и передают число, чтобы можно было соответствующим образом обновить selectedState.
  • Теперь щелчок по нашим текстовым полям должен изменить свойство selectedState локального состояния PanoramicRoadTrip, что в конечном итоге изменит отображаемую панорамную фотографию.

Пойдем к нашему локальному хосту, обновим и проверим.

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

Когда мы щелкаем текстовые поля, мы видим, что код работает, так как наш выбор регистрируется в консоли, а наши панорамные фотографии обновляются.

Иногда я получал следующую ошибку:

Error: WebGL warning: drawArrays: This operation requires zeroing texture data. This is slow.
Error: WebGL warning: drawArrays: Active texture 0 for target 0x0de1 is 'incomplete', and will be rendered as RGBA(0,0,0,1), as per the GLES 2.0.24 $3.8.2: The dimensions of `level_base` are not all positive.

По сути, иногда WebGL не может обработать обновленную панораму. Однако в большинстве случаев он работает нормально. На данный момент мы не будем вдаваться в подробности решения этой проблемы.

Окончательный код

Код этой главы доступен на GitHub.

Заключительные мысли

В этой главе, возможно, были некоторые части, которые показались тривиальными, поскольку они работают точно так же, как стандартный React. Однако в этом прелесть работы с React VR. Мы можем создавать приложения VR, не изобретая велосипед.

Основные различия в использовании компонентов в React VR (в отличие от стандартного React) заключаются в том, что мы должны реализовать макет с помощью Flexbox и использовать предопределенные компоненты React VR (например, VrButton) для обработки событий.

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

Глава 3

Глава 3 теперь доступна.

Поддержи автора

Если вы хотите поддержать меня, когда я пишу эту книгу, вы можете купить книгу здесь.

Книга публикуется на платформе LeanPub, которая позволяет мне обновлять книгу по мере ее продвижения. Каждый раз, когда добавляется глава, вы будете получать уведомление по электронной почте. Книгу можно будет бесплатно читать на Medium, однако покупка ее через LeanPub позволяет загружать электронную книгу в виде файла в формате PDF, EPUB или MOBI и помогает мне в финансовом отношении.

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

Так что, будьте любезны, купите эту книгу на LeanPub.

С уважением,
Майк Манджаларди
Основатель Coding Artist