Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled
333 lines
5.8 KiB
Markdown
333 lines
5.8 KiB
Markdown
# JavaScript/TypeScript Code Style Rules
|
||
|
||
## JavaScript Version
|
||
|
||
- ES2020+ features
|
||
- Modern async/await
|
||
- Optional chaining (`?.`)
|
||
- Nullish coalescing (`??`)
|
||
- Template literals
|
||
|
||
## Code Style
|
||
|
||
### Variable Declarations
|
||
|
||
```javascript
|
||
// ✅ Use const by default
|
||
const customers = [];
|
||
const totalRecords = 0;
|
||
|
||
// ✅ Use let only when reassignment is needed
|
||
let currentPage = 1;
|
||
currentPage = 2;
|
||
|
||
// ❌ Do not use var
|
||
var oldVariable = 'bad';
|
||
```
|
||
|
||
### Arrow Functions
|
||
|
||
```javascript
|
||
// ✅ Preferred for short functions
|
||
const filtered = items.filter(item => item.isActive);
|
||
|
||
// ✅ For object methods
|
||
const api = {
|
||
get: async (url) => {
|
||
return await fetch(url);
|
||
}
|
||
};
|
||
|
||
// ✅ For complex logic – use regular functions
|
||
function complexCalculation(data) {
|
||
// many lines of code
|
||
return result;
|
||
}
|
||
```
|
||
|
||
### Template Literals
|
||
|
||
```javascript
|
||
// ✅ Preferred
|
||
const message = `User ${userId} not found`;
|
||
const url = `${baseUrl}/api/${endpoint}`;
|
||
|
||
// ❌ Do not use concatenation
|
||
const message = 'User ' + userId + ' not found';
|
||
```
|
||
|
||
### Optional Chaining
|
||
|
||
```javascript
|
||
// ✅ Use optional chaining
|
||
const name = user?.profile?.name;
|
||
const count = data?.items?.length ?? 0;
|
||
|
||
// ❌ Avoid long nested checks
|
||
const name = user && user.profile && user.profile.name;
|
||
```
|
||
|
||
### Nullish Coalescing
|
||
|
||
```javascript
|
||
// ✅ Use ?? for default values
|
||
const page = params.page ?? 1;
|
||
const name = user.name ?? 'Unknown';
|
||
|
||
// ❌ Do not use || for numbers/booleans
|
||
const page = params.page || 1; // 0 will be replaced with 1
|
||
```
|
||
|
||
### Destructuring
|
||
|
||
```javascript
|
||
// ✅ Use destructuring
|
||
const { data, totalRecords } = response.data;
|
||
const [first, second] = items;
|
||
|
||
// ✅ In function parameters
|
||
function processUser({ id, name, email }) {
|
||
// ...
|
||
}
|
||
|
||
// ✅ With default values
|
||
const { page = 1, limit = 20 } = params;
|
||
```
|
||
|
||
### Async/Await
|
||
|
||
```javascript
|
||
// ✅ Preferred
|
||
async function loadCustomers() {
|
||
try {
|
||
const response = await apiGet('getCustomers', params);
|
||
return response.data;
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ❌ Avoid .then() chains
|
||
function loadCustomers() {
|
||
return apiGet('getCustomers', params)
|
||
.then(response => response.data)
|
||
.catch(error => console.error(error));
|
||
}
|
||
```
|
||
|
||
## Vue.js 3 Composition API
|
||
|
||
### Script Setup
|
||
|
||
```vue
|
||
<script setup>
|
||
// ✅ Use <script setup>
|
||
import { ref, computed, onMounted } from 'vue';
|
||
import { apiGet } from '@/utils/http.js';
|
||
|
||
const customers = ref([]);
|
||
const loading = ref(false);
|
||
|
||
const totalRecords = computed(() => customers.value.length);
|
||
|
||
async function loadCustomers() {
|
||
loading.value = true;
|
||
try {
|
||
const result = await apiGet('getCustomers');
|
||
customers.value = result.data || [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadCustomers();
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### Reactive State
|
||
|
||
```javascript
|
||
// ✅ Use ref for primitives
|
||
const count = ref(0);
|
||
const name = ref('');
|
||
|
||
// ✅ Use reactive for objects
|
||
import { reactive } from 'vue';
|
||
const state = reactive({
|
||
customers: [],
|
||
loading: false
|
||
});
|
||
|
||
// ✅ Or ref for objects (preferred)
|
||
const state = ref({
|
||
customers: [],
|
||
loading: false
|
||
});
|
||
```
|
||
|
||
### Computed Properties
|
||
|
||
```javascript
|
||
// ✅ Use computed for derived values
|
||
const filteredCustomers = computed(() => {
|
||
return customers.value.filter(c => c.isActive);
|
||
});
|
||
|
||
// ❌ Do not use methods for derived values
|
||
function filteredCustomers() {
|
||
return customers.value.filter(c => c.isActive);
|
||
}
|
||
```
|
||
|
||
### Props
|
||
|
||
```vue
|
||
<script setup>
|
||
// ✅ Define props with types
|
||
const props = defineProps({
|
||
customerId: {
|
||
type: Number,
|
||
required: true
|
||
},
|
||
showDetails: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### Emits
|
||
|
||
```vue
|
||
<script setup>
|
||
// ✅ Define emits
|
||
const emit = defineEmits(['update', 'delete']);
|
||
|
||
function handleUpdate() {
|
||
emit('update', data);
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## Pinia Stores
|
||
|
||
```javascript
|
||
// ✅ Use setup stores
|
||
import { defineStore } from 'pinia';
|
||
import { ref, computed } from 'vue';
|
||
|
||
export const useCustomersStore = defineStore('customers', () => {
|
||
const customers = ref([]);
|
||
const loading = ref(false);
|
||
|
||
const totalRecords = computed(() => customers.value.length);
|
||
|
||
async function loadCustomers() {
|
||
loading.value = true;
|
||
try {
|
||
const result = await apiGet('getCustomers');
|
||
customers.value = result.data || [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
return {
|
||
customers,
|
||
loading,
|
||
totalRecords,
|
||
loadCustomers
|
||
};
|
||
});
|
||
```
|
||
|
||
## Error Handling
|
||
|
||
```javascript
|
||
// ✅ Always handle errors
|
||
async function loadData() {
|
||
try {
|
||
const result = await apiGet('endpoint');
|
||
if (result.success) {
|
||
return result.data;
|
||
} else {
|
||
throw new Error(result.error);
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load data:', error);
|
||
toast.error('Failed to load data');
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Naming Conventions
|
||
|
||
### Variables and Functions
|
||
|
||
```javascript
|
||
// ✅ camelCase
|
||
const customerData = {};
|
||
const totalRecords = 0;
|
||
function loadCustomers() {}
|
||
|
||
// ✅ Constants in UPPER_SNAKE_CASE
|
||
const MAX_RETRIES = 3;
|
||
const API_BASE_URL = '/api';
|
||
```
|
||
|
||
### Components
|
||
|
||
```vue
|
||
<!-- ✅ PascalCase for components -->
|
||
<CustomerCard />
|
||
<ProductsList />
|
||
```
|
||
|
||
### Files
|
||
|
||
```javascript
|
||
// ✅ Use kebab-case for files
|
||
// customers-view.vue
|
||
// http-utils.js
|
||
// customer-service.js
|
||
```
|
||
|
||
## Imports
|
||
|
||
```javascript
|
||
// ✅ Group imports
|
||
// 1. Vue core
|
||
import { ref, computed, onMounted } from 'vue';
|
||
|
||
// 2. Third-party
|
||
import { apiGet } from '@/utils/http.js';
|
||
import { useToast } from 'primevue';
|
||
|
||
// 3. Local components
|
||
import CustomerCard from '@/components/CustomerCard.vue';
|
||
|
||
// 4. Types (if using TypeScript)
|
||
import type { Customer } from '@/types';
|
||
```
|
||
|
||
## TypeScript (where used)
|
||
|
||
```typescript
|
||
// ✅ Use types
|
||
interface Customer {
|
||
id: number;
|
||
name: string;
|
||
email?: string;
|
||
}
|
||
|
||
function getCustomer(id: number): Promise<Customer> {
|
||
return apiGet(`customers/${id}`);
|
||
}
|
||
```
|
||
|