import { Meta } from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/vi/functional/Functions/executeUse - Singleton với quản lý vòng đời'/>

# `executeUse`

`executeUse` là một factory bao bọc bất kỳ hàm khởi tạo nào và biến kết quả của nó thành một **singleton được quản lý**. Đối tượng được tạo đúng một lần, đóng băng (`Object.freeze`) và tái sử dụng trong mỗi lần gọi tiếp theo. Tùy theo loại được chọn, singleton có thể là phạm vi component, toàn cục, hoặc cục bộ.

Để đảm bảo định danh duy nhất, thư viện kết hợp mã code toàn cục nội bộ và hàm `getElementId()`, giúp ngăn chặn hoàn toàn việc trùng lặp ID ngay cả khi sử dụng trong nhiều module khác nhau.



### Giới hạn Generics (Generic Constraints)

- `R` — kiểu đối tượng được trả về bởi hàm khởi tạo (`callback`).
- `O extends any[]` — kiểu của các tham số đầu vào cho `callback`.
- `RI extends ExecuteUseReturn<R>` — kiểu kết quả của singleton, bao gồm các phương thức quản lý.

```typescript
function executeUse<R, O extends any[], RI extends ExecuteUseReturn<R>>(
  callback: (...args: O) => R,
  type: ExecuteUseType = ExecuteUseType.provide
): (...args: O) => RI

function executeUseGlobal<R>(callback: () => R): () => ExecuteUseReturn<R>
function executeUseProvide<R, O extends any[]>(callback: (...args: O) => R): (...args: O) => ExecuteUseReturn<R>
function executeUseLocal<R, O extends any[]>(callback: (...args: O) => R): (...args: O) => ExecuteUseReturn<R>
```

**Tham số:**
- `callback: (...args: O) => R` — Hàm khởi tạo. Được gọi khi truy cập lần đầu và trả về đối tượng sẽ được đóng băng và cache. Tham số chỉ được truyền vào ở lần gọi đầu tiên.
- `type: ExecuteUseType` — Loại singleton (mặc định: `ExecuteUseType.provide`).

**Trả về:** `ExecuteUseReturn<R>` — đối tượng đóng băng mở rộng `R` với các phương thức:
- `init(): Readonly<R>` — trả về kết quả gốc của `callback` mà không có các phương thức quản lý (`init`, `destroyExecute`).
- `destroyExecute?(): void` — chỉ có ở `local` và `global`. Đặt lại cache, cho phép tạo lại đối tượng ở lần gọi tiếp theo.

### Hàm Khởi Tạo (`callback`) và Tham Số Đầu Vào (`...args: O`)

Đặc điểm chính của `executeUse` là hàm factory (`callback`) và các tham số được truyền vào nó (`...args`) chỉ được sử dụng **đúng một lần** — trong quá trình khởi tạo thực tế (ngay ở lần gọi đầu tiên).

- **`callback`**: Đây là một factory mô tả logic tạo lập trạng thái. Bên trong đó, bạn thường khai báo các biến phản ứng (`ref`, `reactive`), các hàm để thay đổi chúng, và các giá trị tính toán (`computed`). Đối tượng trả về từ `callback` sẽ đóng băng và được đưa vào cache.
- **`...args: O`**: Bạn có thể truyền bất kỳ tham số nào vào hàm khởi tạo của mình (ví dụ: trạng thái ban đầu, ID của thực thể đang tải hoặc API client). Các tham số này sẽ được chuyển trực tiếp vào bên trong `callback`.

> **Lưu ý quan trọng về tham số:** Vì `executeUse` ghi nhớ rất chặt (cache) kết quả của lần gọi đầu tiên, mọi tham số được truyền vào ở các lần gọi **tiếp theo** của cùng hook đó ở những component khác **sẽ bị bỏ qua hoàn toàn**. Đối tượng sẽ không được tạo lại và hàm `callback` cũng không chạy lại với tham số mới (trừ phi bạn đã chủ động đặt lại cache trước đó thông qua `destroyExecute()`).

### Phương Thức Ký Hiệu `init()`

Phương thức `init` chỉ đơn thuần là một hàm trống, chức năng duy nhất là trả về chính xác cùng một đối tượng trạng thái đã được đóng băng. Nó tồn tại **hoàn toàn vì mục đích dễ đọc mã nguồn và trải nghiệm của lập trình viên (DX)**.

Mục đích chính của nó là cung cấp cho lập trình viên một cách để đánh dấu một cách tường minh và trực quan ngay trong mã nguồn vị trí chính xác xảy ra quá trình khởi tạo (lần gọi đầu tiên) của singleton. Từ góc độ kỹ thuật, `const state = useTheme()` và `const state = useTheme().init()` hoạt động hoàn toàn giống hệt nhau.

```typescript
// Ở một nơi nào đó sâu trong cây component, chúng ta chỉ cần lấy trạng thái đã sẵn sàng
const theme = useTheme()

// Còn ở đây, trong component gốc (hoặc main.ts), 
// chúng ta chủ động hiển thị cho các lập trình viên khác thấy: "Quá trình khởi tạo diễn ra ngay TẠI ĐÂY"
const theme = useTheme().init()
```
## Hướng Dẫn Từng Bước: Cách Tạo và Sử Dụng Dịch Vụ Của Riêng Bạn

Việc tạo một singleton được quản lý bằng `executeUse` thường bao gồm ba bước đơn giản.

### Bước 1: Định Nghĩa Trạng Thái (Factory)
Trước tiên, hãy quyết định bạn cần loại trạng thái nào (`global`, `local` hoặc `provide`). Tạo một tệp riêng biệt (ví dụ: `useMyService.ts`). Sử dụng một trong các hàm factory (ví dụ: `executeUseGlobal`).

Bên trong hàm callback, hãy định nghĩa tất cả các biến phản ứng (`ref`, `reactive`), thuộc tính tính toán (`computed`) và các hàm để thay đổi trạng thái:

```typescript
// src/composables/useMyService.ts
import { ref, computed } from 'vue'
import { executeUseGlobal } from '@dxtmisha/functional'

// Tạo singleton và đóng gói cơ chế logic
const instance = executeUseGlobal(() => {
  // 1. Trạng thái phản hồi
  const count = ref(0)
  
  // 2. Thuộc tính tính toán
  const isPositive = computed(() => count.value > 0)
  
  // 3. Phương thức quản lý
  const increment = () => count.value++
  const reset = () => { count.value = 0 }

  // Trả về những gì cần được bộc lộ ra bên ngoài
  return { count, isPositive, increment, reset }
})

// Xuất ra một hàm bọc ngoài (wrapper).
// Cú pháp () => instance() là điều cần thiết để giúp các trình soạn thảo mã (IDE)
// nhận diện useMyService chính xác là một hàm composable.
// Điều này đảm bảo tính năng tự động hoàn thành mã (autocomplete) và suy luận kiểu hoạt động đúng.
export const useMyService = () => instance()
```

### Bước 2: Khởi Tạo (Chỉ dành cho `global`)
Nếu bạn chọn chế độ `global` (như trong ví dụ trên), bạn cần báo cho ứng dụng biết chính xác thời điểm thể hiện nó. Điều này thường được thực hiện một lần tại điểm xâm nhập (`main.ts`):

```typescript
// src/main.ts
import { createApp } from 'vue'
import { executeUseGlobalInit } from '@dxtmisha/functional'
import App from './App.vue'

// Khởi tạo toàn bộ các global singleton cùng một lúc
executeUseGlobalInit() 

createApp(App).mount('#app')
```

> **Lưu ý:** Nếu ứng dụng của bạn sử dụng `dxtFunctionalPlugin`, bạn **không cần phải** gọi `executeUseGlobalInit()` một cách thủ công lúc khởi động — plugin đã tự động xử lý việc này ở hậu trường.

### Bước 3: Sử Dụng trong Component
Bây giờ bạn có thể nhập hàm `useMyService` của mình vào bất kỳ component Vue nào. Tại lần gọi định danh đầu tiên, hãy sử dụng phương thức `init()` để mã rõ ràng hơn.

```vue
<!-- src/components/MyComponent.vue -->
<script setup lang="ts">
import { useMyService } from '@/composables/useMyService'

// Nhận trực tiếp trạng thái. Phương thức init() cho biết
// lệnh gọi (truy cập) chính đầu tiên đang xảy ra ở đây.
const { count, isPositive, increment } = useMyService().init()
</script>

<template>
  <div>
    <p>Số đếm hiện tại: {{ count }}</p>
    <p>Có dương không? {{ isPositive ? 'Có' : 'Không' }}</p>
    <button @click="increment">Tăng Dần</button>
  </div>
</template>
```

