Или "Как не создавать веб-приложение"

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

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

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

С помощью этой темы Wordpress + Vue, которую я разрабатываю, я трачу время, которое я часто не могу себе позволить, на проекты клиента / работы / в срок.

Изобретать колесо - не весело, как и рефакторинг 3 раза за 2 недели после ранней оптимизации. Тоже хорошая вещь.

Этот «Единый Источник Истины» хотя ..

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

  1. Под нагрузкой (без AJAX),
  2. Прямо из API (AJAX),
  3. API через локальное хранилище (кэшированное и управляемое).

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

На короткое время я начал загружать некоторые данные при загрузке в файл index.php на основе PHP. Это был действительно очень плохой «двусторонний» подход, который разрушил бы мое здравомыслие.

Я понял, что переменная js была основой моего «Единого источника истины», и мои вызовы API должны были сбрасывать в исходную переменную данных при загрузке как минимум, а-ля подход «Параллельный, централизованный».

Но я также переместил все вызовы API-сервисов вместе и, таким образом, перешел на модель «Сервисно-ориентированная».

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

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

Что в магазине?

Эдер Негрете Недавно ответил на этот вопрос очень кратко, но точно:

Данные текущего представления

Состояние текущего представления

Концепция «есть». (загружается?, готово?, актуально? и т. д.)

Общие данные

По большей части мы будем развивать «общие данные».

Кодовое погружение:

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

// Direct-from-API Pattern
let wordpressService = {
  getFromAPI: function( path, resolve, reject ){
    Vue.http.get( path ).then(response => {
        let responseData = {posts: response.data, totalPages: 1};   
        resolve( responseData );
    }).catch(error => reject(error));
  },
  getPageBySlug: function(page_slug) { 
    let path = "/wp-json/wp/v2/page/?slug="+page_slug;
    return new Promise((resolve, reject) => {
        this.getFromAPI( path, resolve, reject );
    })
  }
};
// Component: => Service => API
import WordpressService from '../services/wordpress';
export default {
  props: ['post_slug'],
  created: function(){
    this.getPage();
  },
  methods: {
    getPageBySlug: function() {
      const wpPromisedResult = WordpressService.getPageBySlug( this.post_slug );
      wpPromisedResult.then(result => {
        if( result.posts.length > 0){
          this.page = result.posts[0];
        }
      }).catch(err => {
        this.error = true;
      });
    }// getPage
  }
}

Когда есть только источник данных и представление, это легко понять: данные компонента становятся видимыми, когда данные появляются.

Когда речь идет о сервисном центре и магазине, теперь это трехкомпонентная система (спросите инженера-механика). Дела становятся шаткими.

Как это на самом деле происходит

Вот общий обзор отношений компонент-хранилище-служба:

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

Большая часть моего кода для Магазина основана на очень понятном коде Огундипе Самуэля из Приступая к работе с VueX.

// VueX Store Pattern:
Vue.use(Vuex);
const store = new Vuex.Store({
  state: {
    posts: [],
    posts_loading: false
  },
  mutations: { // Used by store.actions 
    STORE_POSTS: (state, { posts }) => {
      state.posts.push(posts);
    }
  },
  actions: {  // Used by Components via dispatch()
    FETCH_POSTS: function ({ commit }, get_object) {
      const found_post = this.getters.getPost( get_object.type, get_object.slug );
      if( typeof found_post !== 'undefined' ){ // In-Memory already
        commit('SET_POSTS_LOADING', false);
      }else { //no matches, hit the API
        commit('SET_POSTS_LOADING', true);
        WordpressService
        .getPost( get_object.type, get_object.slug )
        .then((response) => {
          if( response.posts.length == 0 ) // OPTIONAL: Handle 404s
          commit('STORE_POSTS', { posts: response.posts });
        }, err => { // seems to be rare in normal circumstances.
              console.log("FETCH_POST: err:", err, get_object );
              commit('SET_POSTS_LOADING', false);
        });
      }
    }
  },
  getters: { // Used by Component 
    getPostBySlug: (state) => (slug) => {
      let foundit = state.posts.find(post => ( post.slug === slug && post.type == 'post'));
      if(typeof foundit == 'undefined') store.dispatch('FETCH_POSTS', { slug: slug } );
      return state.posts.find(post => post.slug === slug);
    }
  }
});
export default store

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

// Component >> Store >> Service >> API
<template>
  <div class="page-wrapper">
    <div class="article" v-if="(!posts_loading && this_post)">
      <h1 class="article-title">{{ this_post.title.rendered }}</h1
    </div>
  </div>
