Vue.js разрабатывает веб-приложение Qunar

Vue.js
Vue.js разрабатывает веб-приложение Qunar

1. Введение проекта

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

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

vue仿去哪儿网webapp

1.1 Стек технологий

Vue 2.5: прогрессивный фреймворк для создания пользовательских интерфейсов.

Vuex: шаблон управления состоянием, разработанный для приложений Vue.js.

Vue Router: официальный менеджер маршрутизации для Vue.js.

keep-alive: абстрактный компонент, предоставляемый Vue для кэширования компонентов для сохранения производительности.

vue-awesome-swiper: компонент карусели на основе Swiper для Vue, поддерживающий рендеринг на стороне сервера и одностраничные приложения.

стилус: препроцессор css

Axios: HTTP-библиотека на основе обещаний

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

webpack: сборщик статических модулей для современных приложений JavaScript.

EsLint: инструмент, помогающий проверять синтаксические ошибки в программировании на Javascript и стандартизировать стиль кода.

iconfont: Библиотека иконок Alibaba

fastclick: решить проблему задержки мобильного клика в 300 мс

1.2 Отображение проекта

  1. Билеты на достопримечательности Главная

2去哪儿网首页展示

  1. Страница со списком городов

4城市搜索-字母表

  1. Страница сведений о аттракционе

5详情页面-画廊

1.3 Урожай проекта

1. Понять процесс разработки всего проекта vue и начать разработку проектов vue среднего размера.

  • Vue Router для многостраничной маршрутизации
  • Совместное использование данных нескольких компонентов в Vuex
  • Плагин swiper для достижения эффекта поворота страницы
  • Axios для получения данных Ajax

2. Навыки верстки мобильных страниц

3. Стилус пишет стиль переднего конца

4. Разделение общих компонентов

5. Написание стандартизированного кода

1.4 Каталог проектов

Прикрепите каталог проекта и адрес складаVue имитирует веб-приложение Qunar

F:.
│  .babelrc
│  .editorconfig
│  .eslintignore
│  .eslintrc.js
│  .gitignore
│  .postcssrc.js
│  index.html
│  package-lock.json
│  package.json
│  README.en.md
│  README.md
│
├─build
│      build.js
│      check-versions.js
│      logo.png
│      utils.js
│      vue-loader.conf.js
│      webpack.base.conf.js
│      webpack.dev.conf.js
│      webpack.prod.conf.js
│
├─config
│      dev.env.js
│      index.js
│      prod.env.js
│
├─src
│  │  App.vue
│  │  main.js
│  │
│  ├─assets
│  │  └─styles
│  │      │  border.css
│  │      │  iconfont.css
│  │      │  mixins.styl
│  │      │  reset.css
│  │      │  varibles.styl
│  │      │
│  │      └─iconfont
│  │              iconfont.eot
│  │              iconfont.svg
│  │              iconfont.ttf
│  │              iconfont.woff
│  │
│  ├─common
│  │  ├─fade
│  │  │      FadeAnimation.vue
│  │  │
│  │  └─gallary
│  │          Gallary.vue
│  │
│  ├─pages
│  │  │  testGit.js
│  │  │
│  │  ├─city
│  │  │  │  City.vue
│  │  │  │
│  │  │  └─components
│  │  │          Alphabet.vue
│  │  │          Header.vue
│  │  │          List.vue
│  │  │          Search.vue
│  │  │
│  │  ├─detail
│  │  │  │  Detail.vue
│  │  │  │
│  │  │  └─components
│  │  │          Banner.vue
│  │  │          Header.vue
│  │  │          List.vue
│  │  │
│  │  └─home
│  │      │  Home.vue
│  │      │
│  │      └─components
│  │              Header.vue
│  │              Icons.vue
│  │              Recommend.vue
│  │              Swiper.vue
│  │              Weekend.vue
│  │
│  ├─router
│  │      index.js
│  │
│  └─store
│          index.js
│          mutations.js
│          state.js
│
└─static
        .gitkeep


1.5 Инициализация кода проекта

Поскольку это веб-приложение, необходимо выполнить соответствующие приготовления для мобильного терминала.

1. Настройки, связанные с метатегами

index.html

    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

Эффект: соотношение страниц всегда 1:1, а масштабирование пальцем пользователя недопустимо.

2. Импортируйте файл reset.css

Цель: сбросить стиль страницы

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

3. Импортируйте файл border.css

Цель: Решить проблему границы в 1 пиксель на мобильном терминале

4. Устанавливаем фастклик в проект

npm install fastclick --save

Цель: решить проблему задержки 300 мс на мобильном терминале.

1.6 Компонентизация страницы

маршрутизация маршрутизатор-index.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'
import Detail from '@/pages/detail/Detail'

Vue.use(Router)

export default new Router({
  routes: [{
    path: '/',
    name: 'Home',
    component: Home
  }, {
    path: '/city',
    name: 'City',
    component: City
  }, {
    path: '/detail/:id',
    name: 'Detail',
    component: Detail
  }],
  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0 }
  }
})

Каталог, связанный со страницей

pages
├─city
│  │  City.vue
│  │
│  └─components
│          Alphabet.vue
│          Header.vue
│          List.vue
│          Search.vue
│
├─detail
│  │  Detail.vue
│  │
│  └─components
│          Banner.vue
│          Header.vue
│          List.vue
│
└─home
    │  Home.vue
    │
    └─components
            Header.vue
            Icons.vue
            Recommend.vue
            Swiper.vue
            Weekend.vue
common
├─fade
│      FadeAnimation.vue
│
└─gallary
        Gallary.vue

Например, для страницы билета аттракциона вы можете разделить ее на несколько небольших компонентов, поместить их в каталог компонентов и интегрировать страницу, ссылаясь на компоненты в компоненте-контейнере Home.vue.

Home.vue часть кода

<template>
  <div>
    <home-header></home-header>
    <home-swiper :list="swiperList"></home-swiper>
    <home-icons :list="iconList"></home-icons>
    <home-recommend :list="recommendList"></home-recommend>
    <home-weekend :list="weekendList"></home-weekend>
  </div>
</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
import { mapState } from 'vuex'
export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      lastCity: '',
      swiperList: [],
      iconList: [],
      recommendList: [],
      weekendList: []
    }
  }
}
</script>


Во-вторых, использование плагинов проекта

2.1 Ajax для получения данных домашней страницы

Vue рекомендует использовать axios для реализации кроссплатформенных запросов данных.

установить аксиомы

npm install axios --save

Отправка запросов Ajax в Home.vue — лучший выбор.После того, как этот компонент получит данные Ajax, он может передать данные каждому дочернему компоненту.

Поместите некоторые статические файлы в статический каталог черезhttp://localhost:8080/static/mock/index.jsonможет получить доступ к

static
│  .gitkeep
│
└─mock
        city.json
        detail.json
        index.json

Home.vue часть кода

<template>
  <div>
    <home-header></home-header>
    <home-swiper :list="swiperList"></home-swiper>
    <home-icons :list="iconList"></home-icons>
    <home-recommend :list="recommendList"></home-recommend>
    <home-weekend :list="weekendList"></home-weekend>
  </div>
</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
import { mapState } from 'vuex'
export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      lastCity: '',
      swiperList: [],
      iconList: [],
      recommendList: [],
      weekendList: []
    }
  },
  computed: {
    ...mapState(['city'])
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json?city=' + this.city)
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.swiperList = data.swiperList
        this.iconList = data.iconList
        this.recommendList = data.recommendList
        this.weekendList = data.weekendList
      }
    }
  },
  mounted () {
    this.lastCity = this.city
    this.getHomeInfo()
  }
}
</script>

<style>

</style>

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

Родительский компонент передает данные дочернему компоненту через реквизиты, а дочерний компонент передает данные родительскому компоненту через событие emit.

Возьмите компонент List в качестве примера (List.vue часть кода)

<template>
  <div>
    <div class="title">热销推荐</div>
    <ul>
      <router-link
        tag="li"
        class="item border-bottom"
        v-for="item of list"
        :key="item.id"
        :to="'/detail/' + item.id"
      >
        <img class="item-img" :src="item.imgUrl" />
        <div class="item-info">
          <p class="item-title">{{item.title}}</p>
          <p class="item-desc">{{item.desc}}</p>
          <button class="item-button">查看详情</button>
        </div>
      </router-link>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HomeRecommend',
  props: {
    list: Array
  }
}
</script>

2.2 Карусель

Установите плагин vue-awesome-swiper.

npm install vue-awesome-swiper@2.6.7 --save

Карусель используется в нескольких компонентах

6-swiper

Возьмем в качестве примера home-components-Swiper.vue.

<template>
  <div class="wrapper">
    <swiper :options="swiperOption" v-if="showSwiper">
      <swiper-slide v-for="item of list" :key="item.id">
        <img class="swiper-img" :src="item.imgUrl" />
      </swiper-slide>
      <div class="swiper-pagination"  slot="pagination"></div>
    </swiper>
  </div>
</template>

<script>
export default {
  name: 'HomeSwiper',
  props: {
    list: Array
  },
  data () {
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true
      }
    }
  },
  computed: {
    showSwiper () {
      return this.list.length
    }
  }
}
</script>

2.3 Better-scroll

Установить

npm install better-scroll --save

использовать

<div class="wrapper">
  <ul class="content">
    <li>...</li>
    <li>...</li>
    ...
  </ul>
  <!-- you can put some other DOMs here, it won't affect the scrolling
</div>
import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)

2.4 Используйте vuex для обмена данными

установить вуекс

npm install vuex --save

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

Конкретное описание:

Проект заключается в реализации передачи данных между страницей списка выбора города и домашней страницей, и нет общедоступного компонента, city/components/List.vue. , home/components/Header.vue и компоненты Home.vue должны получать данные.

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

store
    index.js
    mutations.js
    state.js

state.js

let defaultCity = '上海'
try {
  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {}

export default {
  city: defaultCity
}

mutations.js

export default {
  changeCity (state, city) {
    state.city = city
    try {
      localStorage.city = city
    } catch (e) {}
  }
}

index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations
})

Компонент Home.vue в вычисляемых свойствах,this.$store.state.xxx, в этом проекте естьthis.$store.state.cityМожно получить данные о состоянии. Конечно, чтобы сделать код более кратким, используйте mapState дляthis.xxxкарты наthis.$store.state.xxx.

В List.vue методы в мутациях запускаются фиксацией для изменения данных. Опять же, чтобы сделать код более кратким, введение mapMutations будетthis.changeCity(city)карты наthis.$store.commit('changeCity', city).

[город/List.vue специально]

import { mapState, mapMutations } from 'vuex'

computed: {
    ...mapState({
      currentCity: 'city'
    })
  },
  methods: {
    handleCityClick (city) {
      // this.$store.commit('changeCity', city)
      this.changeCity(city)
      this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
  }

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

3. Трудности проекта

3.1 Связь между родственными компонентами

Реализованная функция: Нажмите на букву в правой части страницы списка городов, опция списка прокрутится до соответствующей области буквы. 【отображение GIF】

Значение одноуровневых компонентов может быть передано в виде шины. Но поскольку наш неродительско-дочерний компонент теперь относительно прост, мы можем позволить компоненту Alphabet.vue передать значение родительскому компоненту, компоненту City.vue, а затем компонент City.vue перенаправит значение компоненту List.vue. , реализуя, таким образом, одноуровневые компоненты. [Дочерний компонент передается родительскому компоненту, а родительский компонент передается другому дочернему компоненту]. Таким образом, щелкнув нужную букву в Alphabet.vue, вы получите соответствующую букву.

4城市搜索-字母表

Alphabet.vue

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

<template>
  <ul class="list">
    <li
      class="item"
      v-for="item of letters"
      :key="item"
      :ref="item"
      @click="handleLetterClick"
    >
      {{item}}
    </li>
  </ul>
</template>

<script>
methods: {
    handleLetterClick(e) {
        this.$emit("change", e.target.innerHTML);
    }
}
</script>

Далее пересылаем полученные родительским компонентом данные дочернему компоненту List.vue, а родительский компонент через свойства передает значения дочернему компоненту.

Сначала определите букву в данных в родительском компоненте City.vue, значение по умолчанию пусто, вhandleLetterClickВ методе при получении письма извне пустьthis.letter = letter.

City.vue

<template>
  <div>
    <city-header></city-header>
    <city-search :cities="cities"></city-search>
    <city-list
      :cities="cities"
      :hot="hotCities"
      :letter="letter"
    ></city-list>
    <city-alphabet
      :cities="cities"
      @change="handleLetterChange"
    ></city-alphabet>
  </div>
</template>

<script>
data() {
    return {
        hotCities: [],
        cities: {},
        letter: ""
    };
},
methods: {
    handleAlpChange(letter) {
        this.letter = letter;
    }
}
</script>

Наконец, просто передайте письмо в подкомпонент List.vue и передайте его в шаблоне city-list компонента City.vue:letter="letter"Передайте значение в список дочерних компонентов, получите букву в реквизитах и ​​убедитесь, что тип — String.

List.vue

props: {
    hot: Array,
    cities: Object,
    letter: String
  }

Это реализует передачу значений родственных компонентов.

【Трудности проекта】

Следующее, что нужно сделать, когда List.vue обнаружит, что буква изменилась, ему нужно сделать элемент списка отображаемым компонентом и элемент списка с той же начальной буквой, что и буква для отображения.Как это сделать?

В это время нам нужно использовать прослушиватель для отслеживания изменений письма;

Better-scroll предоставляет такой интерфейс,scroll.scorllToElement, если письмо не пустое, звонитеthis.scroll.scrollToElement()Этот метод позволяет зону прокрутки автоматически бросить определенный элемент, как вы проходите этот элемент? В этом блоке добавьте ссылку на элемент петли, чтобы получить текущий элемент DOM, равный ключу, затем вернитесь к букве прослушивателя, определите элемент, он равен элементу, полученному Ref:

scroll.scorllToElement

List.vue

watch: {
    letter() {
        if (this.letter) {
            const element = this.$refs[this.letter][0];
            this.scroll.scrollToElement(element);
        }
    }
},

В это время вы можете получить соответствующую область через буквы, а затем передать элемент в scrollToElement.Обратите внимание, что [0] добавляется в конце кода выше, потому что, если он не добавлен, содержимое через ref или является массив, этот массив Первый элемент является реальным элементом DOM.В это время щелкните алфавит справа, чтобы перейти к списку городов под соответствующей буквой.

Нажмите, чтобы перейти функция

Затем реализуйте эффект скольжения алфавита справа и переключения списка городов слева.

<template>
  <ul class="list">
    <li
      class="item"
      v-for="item of letters"
      :key="item"
      :ref="item"
      @touchstart.prevent="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="handleTouchEnd"
      @click="handleLetterClick"
    >
      {{item}}
    </li>
  </ul>
</template>

<script>
export default {
  name: 'CityAlphabet',
  props: {
    cities: Object
  },
  computed: {
    letters () {
      const letters = []
      for (let i in this.cities) {
        letters.push(i)
      }
      return letters
    }
  },
  data () {
    return {
      touchStatus: false,
      startY: 0,
      timer: null
    }
  },
  updated () {
    this.startY = this.$refs['A'][0].offsetTop
  },
  methods: {
    handleLetterClick (e) {
      this.$emit('change', e.target.innerText)
    },
    handleTouchStart () {
      this.touchStatus = true
    },
    handleTouchMove (e) {
      if (this.touchStatus) {
        if (this.timer) {
          clearTimeout(this.timer)
        }
        this.timer = setTimeout(() => {
          const touchY = e.touches[0].clientY - 79
          const index = Math.floor((touchY - this.startY) / 20)
          if (index >= 0 && index < this.letters.length) {
            this.$emit('change', this.letters[index])
          }
        }, 16)
      }
    },
    handleTouchEnd () {
      this.touchStatus = false
    }
  }
}
</script>

3.2 поисковый компонент

Функция: при входе на страницу выбора города, когда фокус находится в поле поиска, введите название города или пиньинь для отображения результатов поиска.

3城市列表-搜索

<template>
  <div>
    <div class="search">
      <input v-model="keyword" class="search-input" type="text" placeholder="输入城市名或拼音" />
    </div>
    <div
      class="search-content"
      ref="search"
      v-show="keyword"
    >
      <ul>
        <li
          class="search-item border-bottom"
          v-for="item of list"
          :key="item.id"
          @click="handleCityClick(item.name)"
        >
          {{item.name}}
        </li>
        <li class="search-item border-bottom" v-show="hasNoData">
          没有找到匹配数据
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import Bscroll from 'better-scroll'
import { mapMutations } from 'vuex'
export default {
  name: 'CitySearch',
  props: {
    cities: Object
  },
  data () {
    return {
      keyword: '',
      list: [],
      timer: null
    }
  },
  computed: {
    hasNoData () {
      return !this.list.length
    }
  },
  watch: {
    keyword () {
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (!this.keyword) {
        this.list = []
        return
      }
      this.timer = setTimeout(() => {
        const result = []
        for (let i in this.cities) {
          this.cities[i].forEach((value) => {
            if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
              result.push(value)
            }
          })
        }
        this.list = result
      }, 100)
    }
  },
  methods: {
    handleCityClick (city) {
      this.changeCity(city)
      this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
  },
  mounted () {
    this.scroll = new Bscroll(this.$refs.search)
  }
}
</script>

[Оптимизация производительности стабилизации изображения ---]

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

3.3 Рекурсивные компоненты

Рекурсивные компоненты означают, что сам компонент вызывает сам компонент.

детали данных.json

{
  "ret": true,
  "data": {
    "sightName": "大连圣亚海洋世界(AAAA景区)",
    "bannerImg": "http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_600x330_bf9c4904.jpg",
    "gallaryImgs": ["http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_800x800_70debc93.jpg", "http://img1.qunarzz.com/sight/p0/1709/76/7691528bc7d7ad3ca3.img.png_800x800_9ef05ee7.png"],
    "categoryList": [{
        "title": "成人票",
        "children": [{
          "title": "成人三馆联票",
          "children": [{
            "title": "成人三馆联票 - 某一连锁店销售"
          }]
        },{
          "title": "成人五馆联票"
        }]
      }, {
        "title": "学生票"
      }, {
        "title": "儿童票"
      }, {
        "title": "特惠票"
      }]
  }
}

list.vue

<template>
  <div>
    <div
      class="item"
      v-for="(item, index) of list"
      :key="index"
    >
      <div class="item-title border-bottom">
        <span class="item-title-icon"></span>
        {{item.title}}
      </div>
      <div v-if="item.children" class="item-chilren">
        <detail-list :list="item.children"></detail-list>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'DetailList',
  props: {
    list: Array
  }
}
</script>

<style lang="stylus" scoped>
  .item-title-icon
    position: relative
    left: .06rem
    top: .06rem
    display: inline-block
    width: .36rem
    height: .36rem
    background: url(http://s.qunarzz.com/piao/image/touch/sight/detail.png) 0 -.45rem no-repeat
    margin-right: .1rem
    background-size: .4rem 3rem
  .item-title
    line-height: .8rem
    font-size: .32rem
    padding: 0 .2rem
  .item-chilren
    padding: 0 .2rem
</style>

В приведенном выше коде вlist-childrenВ соответствии с этим элементом суждение выносится первым, когдаitem.childrenКогда есть значение, вызовите себя, то есть компонент detail-list.Этот компонент также передает список в виде свойств, потому что список был получен через пропсы в list.vue, а внешний слой был зациклен Список теперь есть, и теперь мы хотим получить данные в дочерних элементах под списком, поэтому пусть атрибут списка будет равенitem.childrenВот и все. Поскольку данные имеют иерархическую взаимосвязь, их можно представить, добавив стиль. Эффект будет следующим:

递归.PNG

4. Проблемы и решения, возникшие в проекте

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

4.1 localStorage

Когда я впервые понял, что местоположение города отображается в правом верхнем углу домашней страницы, в каталоге src был создан новый каталог магазина для хранения данных по умолчанию в Vuex.Нажатие на город изменит город, но когда страница обновленный, он изменится обратно на Пекин.

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

В настоящее время вы можете использовать новый API, представленный в HTML5, который называетсяlocalStorage localStorage, он может реализовать локальное хранилище, которое должно реализовать здесь функцию спасения города.

В store/index.js напишите такой код, когда пользователь пытается изменить город, я не только меняю город в штате, но и сохраняюlocalStorage, пишите прямоlocalStorage.city = cityВот и все. Затем пусть значение города по умолчанию будетlocalStorage.city || "北京", Это оно. То есть значение города я пойду первым по умолчаниюlocalStorageЕсли он недоступен, по умолчанию будет использоваться «Пекин».

store/index.js

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
    state: {
        city: localStorage.city || "北京"
    },
    mutations: {
        changeCity(state, city) {
            state.city = city;
            localStorage.city = city;
        }
    }
})

В это время, когда страница открыта, когда пользователь выбирает город, а затем обновляет страницу, вы можете видеть, что город, выбранный в последний раз, все еще существует. но при использованииlocalStorage, рекомендуется обернутьtry{ }catch(e){ }Потому что в некоторых браузерах, если пользователь закрывает локальное хранилище такую ​​функцию, или используйте режим стелс, используйтеlocalStorageЭто может привести к тому, что браузер выдаст исключение напрямую, и код не запустится. Во избежание этой проблемы рекомендуется добавитьtry{ }catch(e){ }, как его добавить?

Сначала определите значение по умолчаниюdefaultCityРавно "Пекин", затем напишитеtry{ }catch(e){ }, напишите так: если естьlocalStorage.city,default.cityэквивалентноlocalStorage.city, город в состоянии ниже может быть равенdefaultCityТеперь также напишите changeCity в мутацияхtry{ }catch(e):

store/index.js

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

let defaultCity = "北京"
try {
    if (localStorage.city) {
        defaultCity = localStorage.city;
    }
} catch (e) { }

export default new Vuex.Store({
    state: {
        city: defaultCity
    },
    mutations: {
        changeCity(state, city) {
            state.city = city;
            try {
                localStorage.city = city;
            } catch (e) { }
        }
    }
})

Теперь мы видим, что файл store/index.js потихоньку усложняется, на самом деле в реальном проекте разработки и развития будет производиться дальнейшее разбиение, то есть файл будет разбиваться на State, Actions, Mutations, create файл с именем state.js в хранилище (хранить только общедоступные данные), затем поместить в него код, который устанавливает данные по умолчанию, и экспортировать его через экспорт, содержимое находится в объекте состояния, определенном в содержимом index.js:

let defaultCity = "北京"
try {
    if (localStorage.city) {
        defaultCity = localStorage.city;
    }
} catch (e) { }
export default {
    city: defaultCity
}

Далее просто импортируйте состояние в index.js:

import Vue from "vue";
import Vuex from "vuex";
import state from "./state";
Vue.use(Vuex);

export default new Vuex.Store({
    state: state,
    mutations: {
        changeCity(state, city) {
            state.city = city;
            try {
                localStorage.city = city;
            } catch (e) { }
        }
    }
})

Затем создайте файл в каталоге магазина с именем мутации.js и вырежьте в него код из объекта мутаций в index.js:

export default {
    changeCity(state, city) {
        state.city = city;
        try {
            localStorage.city = city;
        } catch (e) { }
    }
}
最终 index.js 就变成了这样:

import Vue from "vue";
import Vuex from "vuex";
import state from "./state";
import mutations from "./mutations";
Vue.use(Vuex);

export default new Vuex.Store({
    state: state,
    mutations: mutations
})

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

4.2 keep-alive

Оптимизация производительности веб-страницы с помощью keep-alive

После написания кода ответа списка городов запустите службу и откройте страницу, проблем нет, и была реализована некоторая базовая бизнес-логика, но откройте параметр «Сеть» в консоли, выберите XHR при входе на домашнюю страницу для первый раз, запрашивая файл index.json, затем переключаясь на страницу списка, снова запрашивая city.json, затем возвращаясь на домашнюю страницу, снова запрашивая index.json, снова переходя на страницу списка и снова запрашивая city.json , то есть каждый раз при изменении маршрута аякс будет отправляться заново.

keep-alive-1

Подумайте, в чем причина этой проблемы, откройте компонент домашней страницы Home.vue, и каждый раз, когда вы открываете домашнюю страницу, она будет перерисовываться, поэтому смонтированный хук будет выполняться повторно, а затем данные Ajax будут пересчитываться. fetch, так почему же он извлекается только один раз?

Откройте main.js, вы увидите, что компонент входа — это компонент приложения, а затем откройте App.vue, представление маршрутизатора отображает содержимое, соответствующее текущему адресу, мы можем обернуть метку поддержки активности во внешний слой, который является Vue Метка, которая идет с ним, означает, что после того, как содержимое моего маршрута будет загружено один раз, я помещу содержимое маршрута в память.Чтобы повторно выполнить функцию ловушки, просто перейдите в память и выньте предыдущий контент.

keep-alive-4

В это время вернитесь на страницу, снова откройте Сеть, войдите на страницу списка, выберите город и затем вернитесь на домашнюю страницу, вы больше не будете загружать index.json, а также снова войдете на страницу списка, и не будете загрузить city.json еще раз. , он будет напрямую корректировать данные из памяти, без повторного прохождения Ajax-запроса.

keep-alive-2

Это все еще логическая проблема.Когда я нахожусь в «Пекин», отображается содержимое «Пекин».При переключении на «Шанхай» должно отображаться содержимое «Шанхай», поэтому город изменился В то время домашняя страница также необходимо повторно выдать AJAX-запрос для получения информации о данных из разных городов, и мы вносим корректировку в этот блок.

Откройте компонент Home.vue, измените здесь адрес запроса axios и поставьте после него параметр, чтобы сделать его равным текущему городу, хранящемуся в Vuex, поэтому вам также нужно обратиться к Vuex в компоненте Home.vue,import { mapState } from "vuex", а затем добавьте вычисляемое свойство:

computed:{
    ...mapState(['city'])
}

Получите контент, соответствующий городу, а затем вы можете указать город в параметре запроса при отправке Ajax:

axios.get("/api/index.json?city=" + this.city)
.then(this.getHomeInfoSucc);

В это время, когда мы открываем страницу, мы видим, что параметр запроса уже содержит текущий город:

Однако, например, когда вы переключаете город «Гуйлинь» и возвращаетесь на домашнюю страницу, запрос Ajax не отправляется снова.Хотя город выше стал «Гуйлинь», содержимое ниже по-прежнему является содержимым «Пекин». Мы надеемся, что содержание ниже С изменением, что мне делать?

Когда мы использовали в App.vuekeep-aliveКогда содержимое этого блока закэшировано, он напрямую получает данные в кеше, так как же изменить данные в кеше? когда вы используетеkeep-alive, в компоненте появится дополнительная функция жизненного циклаactivted,

keep-alive-3

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

mounted() {
    console.log("mounted");
    this.getHomeInfo();
},
activated(){
    console.log("activted");
}

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

Прежде всего, когда страница будет смонтирована, то есть в смонтированном состоянии, будет отправлен Ajax-запрос.При повторном отображении страницы активация будет выполняться снова, поэтому мы можем каждый раз повторно отображать страницу, это может быть оценивает, совпадает ли город на текущей странице с городом, отображаемым на последней странице, если нет, снова отправляет запрос Ajax.

Сначала установите данные в данныхlastCity, значение по умолчанию пусто, тогда, когда страница смонтирована, сделайте его равнымthis.city, чтобы сохранить последний город:

mounted() {
    this.lastCity = this.city
    this.getHomeInfo();
}

Когда страница повторно активируется, мы пишем это в активированном:

activated() {
    if(this.lastCity != this.city){
        this.lastCity = this.city
        this.getHomeInfo();
    }
}

Если последний город lastCity не равен текущему городу, повторно отправьте запрос Ajax и вызовите вышеуказанное напрямуюgetHomeInfoметод подойдет. Когда последний город отличается от этого времени, он должен быть равен текущему городу. Вернувшись на страницу, вы можете видеть, что когда город для переключения совпадает с последним городом, Ajax не будет запрашивать city.json, а когда он отличается, он будет запрашивать city.json.

Возвращаясь к коду, с помощью новой функции жизненного цикла keep-alive, такой как activted, в сочетании с переменной временного кэша, такой как lastCity, можно настроить оптимизацию производительности кода домашней страницы.

V. Последующие мероприятия

  • Функционал страницы улучшен
  • Развернуть проект на сервере
  • В сочетании с узлом, стыковка с реальными данными

Обратите внимание, этот проект исходит от DellРазработка Vue2.5 приложения Qunar от нуля до реального боевого проекта

Часть вдохновения для написания и идей этой статьи исходит от троицы богов.React hooks+redux+immutable.js для создания красивого веб-приложения для NetEase Cloud Music