Nếu vì lý do nào đó, bạn cần thiết lập lại trạng thái (ví dụ: khi người dùng đăng xuất), bạn chỉ cần gọi phương thức reset tích hợp sẵn:
```typescript
const service = useMyService()
if (service.destroyExecute) {
  service.destroyExecute()
}
```

## Enum `ExecuteUseType`

### `provide` (singleton phạm vi component, mặc định)

Đây là một singleton định hướng component, được liên kết chặt chẽ với vòng đời của một component Vue và toàn bộ cây con của nó. Bên dưới, nó sử dụng cơ chế truyền ngữ cảnh và phản ứng tích hợp sẵn của Vue (`provide`/`inject`), cho phép bạn truyền cùng một phiên bản trạng thái (state instance) sâu xuống cây component một cách an toàn mà không cần phải truyền qua các props.

**Khi nào sử dụng:** 
- Để chia sẻ trạng thái trong một nhánh component được chỉ định (component cha -> nhiều cấp lồng nhau -> component con), tránh làm ô nhiễm kho lưu trữ (store) toàn cục.
- Khi cần hiển thị nhiều khối độc lập trên cùng một trang (ví dụ: một vài widget tải tệp lên phức tạp), mỗi khối cần có một trạng thái duy nhất, dùng chung riêng bên trong widget đó.
- Nếu bạn cần dọn dẹp bộ nhớ tự động (garbage collection) ngay khi "component chủ" bị hủy (unmount).

**Cách hoạt động:**
Component gọi hàm này đầu tiên tự động trở thành "chủ sở hữu" (owner). Hàm tạo đối tượng và ngay lập tức gọi `provide(id, instance)`. Mọi component con (bên trong) khi gọi cùng một hàm này sẽ không tạo ra đối tượng mới mà lấy đối tượng đã tạo trước đó qua `inject(id)`. 
> Chú ý: Loại này thiếu phương thức `destroyExecute()` vì bộ nhớ được tự do hóa một cách tự động nhờ hệ thống phản ứng (reactivity) của Vue tại thời điểm hủy component.

```typescript
import { executeUseProvide } from '@dxtmisha/functional'
import { ref } from 'vue'

const useTheme = executeUseProvide(() => {
  const mode = ref<'light' | 'dark'>('light')
  const toggle = () => { mode.value = mode.value === 'light' ? 'dark' : 'light' }
  return { mode, toggle }
})

// Trong component cha (chủ) — đối tượng được tạo và phát đi (provide):
const theme = useTheme()
theme.toggle()

// Trong component con lồng sâu — nó nhận về cùng một đối tượng qua inject:
const theme = useTheme()

// Ở một cây component hoàn toàn khác biệt, độc lập — một đối tượng MỚI sẽ được tạo:
const theme2 = useTheme()
```

### `global` (singleton toàn ứng dụng)

Đây là một mẫu kiến trúc (architectural pattern) Singleton cổ điển, tồn tại như một phiên bản duy nhất cực kỳ nghiêm ngặt trong toàn bộ thời gian tồn tại của ứng dụng của bạn (từ lúc tạo cho đến khi đóng tab trình duyệt). Nó cung cấp một điểm truy cập toàn cục vào trạng thái từ bất kỳ vị trí thực tế nào trong mã nguồn.

**Khi nào sử dụng:**
- Dùng cho trạng thái toàn cục của ứng dụng: thông tin xác thực, tài khoản người dùng, thiết lập ngôn ngữ, giỏ hàng toàn cục.
- Khi một dữ liệu cần được chia sẻ cho các khối thành phần khác biệt, độc lập nhau trong chương trình (hoặc bên ngoài component Vue, như ở router).
- Như một biện pháp thay thế dung lượng nhỏ cho Vuex / Pinia trong các tác vụ đơn giản.

**Cách hoạt động:**
Hàm khởi tạo sẽ **không thực thi ngay khi gọi**. Tuy nhiên, nó sẽ được chuyển tới một hàm đợi bên trong (`globalMethods`). Mọi đối tượng toàn cục chỉ khởi tạo đầy đủ khi trực tiếp gọi hàm `executeUseGlobalInit()`, chủ yếu thường nằm trong tệp `main.ts` trước khi ứng dụng được mount. Kể từ lúc ấy, mọi khối sẽ chia sẻ với nhau cùng một đối tượng được cache trong bộ nhớ.

```typescript
import { executeUseGlobal, executeUseGlobalInit } from '@dxtmisha/functional'
import { ref } from 'vue'

const useAuth = executeUseGlobal(() => {
  const user = ref<string | null>(null)
  const login = (name: string) => { user.value = name }
  const logout = () => { user.value = null }
  return { user, login, logout }
})

// Trong tệp main.ts — tuân thủ TRƯỚC thời điểm mount app (app.mount()):
executeUseGlobalInit()

// Ở mọi block component, utility hay router:
const auth = useAuth()
auth.login('Alice')

// Trong trường hợp bắt buộc phải đặt lại trạng thái (tạo lại trạng thái nguyên mẫu từ đầu):
auth.destroyExecute?.() 
```

### `local` (singleton lưu trong closure)

Đây là một singleton hoạt động độc lập, dựa trên cơ chế kết thúc từ vựng (`closure caching`) trong JavaScript. Thay vì dùng ngữ cảnh Vue hay phạm vi toàn cục, nó bảo tồn kết quả đã tạo ra một lần trực tiếp ngay trong bộ nhớ nội bộ của hàm, và trả về nó ngay lập tức trong tất cả các lần gọi tiếp theo.

**Khi nào sử dụng:**
- Dùng cho utilities, các tác vụ nhà máy (factory) hay các service riêng biệt không quy bám vào Vue (bởi vì `provide`/`inject` chỉ tác dụng bên trong scope của `setup`).
- Tại lúc muốn lấy cá thể độc quyền dành cho những dịch vụ nặng hoặc tốn kém chỉ duy nhất tại lúc nào thiệt cần (khởi động trễ thụy động laziness on demand), mà không phải thời gian đầu trong chu kỳ chung.
- Ở những lần dùng phép lưu hệ số tạm thời (cache) những hoạt hóa tính toán, chuyển đổi của phép hệ thống lớn.

**Cách hoạt động:**
Loại này không màng đến cơ chế Vue. Ngay gọi hàm lần bước 1 tiên phong, các chức năng hàm của tham số đưa lên sẽ lập tức thi triển. Hệ trả xuất ra định chặt `Object.freeze` (bị đóng băng hoàn trả) lại cất vào trong một biến thể nội bộ giữa đám đóng của hàm `executeUse`. Những lần thứ kế theo (đôi khi cho là tham số sai số) nó vẫn nhả thẳng dữ liệu từ cùng cái chỗ kia nhanh chóng.
Phương pháp `destroyExecute()` quét dọn biến của đợt trong vòng, châm ngọn cho sự tái bật khởi động cho thời kỳ lần tiếp khởi xưởng sau này.

```typescript
import { executeUseLocal } from '@dxtmisha/functional'
import { ref } from 'vue'

const useHeavyConfig = executeUseLocal((env: string) => {
  // Thực hiện một thao tác phân giải số liệu config tải siêu lớn...
  const config = ref({ api: `https://${env}.example.com` })
  return { config }
})

// Chạy lần đầu — Quá trình sinh diễn
const devConfig = useHeavyConfig('dev')
console.log(devConfig.config.value.api) // https://dev.example.com

// Lần gọi hai bên file nào đi tữa — Sẽ luôn ra đúng một đằng, mọi lệnh chèn sau vô tác dụng
const sameConfig = useHeavyConfig('prod') 
console.log(sameConfig.config.value.api) // Vẫn là kết quả cũ: https://dev.example.com

// Ép việc phá hủy kho tạm
sameConfig.destroyExecute?.()
const newConfig = useHeavyConfig('prod') // Bắt đầu tái xuất bản quy trình một lần nữa
```

## `executeUseGlobalInit`

```typescript
function executeUseGlobalInit(): void
```

Gọi tất cả các hàm khởi tạo toàn cục đã đăng ký (`ExecuteUseType.global`) và xóa hàng đợi. Gọi lại lần nữa sẽ không khởi tạo lại các singleton đã tồn tại. Cần được gọi **một lần** khi khởi động ứng dụng — trước khi mount component gốc.

```typescript
import { createApp } from 'vue'
import { executeUseGlobalInit } from '@dxtmisha/functional'
import App from './App.vue'

executeUseGlobalInit()
createApp(App).mount('#app')
```