</template>
<script>
import Vuex from 'vuex';
export default {
  props: ['post_type', 'post_slug'],
  created(){
    this.$store.dispatch('FETCH_POST', 
      { type: this.post_type, slug: this.post_slug} );
  },
  computed: {
    ...Vuex.mapState(['posts_loading']),
    this_post: function(){  //Get the data from Store
      let foundPostInStore =     this.$store.getters.getPostBySlug(this.post_slug);
      return foundPostInStore;
   }
}
</script>

Полное построение

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

  • store.posts [] = {id, post_type, post_meta и т. д.}
  • store.terms [] = {term_slug, taxonomy_name}
  • store.authors [] = {}

Таксономии (группа терминов) просто фильтруют / сокращают:

Vue.use(Vuex);
const store = new Vuex.Store({
getters: { // Used by Component
getTerms: (state) => ( requested_taxonomy_name, requested_term_slug ) => {
    let found_term = [];
    if( state.terms.length > 0 ){
      found_term = state.terms.reduce(
        function( accumulator, currentTerm ) {
          if( currentTerm.taxonomy == requested_taxonomy_name && currentTerm.slug == requested_term_slug ){
            accumulator.push( currentTerm );
          }
          return accumulator;
        }, [] );
    }
    return found_term;
  }
} //getters
}); //store

Я использовал один и тот же подход для всех архивов: если задан requested_author_slug, вернуть все соответствующие записи в Магазине. Я также уверен, что у lodash есть лучший метод для моего чистого reduce. Примечание. Для разбивки на страницы пользовательского интерфейса требовался другой подход.

Главный потенциал роста?

По мере того, как пользователь щелкает по сайту, Магазин получает все больше и больше сообщений. Каждый раз, когда пользователь нажимает на что-то, что уже есть в магазине (например, от выдержки из публикации), она мгновенно загружается. Мгновенно. Кэширование запросов API приближается, но теперь мы можем:

  1. Предварительная загрузка! Пагинация пользовательского интерфейса может составлять 4 сообщения на страницу, но разбивка на страницы API может быть 20.
  2. Используйте данные на протяжении всего сеанса.

Основные недостатки этого?

  1. Помните, что Store - это просто переменная в памяти. Не перегружайте его. Если вам нужно сохранить больше данных, используйте IndexDB или LowDB. На устройствах ваших пользователей запущены другие вещи.
  2. Параллелизм. Сейчас я пишу одну и ту же функцию 2–3 раза для каждого типа данных просто для передачи данных.

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

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

Хорошо выбирайте компромиссы. Если пользователя поразит время загрузки в 1 секунду за время загрузки в 0,1 секунды, тогда вы хорошо справляетесь с инфраструктурным (CDN + браузер) кешированием для запросов API.

Слон в комнате (может быть, а может и не быть быком в посудной лавке)

В какой-то момент я не понимаю, почему я воссоздаю WordPress API локально, кроме как для скорости, и, похоже, я не единственный:

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

В худшем случае это проявляется при поиске. Если компонент поиска напрямую обращался к API, поиск выполнял сам WordPress. Конечно, это займет лишнюю секунду, и, возможно, это нормально, но любая store.getters.search функция, которую я напишу, будет искать только сообщения в памяти.

Подождите, я создаю собственные алгоритмы поиска?

Все это имеет смысл. Вот один сумасшедший, который реализовал свой собственный WordPress API с помощью GraphQL. Я наконец понимаю, почему. Обычные маршруты REST могут быть похожи на поедание тарелки спагетти по одной лапше за раз или поедание хлопьев AlphaBits в алфавитном порядке.

Возможно, реальный ответ на проблему поиска - просто вернуть идентификаторы постов?

Резюме

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



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

Хорошее место для начала поиска состояния приложения - реквизиты компонентов, а точнее реквизиты нескольких компонентов, которые используют одни и те же данные.



Несколько распространенных ошибок:

  • Array.push не является реактивным способом обновления состояния.
  • Перейти в магазин / api во время created(), а не mounted() Возможно, вам также потребуется открыть магазин на beforeRouteUpdate(to, from, next).
  • Состояние каждого компонента по умолчанию перемещено из data(); теперь состояние по умолчанию - computed:{}.
  • Для объединения вычисляемых переменных, основанных на Promise, по-прежнему требуется Promise. Возможно, использование переменных data_loading просто должно быть обещанием!

Следующие шаги

Я начинаю использовать VueX в качестве хранилища кешей, а это не то, о чем вы слышите, как большинство людей говорят об этом. Затем я попытаюсь фактически извлечь состояние приложения, возможно, по store.current и, возможно, store.history переменной.

Спасибо за прочтение! Вы можете сравнить разницу для конечного пользователя с предыдущей версией:



..или проверьте код в его собственной ветке: