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-жылы ири компаниялар үчүн туура болчу.