В этой части мы рассмотрим перенос вещей в отдельные компоненты.

Вот все части этой серии:
Github:
https://github.com/jespr/vue-phoenix-chat
Heroku: https://stormy-inlet-39179.herokuapp.com/

Часть 1 - Введение и получение базового веб-приложения с функциями чата.

Часть 2 - Дайте возможность пользователю идентифицировать себя по имени перед тем, как присоединиться к чату.

Часть 3 - узнайте, кто сейчас в чате с вами

Часть 4 - Красивый дизайн + забавные переходы

Часть 5 - Настойчивость.

Часть 6 (эта статья) - Помещение объектов в отдельные компоненты

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

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

Для создания компонентов было бы неплохо иметь одно место для обработки состояния. Прямо сейчас, поскольку все находится в одном компоненте, мы просто назначаем пользователей, сообщения и т. Д. Как часть data() в этом компоненте.

Для этого мы представим новый пакет под названием Vuex. Vuex - это (как это красиво сказано) централизованное управление состоянием для vue.js.

Итак, давайте установим это, запустив:

npm install vuex --save

Это также добавит его к нашим зависимостям в package.json.

Теперь нам нужно создать магазин. Итак, давайте создадим новый файл web/static/js/store.js

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

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// root state object.
// each Vuex instance is just a single state tree.
const state = {
  users: []
}
// mutations are operations that actually mutates the state.
// each mutation handler gets the entire state tree as the
// first argument, followed by additional payload arguments.
// mutations must be synchronous and can be recorded by plugins
// for debugging purposes.
const mutations = {
}
// actions are functions that causes side effects and can involve
// asynchronous operations.
const actions = {
}
// getters are functions
const getters = {
}
// A Vuex instance is created by combining the state, mutations, actions,
// and getters.
export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})

Теперь мы можем сообщить приложению Vue, что мы используем магазин, добавив его в web/static/js/app.js, поэтому давайте откроем этот файл и добавим в него магазин.

Вверху добавьте следующее:

import store from "./store.js"

Теперь это выглядит так:

import "phoenix_html"
import Vue from 'vue'
import MyApp from "../components/my-app.vue"
import store from "./store.js"

Затем при инициализации нашего приложения Vue мы добавляем store непосредственно перед нашим вызовом render:

// And create the top-level view model:
new Vue({
  el: '#app',
  store,
  render(createElement) {
    return createElement(MyApp, {})
  }
});

Идеально! Теперь давайте продолжим и извлечем наш список пользователей в его собственный компонент. Откройте web/static/components/my-app.vue и найдите div с идентификатором users-list. Скопируйте все div:

<div id="users-list">
  <h3>Online</h3>
  <ul>
    <transition-group name="user-appear">
      <li v-for="user in users" v-bind:key="user.user">
        {{user.user}} ({{user.online_at}})
      </li>
    </transition-group>
  </ul>
</div>

Давайте продолжим и создадим этот новый файл компонента. web/static/components/users-list.vue Давайте начнем с добавления HTML, который мы только что скопировали, в шаблон этого компонента. Итак, в верхней части нового файла добавьте скопированный HTML-код внутри тегов <template>..</template>, чтобы он выглядел так:

<template>
  <div id="users-list">
    <h3>Online</h3>
    <ul>
      <transition-group name="user-appear">
        <li v-for="user in users" v-bind:key="user.user">
          {{user.user}} ({{user.online_at}})
        </li>
      </transition-group>
    </ul>
  </div>
</template>

Давайте добавим наш script раздел ниже:

<script>
export default {
  computed: {
    users() {
      return this.$store.state.users;
    }
  }
}
</script>

Как вы могли заметить, мы добавили новую вычисляемую функцию с именем users(), которая ссылается на users, исходящую из глобального состояния. Это прямо сейчас ссылается на пустой массив, который мы добавили в наш store.js.

Теперь давайте использовать этот новый компонент внутри web/static/components/my-app.vue вместо того, чтобы иметь логику непосредственно внутри этого одного компонента. Вверху раздела script мы импортируем новый компонент, добавив:

import UsersList from "./users-list"

Затем нам нужно будет зарегистрировать этот компонент в web/static/components/my-app.vue, чтобы мы могли использовать его в шаблоне. Итак, добавьте новый объект components под функцией data():

components: {
  'users-list': UsersList
},

Теперь мы также можем продолжить и удалить users: [] из функции data() в my-app.vue. У вас должно получиться что-то вроде:

data(): {
  return {
    ...
  }
},
components: {
  'users-list': UsersList
},
methods: {
  ...
}

Я заменил фактическое содержание data() и methods на ... для простоты.

Теперь вы можете перейти к части шаблона my-app.vue и заменить <div id="users-list">..</div> на <users-list/>. Хороший!

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

<div id="main-container" v-else>
  <users-list/>
    <div id="messages-list">
      <ul>
        ...

Если вы обновите приложение и введете имя, вы увидите, что пользователи не отображаются. Очевидно, это потому, что мы ссылаемся на пустой массив users из глобального хранилища состояний.

Поэтому, когда мы получаем пользователей в my-app.vue, где мы обычно назначаем их локальным данным пользователей, мы будем знать, что вместо этого обновим глобальное хранилище.

Внутри web/static/components/my-app.vue найдите функцию assignUsers и измените ее на это:

assignUsers(presences) {
  let users = Presence.list(presences, (user, {metas: metas}) => {
    return { name: user, online_at: metas[0].online_at }
  })
  this.$store.commit('addUsers', { users })
}

Как вы можете видеть, теперь мы получаем всех пользователей из списка присутствия и возвращаем объекты, содержащие name и online_at. Затем мы передаем это в store, вызывая commit - кстати, вы всегда можете ссылаться на глобальное хранилище через this.$store.

Давайте продолжим, откроем web/static/js/store.js и добавим туда мутацию addUsers.

Итак, внутри const mutations = { ... } вы добавите следующее, чтобы оно выглядело так:

const mutations = {
  addUsers (state, { users }) {
    state.users = users
  }
}

Здесь мы просто назначаем массив пользователей атрибуту state.users.

Затем нам нужно внести изменения в наш users-list компонент, поэтому откройте web/static/components/users-list.vue

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

computed: {
  users() {
    let formatTimestamp = (timestamp) => {
      timestamp = parseInt(timestamp)
      let date = new Date(timestamp)
      return date.toLocaleTimeString()
    }
    return this.$store.state.users.map(function(user) {
      return { 
        name: user.name, 
        online_at: formatTimestamp(user.online_at) }
      })
    }
  }
}

Как видите, я изменил имя пользователя с user на name, поэтому давайте внесем это изменение и в наш шаблон:

<li v-for="user in users" v-bind:key="user.name">
  {{user.name}} ({{user.online_at}})
</li>

Здесь я изменил {{user.user}} на {{user.name}}, а также привязку клавиш с v-bind:key="user.user" на v-bind:key="user.name".

Вуаля! Все должно работать и выглядеть одинаково!

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