Или "Как не создавать веб-приложение"
Подруга недавно получила очки, и она была удивлена. Она не знала, что они ей нужны, но потом почувствовала, что мир был в HD.
Я прожил большую часть своей жизни программирования, как она. Я не знал того, чего не знал.
С помощью этой темы Wordpress + Vue, которую я разрабатываю, я трачу время, которое я часто не могу себе позволить, на проекты клиента / работы / в срок.
Изобретать колесо - не весело, как и рефакторинг 3 раза за 2 недели после ранней оптимизации. Тоже хорошая вещь.
Этот «Единый Источник Истины» хотя ..
Оказывается, идея единого источника данных - подвижная цель. Есть 3 распространенных способа обработки внутренних данных веб-приложения:
- Под нагрузкой (без AJAX),
- Прямо из API (AJAX),
- 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.actionsSTORE_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 приближается, но теперь мы можем:
- Предварительная загрузка! Пагинация пользовательского интерфейса может составлять 4 сообщения на страницу, но разбивка на страницы API может быть 20.
- Используйте данные на протяжении всего сеанса.
Основные недостатки этого?
- Помните, что Store - это просто переменная в памяти. Не перегружайте его. Если вам нужно сохранить больше данных, используйте IndexDB или LowDB. На устройствах ваших пользователей запущены другие вещи.
- Параллелизм. Сейчас я пишу одну и ту же функцию 2–3 раза для каждого типа данных просто для передачи данных.
WordPress API в основном предоставляет доступ к каждой основной таблице базы данных. Ничего безумного. Основные маршруты следуют той же схеме. Требуется добавление службы для взаимодействия с API, а добавление локального магазина - это еще один способ получить доступ к тому же самому.
Сколько раз вы хотите провести рефакторинг, зависит от того, сколько раз вы хотите повторно реализовать методы доступа к данным.
Хорошо выбирайте компромиссы. Если пользователя поразит время загрузки в 1 секунду за время загрузки в 0,1 секунды, тогда вы хорошо справляетесь с инфраструктурным (CDN + браузер) кешированием для запросов API.
Слон в комнате (может быть, а может и не быть быком в посудной лавке)
В какой-то момент я не понимаю, почему я воссоздаю WordPress API локально, кроме как для скорости, и, похоже, я не единственный:
Когда у вас есть несколько магазинов или веток в вашем дереве состояний, вы фактически дублируете свои серверные бизнес-данные и отношения на клиенте. - Марк Джонсон
В худшем случае это проявляется при поиске. Если компонент поиска напрямую обращался к API, поиск выполнял сам WordPress. Конечно, это займет лишнюю секунду, и, возможно, это нормально, но любая store.getters.search
функция, которую я напишу, будет искать только сообщения в памяти.
Подождите, я создаю собственные алгоритмы поиска?
Все это имеет смысл. Вот один сумасшедший, который реализовал свой собственный WordPress API с помощью GraphQL. Я наконец понимаю, почему. Обычные маршруты REST могут быть похожи на поедание тарелки спагетти по одной лапше за раз или поедание хлопьев AlphaBits в алфавитном порядке.
Возможно, реальный ответ на проблему поиска - просто вернуть идентификаторы постов?
Резюме
Необходимо принять решение / обсудить, использовать ли локальное хранилище в качестве механизма кэширования или в качестве системы обмена сообщениями между компонентами em. >:
Это приятное практическое руководство показывает обмен сообщениями между компонентами, альтернативу тому, что я делаю:
Хорошее место для начала поиска состояния приложения - реквизиты компонентов, а точнее реквизиты нескольких компонентов, которые используют одни и те же данные.
Несколько распространенных ошибок:
- Array.push не является реактивным способом обновления состояния.
- Перейти в магазин / api во время
created()
, а неmounted()
Возможно, вам также потребуется открыть магазин наbeforeRouteUpdate(to, from, next)
. - Состояние каждого компонента по умолчанию перемещено из
data()
; теперь состояние по умолчанию -computed:{}
. - Для объединения вычисляемых переменных, основанных на Promise, по-прежнему требуется Promise. Возможно, использование переменных data_loading просто должно быть обещанием!
Следующие шаги
Я начинаю использовать VueX в качестве хранилища кешей, а это не то, о чем вы слышите, как большинство людей говорят об этом. Затем я попытаюсь фактически извлечь состояние приложения, возможно, по store.current
и, возможно, store.history
переменной.
Спасибо за прочтение! Вы можете сравнить разницу для конечного пользователя с предыдущей версией:
..или проверьте код в его собственной ветке: