Биз жөнүндөБлогБайланыш
Frontend2016-ж., 15-ноябрь 4 мин 10

Vue.js менен биринчи долбоор: Бишкектеги тажрыйба (2016)

AunimedaAunimeda
📋 Мазмуну

Vue.js менен биринчи долбоор: Бишкектеги тажрыйба (2016)

Кыскача: Vue.js 2.0 орнотуу жөнөкөй: <script src="vue.min.js"> же npm install vue. Компоненттер Vue.component() аркылуу же .vue файлдары менен. Реактивдик маалымат: data() объектинен. Vuex — глобал state үчүн. React'ка салыштырмалуу 30% аз код.


Эмне үчүн Vue.js тандадык (2016)

Биздин командада 2016-жылы:

  • React жана Redux — татаал, жаңы башталган разработчикке кыйын
  • Angular 2 beta — нестабилдүү
  • Vue.js 1.x — жөнөкөй, бирок API көп өзгөрдү
  • Vue.js 2.0 (октябрь 2016) — туруктуу, React'тин иштеп чыгуу тезди, жеңил үйрөнүлгөн

Команданын 2 junior разработчики Vue.js'ти 1 апта ичинде үйрөнүштү. React: 3–4 апта.


Орнотуу

# npm аркылуу
npm install vue
npm install vue-cli -g
vue init webpack-simple my-vue-app
cd my-vue-app && npm install && npm run dev

# Же CDN аркылуу (быстрый прототип)
# <script src="https://unpkg.com/vue@2.0.5/dist/vue.min.js"></script>

Биринчи компонент: себет карточкасы

<!-- CartItem.vue — Vue Single File Component -->
<template>
  <div class="cart-item">
    <img :src="item.image" :alt="item.name" class="cart-item__image">
    
    <div class="cart-item__info">
      <h3 class="cart-item__name">{{ item.name }}</h3>
      <p class="cart-item__price">{{ formatPrice(item.price) }}</p>
    </div>
    
    <div class="cart-item__quantity">
      <button @click="decrease" :disabled="item.qty <= 1">−</button>
      <span>{{ item.qty }}</span>
      <button @click="increase">+</button>
    </div>
    
    <p class="cart-item__total">{{ formatPrice(item.price * item.qty) }}</p>
    
    <button class="cart-item__remove" @click="remove">✕</button>
  </div>
</template>

<script>
export default {
  name: 'CartItem',
  
  props: {
    item: {
      type: Object,
      required: true
    }
  },
  
  methods: {
    increase() {
      this.$emit('update-qty', this.item.id, this.item.qty + 1);
    },
    decrease() {
      if (this.item.qty > 1) {
        this.$emit('update-qty', this.item.id, this.item.qty - 1);
      }
    },
    remove() {
      this.$emit('remove', this.item.id);
    },
    formatPrice(price) {
      return new Intl.NumberFormat('ru-KG').format(price) + ' сом';
    }
  }
}
</script>

<style scoped>
.cart-item {
  display: flex;
  align-items: center;
  padding: 12px;
  border-bottom: 1px solid #eee;
}
.cart-item__image {
  width: 60px;
  height: 60px;
  object-fit: cover;
  margin-right: 12px;
}
</style>

Себет компоненти

<!-- Cart.vue — Себет (корзина) компоненти -->
<template>
  <div class="cart">
    <h2>Себет ({{ itemCount }} товар)</h2>
    
    <div v-if="items.length === 0" class="cart--empty">
      Себет бош. <router-link to="/catalog">Каталогго өтүңүз</router-link>
    </div>
    
    <template v-else>
      <cart-item
        v-for="item in items"
        :key="item.id"
        :item="item"
        @update-qty="updateQuantity"
        @remove="removeItem"
      />
      
      <div class="cart__summary">
        <div class="cart__total">
          Жалпы: <strong>{{ formatPrice(total) }}</strong>
        </div>
        
        <div class="cart__delivery">
          Жеткизүү: 
          <span v-if="total >= 3000" class="free">Бекер</span>
          <span v-else>{{ formatPrice(deliveryCost) }}</span>
        </div>
        
        <div class="cart__grand-total">
          Баардыгы: <strong>{{ formatPrice(grandTotal) }}</strong>
        </div>
        
        <button 
          class="checkout-btn"
          @click="checkout"
          :disabled="loading"
        >
          {{ loading ? 'Иштеп жатат...' : 'Заказ берүү' }}
        </button>
      </div>
    </template>
  </div>
</template>

<script>
import CartItem from './CartItem.vue';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';

export default {
  name: 'Cart',
  components: { CartItem },
  
  data() {
    return {
      loading: false,
      deliveryCost: 200,  // Бишкек боюнча жеткизүү
    };
  },
  
  computed: {
    ...mapState('cart', ['items']),
    
    itemCount() {
      return this.items.reduce((sum, item) => sum + item.qty, 0);
    },
    
    total() {
      return this.items.reduce((sum, item) => sum + item.price * item.qty, 0);
    },
    
    grandTotal() {
      return this.total + (this.total >= 3000 ? 0 : this.deliveryCost);
    },
  },
  
  methods: {
    ...mapMutations('cart', ['removeItem', 'updateQuantity']),
    ...mapActions('cart', ['submitOrder']),
    
    async checkout() {
      this.loading = true;
      try {
        const order = await this.submitOrder();
        this.$router.push('/order/' + order.id + '/success');
      } catch (error) {
        alert('Ката кетти: ' + error.message);
      } finally {
        this.loading = false;
      }
    },
    
    formatPrice(price) {
      return new Intl.NumberFormat('ru-KG').format(price) + ' сом';
    }
  }
}
</script>

Vuex Store: себет статусу

// store/modules/cart.js

import api from '../../services/api';

const state = {
    items: JSON.parse(localStorage.getItem('cart') || '[]'),
};

const mutations = {
    addItem(state, product) {
        const existing = state.items.find(i => i.id === product.id);
        if (existing) {
            existing.qty++;
        } else {
            state.items.push({ ...product, qty: 1 });
        }
        localStorage.setItem('cart', JSON.stringify(state.items));
    },
    
    removeItem(state, productId) {
        state.items = state.items.filter(i => i.id !== productId);
        localStorage.setItem('cart', JSON.stringify(state.items));
    },
    
    updateQuantity(state, { productId, qty }) {
        const item = state.items.find(i => i.id === productId);
        if (item) {
            item.qty = qty;
            localStorage.setItem('cart', JSON.stringify(state.items));
        }
    },
    
    clearCart(state) {
        state.items = [];
        localStorage.removeItem('cart');
    },
};

const actions = {
    async submitOrder({ state, commit, rootState }) {
        const response = await api.post('/orders', {
            items: state.items.map(item => ({
                product_id: item.id,
                qty:        item.qty,
                price:      item.price,
            })),
            // Колдонуучунун дареги profile'дан
            address: rootState.user.profile?.address,
        });
        
        commit('clearCart');
        return response.data.order;
    },
};

export default { namespaced: true, state, mutations, actions };

main.js — Vue колдонмо башталышы

// main.js
import Vue    from 'vue';
import App    from './App.vue';
import router from './router';
import store  from './store';

Vue.config.productionTip = false;

new Vue({
    el:     '#app',
    router,
    store,
    render: h => h(App),
});

React менен салыштыруу (2016 Бишкек тажрыйбасы)

Vue.js 2.0 React + Redux
Код сабы (CartItem) 45 сап 65 сап
Үйрөнүү убактысы (junior) 1 апта 3–4 апта
HTML шаблон .vue файлда JSX
Реактивдик маалымат Автомат (data()) setState() кол менен
Глобал state Vuex Redux
Экосистема Кичинерек Чоңураак

Биздин кичине команда үчүн Vue.js туура тандоо болду. React экосистемасы 2016-жылы ири компаниялар үчүн туура болчу.

Ошондой эле окуңуз

Express.js менен REST API сервер кантип жасоо: нөлдөн production'го чейин (2015)aunimeda
Backend

Express.js менен REST API сервер кантип жасоо: нөлдөн production'го чейин (2015)

Node.js 4 LTS + Express.js 4 — 2015-жылда PHP'га альтернатива катары пайда болду. Биз Бишкекте мобилдик тиркеме үчүн API сервер жасадык. Роутинг, middleware, валидация, JWT авторизация, MySQL — бардыгы бир жерде. Иштеген код мисалдары.

MySQL суроо-талаптарын кантип тездетүү керек: EXPLAIN жана индекстер (2015)aunimeda
Маалымат базасы

MySQL суроо-талаптарын кантип тездетүү керек: EXPLAIN жана индекстер (2015)

Жай MySQL суроо-талаптары 2015-жылда да, 2025-жылда да бирдей диагностикаланат: EXPLAIN, slow query log, индекс жетишпейт. Бул макалада реалдуу мисал менен 8 секунддук суроо-талапты 40 мс'ка чейин тездеткен жолду карайбыз — кодду өзгөртпөстөн.

React Native менен биринчи мобилдик тиркеме жасоо: Бишкек тажрыйбасы (2015)aunimeda
Мобилдик иштеп чыгуу

React Native менен биринчи мобилдик тиркеме жасоо: Бишкек тажрыйбасы (2015)

React Native 2015-жылдын март айында Facebook тарабынан жарыяланды. Биз iOS жана Android үчүн бир эле код жазуу мүмкүнчүлүгүнө ишандык. Биринчи production тиркемебизди 8 апта ичинде жасадык. Реалдуу код, реалдуу проблемалар, реалдуу жыйынтыктар.

Бизнесиңизге IT иштеп чыгуу керекпи?

Веб-сайттарды, мобилдик тиркемелерди жана AI чечимдерин иштеп чыгабыз. Акысыз консультация.

Консультация алуу Бардык макалалар