Позвольте мне предварить мое приключение React-WebSocket, признав, по большому счету, что я довольно новый разработчик. Пробовав несколько языков здесь и там на протяжении многих лет, как и многие из нас, я, наконец, серьезно занялся этим и начал курс JavaScript. Поэтому, прежде чем вы начнете ругать мой плохой код или то, что я не использую метод «xyz» для достижения того, чего я хочу, просто знайте, что я в курсе. Тем не менее, это была история многочисленных часов разочарования, облегчения и, в конечном счете, компромисса, который может коснуться разработчика любого уровня квалификации.

Компоненты

Работая над своим небольшим проектом, я решил создать игру «Выбери себе приключение» в сочетании с WebSockets — в частности, Socket.IO. Предпосылка заключалась в том, что вы будете мчаться через приключение, пытаясь добраться до конца, в то время как кто-то другой делает то же самое. Вы могли видеть их прогресс из-за WebSockets и паниковать соответственно.

Это была простая идея: каждый уровень имел опции, а каждая опция — соответствующее действие. Вернуться к началу, перейти на следующий уровень, остаться на текущем уровне и т. д. Мой наивный ум на самом деле думал, что это будет простой процесс, связывающий React и WebSockets вместе. «React имеет состояние и обновляет/перерисовывает его, когда это необходимо, а Socket.IO может транслировать его всем, когда это необходимо. Что возможно могло пойти не так?"

Реакция

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

<svg>
  <line x1={20} y1={20} x2={265} y2={20} stroke="black"
  strokeWidth="10" strokeLinecap="round" />
  <circle cx={xPos} cy={20} r={10} fill={ color } />
  <text x={15} y={50} stroke="black">1------2------3------4------5--
   ----6------7</text>
</svg>

Приведенный выше код представляет собой просто svg с линией, кругами и маркерами уровня. Грубо и не работало на всех платформах, но для минимально жизнеспособного продукта этого было достаточно. Моя идея заключалась в том, что у вас есть компонент «круг» с координатой x, основанный на формуле, основанной на вашем текущем уровне, а затем противник также видит, на каком уровне он находится в режиме реального времени.

Socket.IO

Добавление WebSockets с помощью npm-пакета Socket.IO тоже не так уж и плохо, но были некоторые сбои. Мой единственный предыдущий опыт работы с Socket.IO заключался в том, что в классе я создавал чертежную доску, на которой могли рисовать несколько человек, но в основном это было предварительно создано, и мы просто добавили несколько обработчиков событий. Создавать все с нуля немного сложнее, но в этом и есть удовольствие, верно? (Было также целое «Заставьте сокеты работать на Heroku, а не только на локальном сервере», но это для другого поста в блоге)

module.exports = io => {
  io.on('connection', (socket) => {
    console.log(' A client has connected! Client ID: ',
    socket.id);
    socket.on('send-level', (level) =>{
      socket.broadcast.emit('update-level', level)
    })
  })
}

Как уже говорилось, серверный компонент моего WebSocket был довольно простым. Прислушайтесь к Send-Level, и как только вы его услышите, выдайте Update-Level с учетом изначально полученных данных об уровне. Оглядываясь назад, могу сказать, что еще один урок, который я усвоил на собственном горьком опыте, заключался в том, что, возможно, имена моих методов/переменных придают больше… разнообразия. (Последний и, возможно, самый важный урок, извлеченный из всего этого, заключался в том, что карандаш и бумага — ваши друзья. Отслеживание процесса всех моих излучений/слушателей было ОЧЕНЬ полезным. Карандаш + бумага. Настоятельно рекомендую)

Довольно просто + Довольно просто = Не работает

Имея две основные части моего набора веб-приложений, все, что мне нужно было сделать, это связать их вместе… но как? Как мне заставить мой компонент React не только слушать обновления других людей, но и рассылать любые свои собственные обновления всем остальным? Как я уже сказал, это история разочарования, облегчения и компромисса. Тут-то и началось разочарование, которое продолжалось много-много часов. Вы знаете, как, когда вы загружаете Chrome, он показывает ваши 8 самых посещаемых страниц? Я почти уверен, что к концу этого как минимум 5 из них были либо документацией React, документацией Socket.IO, либо различными страницами переполнения стека.

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

export default class Template extends Component {
  constructor(props){
    super(props);
    this.state = {
      currentLevel: 1,
      color: randomColor(),
      socket: socketIOClient(),
      opponentLevel: 0
    }
    this.changeLevel = this.changeLevel.bind(this);
    this.listenLevel = this.listenLevel.bind(this);
  }

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

Остальная часть компонента выглядела следующим образом:

componentDidMount(){
  const socket = this.state.socket
  socket.on('connect', () => {
    console.log('Client connected')})
    this.listenLevel();
  }
}
changeLevel(level){
  this.setState({currentLevel: level})
  this.state.socket.emit('send-level', this.state.currentLevel)
}
listenLevel(){
  this.state.socket.on('update-level', (level) => {
    this.setState({
        opponentLevel: level
      })
   })
}
  • Когда компонент смонтируется, подключитесь к серверу WebSocket и вызовите listenLevel
  • listenLevel прослушивает «уровень обновления» с сервера и устанавливает уровень оппонента на этот уровень.
  • changeLevel устанавливает currentLevel и выдает «уровень отправки», чтобы сервер услышал, а затем транслировал

Все это приводит к худшему кошмару разработчика. Все работало… но круг противника не появлялся. Я вхожу в консоль на каждом этапе пути. Все данные, которые я хотел, были должным образом услышаны и кричали всем остальным (Этап приключения «Помощь»). Так что же случилось с Кругом, который не показывался? Я потратил больше часов, чем хотел бы признать, пытаясь понять это (я солгал — еще один урок, полученный на горьком опыте. Узнайте, когда сократить свои потери и вернуться к чему-то). В конце концов я пришел к выводу, что компонент svg моего JSX был главным виновником. React не перерисовывал его независимо от того, что ему говорил мой WebSocket. Я пробовал анонимные функции, создавая новые компоненты и передавая им функции в качестве реквизита, всевозможные методы React Lifecycle. Ничего такого.

Наконец я пошел на компромисс. «Ну, может быть, увидеть их круг было не самым важным, что, если я просто покажу их уровень?» Конечно, это работало отлично (и под «идеально» я подразумеваю, что это было немного глючно, но если бы работало!). Внезапно меня наполнили воспоминания о моем учителе статистики AP в старшей школе: «По мере того, как растут продажи мороженого, увеличивается количество нападений акул. Означает ли это, что мороженое вызывает нападения акул? Нет! Здесь есть скрытая переменная, тот факт, что сейчас лето, и все больше людей плавают в океанах и едят мороженое».

‹svg› была моей скрытой переменной. Все это время я обвинял отношения WebSockets/React, но я должен был знать из своих тысяч журналов консоли, что в этом нет ничего плохого. Тем не менее, вынос здесь был огромен. Мне удалось объединить два совершенно не связанных между собой инструмента Javascript в функциональное приложение и даже выяснить, почему оно не работает. И хоть я только начинаю свою карьеру разработчика, даже я могу вам сказать, что знать причину бага — это больше, чем полдела. Что еще более важно, теперь я знаю, что всякий раз, когда кодируете, взгляните на ВСЕ возможные переменные, чтобы выяснить, что вызывает вашу проблему.