Làm cách nào để sử dụng không gian tên với các mô-đun bên ngoài TypeScript?


233

Tôi có một số mã:

baseTypes.ts

export namespace Living.Things {
  export class Animal {
    move() { /* ... */ }
  }
  export class Plant {
    photosynthesize() { /* ... */ }
  }
}

chó

import b = require('./baseTypes');

export namespace Living.Things {
  // Error, can't find name 'Animal', ??
  export class Dog extends Animal {
    woof() { }
  }
}

cây.ts

// Error, can't use the same name twice, ??
import b = require('./baseTypes');
import b = require('./dogs');

namespace Living.Things {
  // Why do I have to write b.Living.Things.Plant instead of b.Plant??
  class Tree extends b.Living.Things.Plant {

  }
}

Điều này rất khó hiểu. Tôi muốn có một loạt các mô-đun bên ngoài tất cả các loại đóng góp vào cùng một không gian tên , Living.Things. Dường như điều này không làm việc ở tất cả - Tôi không thể nhìn thấy Animaltrong dogs.ts. Tôi phải viết tên không gian tên đầy đủ b.Living.Things.Planttrong tree.ts. Nó không hoạt động để kết hợp nhiều đối tượng trong cùng một không gian tên trên tệp. Làm thế nào để tôi làm điều này?

Câu trả lời:


860

Tương tự Cup kẹo

Phiên bản 1: Một cốc cho mỗi viên kẹo

Giả sử bạn đã viết một số mã như thế này:

Mod1.ts

export namespace A {
    export class Twix { ... }
}

Mod2.ts

export namespace A {
    export class PeanutButterCup { ... }
}

Mod3.ts

export namespace A {
     export class KitKat { ... }
}

Bạn đã tạo thiết lập này: nhập mô tả hình ảnh ở đây

Mỗi mô-đun (tờ giấy) được đặt tên riêng của nóA . Điều này là vô ích - bạn không thực sự tổ chức kẹo của mình ở đây, bạn chỉ cần thêm một bước bổ sung (lấy nó ra khỏi cốc) giữa bạn và các món ăn.


Phiên bản 2: Một cốc trong phạm vi toàn cầu

Nếu bạn không sử dụng các mô-đun, bạn có thể viết mã như thế này (lưu ý thiếu exportkhai báo):

toàn cầu1.ts

namespace A {
    export class Twix { ... }
}

toàn cầu2.ts

namespace A {
    export class PeanutButterCup { ... }
}

toàn cầu3.ts

namespace A {
     export class KitKat { ... }
}

này tạo ra một không gian tên được hợp nhất Atrong phạm vi toàn cầu:

nhập mô tả hình ảnh ở đây

Thiết lập này hữu ích, nhưng không áp dụng trong trường hợp mô-đun (vì mô-đun không gây ô nhiễm phạm vi toàn cầu).


Phiên bản 3: Không ly

Trở lại với ví dụ ban đầu, những chiếc tách A, AAkhông làm bạn bất kỳ ưu đãi. Thay vào đó, bạn có thể viết mã dưới dạng:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

để tạo ra một hình ảnh như thế này:

nhập mô tả hình ảnh ở đây

Tốt hơn nhiều!

Bây giờ, nếu bạn vẫn đang suy nghĩ về mức độ bạn thực sự muốn sử dụng không gian tên với các mô-đun của mình, hãy đọc tiếp ...


Đây không phải là những khái niệm bạn đang tìm kiếm

Chúng ta cần quay trở lại nguồn gốc tại sao không gian tên tồn tại ở nơi đầu tiên và kiểm tra xem những lý do đó có hợp lý với các mô-đun bên ngoài hay không.

Tổ chức : Không gian tên thuận tiện cho việc nhóm các đối tượng và loại liên quan đến logic. Ví dụ: trong C #, bạn sẽ tìm thấy tất cả các loại bộ sưu tập trong System.Collections. Bằng cách tổ chức các loại của chúng tôi thành các không gian tên phân cấp, chúng tôi cung cấp trải nghiệm "khám phá" tốt cho người dùng của các loại đó.

Xung đột tên: Không gian tên là quan trọng để tránh xung đột đặt tên. Ví dụ: bạn có thể có My.Application.Customer.AddFormMy.Application.Order.AddForm- hai loại có cùng tên, nhưng không gian tên khác nhau. Trong một ngôn ngữ mà tất cả các mã định danh tồn tại trong cùng một phạm vi gốc và tất cả các hội đồng tải tất cả các loại, điều quan trọng là phải có mọi thứ trong một không gian tên.

Những lý do đó có ý nghĩa trong các mô-đun bên ngoài?

Tổ chức : Các mô-đun bên ngoài đã có mặt trong một hệ thống tệp, nhất thiết phải có. Chúng tôi phải giải quyết chúng bằng đường dẫn và tên tệp, vì vậy có một sơ đồ tổ chức hợp lý để chúng tôi sử dụng. Chúng ta có thể có một /collections/generic/thư mục với một listmô-đun trong đó.

Xung đột tên : Điều này hoàn toàn không áp dụng trong các mô-đun bên ngoài. Trong một mô-đun, không có lý do chính đáng để có hai đối tượng có cùng tên. Từ phía tiêu thụ, người tiêu dùng của bất kỳ mô-đun cụ thể nào sẽ chọn tên mà họ sẽ sử dụng để đề cập đến mô-đun, vì vậy xung đột đặt tên ngẫu nhiên là không thể.


Ngay cả khi bạn không tin rằng những lý do đó được giải quyết thỏa đáng bằng cách các mô-đun hoạt động, "giải pháp" cố gắng sử dụng không gian tên trong các mô-đun bên ngoài thậm chí không hoạt động.

Hộp trong hộp trong hộp

Một câu chuyện:

Bob bạn của bạn gọi bạn lên. "Tôi có một kế hoạch tổ chức mới tuyệt vời trong nhà của mình", anh nói, "hãy kiểm tra xem!". Không, chúng ta hãy xem những gì Bob đã đưa ra.

Bạn bắt đầu vào bếp và mở tủ đựng thức ăn. Có 60 hộp khác nhau, mỗi hộp được dán nhãn "Phòng đựng thức ăn". Bạn chọn một hộp ngẫu nhiên và mở nó. Bên trong là một hộp duy nhất có nhãn "Ngũ cốc". Bạn mở hộp "Ngũ cốc" và tìm một hộp có nhãn "Pasta". Bạn mở hộp "Pasta" và tìm thấy một hộp có nhãn "Penne". Bạn mở hộp này và tìm thấy, như bạn mong đợi, một túi mì ống penne.

Hơi bối rối, bạn nhặt một hộp liền kề, cũng được dán nhãn "Phòng đựng thức ăn". Bên trong là một hộp duy nhất, một lần nữa được dán nhãn "Ngũ cốc". Bạn mở hộp "Ngũ cốc" và, một lần nữa, tìm một hộp duy nhất có nhãn "Pasta". Bạn mở hộp "Pasta" và tìm thấy một hộp duy nhất, hộp này được dán nhãn "Rigatoni". Bạn mở cái hộp này và tìm ... một túi mì ống cứng.

"Thật tuyệt vời!" Bob nói. "Mọi thứ đều trong một không gian tên!".

"Nhưng Bob ..." bạn trả lời. "Sơ đồ tổ chức của bạn là vô ích. Bạn phải mở ra một loạt các hộp để lấy bất cứ thứ gì, và thực sự không có gì thuận tiện hơn để tìm thấy bất cứ thứ gì hơn là nếu bạn chỉ đặt mọi thứ vào một hộp thay vì ba . Thực tế, vì bạn phòng đựng thức ăn đã được sắp xếp theo từng kệ, bạn hoàn toàn không cần các hộp. Tại sao không đặt mì ống lên kệ và lấy nó khi bạn cần? "

"Bạn không hiểu - Tôi cần đảm bảo rằng không ai khác đặt thứ gì đó không thuộc về không gian tên 'Pantry'. Và tôi đã sắp xếp an toàn tất cả mì ống của mình vào Pantry.Grains.Pastakhông gian tên để tôi có thể dễ dàng tìm thấy nó"

Bob là một người đàn ông rất bối rối.

Các mô-đun là hộp riêng của họ

Bạn có thể đã có một điều tương tự xảy ra trong cuộc sống thực: Bạn đặt mua một vài thứ trên Amazon và mỗi mặt hàng xuất hiện trong hộp riêng của nó, với một hộp nhỏ hơn bên trong, với mặt hàng của bạn được bọc trong bao bì riêng. Ngay cả khi các hộp bên trong tương tự nhau, các lô hàng không được "kết hợp" một cách hữu ích.

Đi cùng với sự tương tự hộp, quan sát chính là các mô-đun bên ngoài là hộp riêng của chúng . Nó có thể là một mục rất phức tạp với nhiều chức năng, nhưng bất kỳ mô-đun bên ngoài nào cũng là hộp riêng của nó.


Hướng dẫn cho các mô-đun bên ngoài

Bây giờ chúng tôi đã nhận ra rằng chúng tôi không cần phải sử dụng 'không gian tên', chúng tôi nên tổ chức các mô-đun như thế nào? Một số nguyên tắc hướng dẫn và ví dụ sau đây.

Xuất càng gần cấp cao nhất càng tốt

  • Nếu bạn chỉ xuất một lớp hoặc hàm duy nhất, hãy sử dụng export default:

MyClass.ts

export default class SomeType {
  constructor() { ... }
}

MyFunc.ts

function getThing() { return 'thing'; }
export default getThing;

Tiêu dùng

import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());

Điều này là tối ưu cho người tiêu dùng. Họ có thể đặt tên cho loại của bạn bất cứ điều gì họ muốn ( ttrong trường hợp này) và không phải thực hiện bất kỳ việc rải rác ngoại lai nào để tìm đối tượng của bạn.

  • Nếu bạn đang xuất nhiều đối tượng, hãy đặt tất cả chúng ở cấp cao nhất:

MyThings.ts

export class SomeType { ... }
export function someFunc() { ... }

Tiêu dùng

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Nếu bạn đang xuất một số lượng lớn các thứ, chỉ sau đó bạn nên sử dụng module/ namespacetừ khóa:

MyLargeModule.ts

export namespace Animals {
  export class Dog { ... }
  export class Cat { ... }
}
export namespace Plants {
  export class Tree { ... }
}

Tiêu dùng

import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();

Cờ đỏ

Tất cả những điều sau đây là cờ đỏ cho cấu trúc mô-đun. Kiểm tra kỹ xem bạn không cố gắng đặt tên cho các mô-đun bên ngoài nếu bất kỳ điều nào trong số này áp dụng cho các tệp của bạn:

  • Một tệp chỉ có khai báo cấp cao nhất là export module Foo { ... }(xóa Foovà di chuyển mọi thứ 'lên' một cấp)
  • Một tập tin có một export classhoặc export functionkhôngexport default
  • Nhiều tệp có cùng export module Foo {mức ở cấp cao nhất (đừng nghĩ rằng những tệp này sẽ kết hợp thành một Foo!)

80
Đây là một câu trả lời không. Tiền đề mà bạn không cần hoặc muốn không gian tên cho các mô-đun bên ngoài là một lỗi. Trong khi hệ thống tập tin là một loại sơ đồ tổ chức, bạn có thể kinda sử dụng cho những mục đích này, nó không phải là gần như tốt đẹp cho người tiêu dùng để có n báo cáo nhập khẩu đối với sử dụng các lớp n hoặc các chức năng từ một dự án nhất định; đặc biệt là vì nó cũng làm vấy bẩn quy ước đặt tên khi bạn không sử dụng mã thực tế.
Albinofblery 12/08/2015

12
Cho dù người ta có thể muốn nó đến mức nào, nó vẫn không thể .
Ryan Cavanaugh

26
Tôi không hiểu, chúng tôi sẽ không viết pascal nữa. Từ khi nào tổ chức sử dụng hệ thống tập tin là cách để đi?
David

9
Bạn có thể bằng cách có một mô-đun "trình bao bọc" để nhập và tái xuất mọi thứ quan tâm cho người tiêu dùng của thư viện của bạn. Nhưng một lần nữa, sử dụng "không gian tên" sẽ không cung cấp bất kỳ giá trị nào ngoài việc buộc một mức độ không xác định khác cho bất kỳ ai sử dụng mã của bạn.
Ryan Cavanaugh

13
Tuyệt vời viết lên, cảm ơn bạn. Tôi cảm thấy như bạn nên liên kết đến điều này từ www.typescriptlang.org/docs/handbook/namespaces.html. Tôi đã phải đọc rằng typeclang.org liên kết 3 hoặc 4 lần và là một nhà phát triển C #, tôi tự nhiên muốn đặt mọi thứ trong một không gian tên. Tôi đã đọc một số gợi ý nói không, nhưng không có lời giải thích tại sao và không có gì rõ ràng (và được mô tả tốt) như thế này. Không có gì trong tài liệu bản thảo đề cập đến AFAIK này
Adam Plocher

53

Không có gì sai với câu trả lời của Ryan, nhưng với những người đến đây tìm cách duy trì cấu trúc một lớp cho mỗi tệp trong khi vẫn sử dụng không gian tên ES6 một cách chính xác, vui lòng tham khảo tài nguyên hữu ích này từ Microsoft.

Một điều đó là không rõ ràng với tôi sau khi đọc doc là: làm thế nào để nhập khẩu toàn bộ (sáp nhập) mô-đun với một single import .

Chỉnh sửa Circling trở lại để cập nhật câu trả lời này. Một vài cách tiếp cận để đặt tên xuất hiện trong TS.

Tất cả các lớp mô-đun trong một tập tin.

export namespace Shapes {
    export class Triangle {}
    export class Square {}      
}

Nhập tệp vào không gian tên và gán lại

import { Triangle as _Triangle } from './triangle';
import { Square as _Square } from './square';

export namespace Shapes {
  export const Triangle = _Triangle;
  export const Square = _Square;
}

Thùng

// ./shapes/index.ts
export { Triangle } from './triangle';
export { Square } from './square';

// in importing file:
import * as Shapes from './shapes/index.ts';
// by node module convention, you can ignore '/index.ts':
import * as Shapes from './shapes';
let myTriangle = new Shapes.Triangle();

Một sự xem xét cuối cùng. Bạn có thể đặt tên cho mỗi tệp

// triangle.ts
export namespace Shapes {
    export class Triangle {}
}

// square.ts
export namespace Shapes {
    export class Square {}
}

Nhưng khi nhập hai lớp từ cùng một không gian tên, TS sẽ khiếu nại có một định danh trùng lặp. Giải pháp duy nhất vì lần này là bí danh không gian tên.

import { Shapes } from './square';
import { Shapes as _Shapes } from './triangle';

// ugh
let myTriangle = new _Shapes.Shapes.Triangle();

Bí danh này hoàn toàn đáng ghê tởm, vì vậy đừng làm điều đó. Bạn tốt hơn với một cách tiếp cận ở trên. Cá nhân, tôi thích 'thùng'.


6
"Không gian tên ES6" là gì?
Aluan Haddad

@AluanHaddad khi nhập es2015 +, những thứ được nhập là mặc định, bị hủy hoặc được đặt tên. const fs = require('fs'), fslà không gian tên. import * as moment from 'moment', momentlà không gian tên. Đây là bản thể học, không phải là đặc điểm kỹ thuật.
Jefftopia

Tôi biết điều đó nhưng bạn sẽ làm tốt để giải thích nó trong câu trả lời của bạn. Tuy nhiên, không gian tên ES6 thực sự là một thứ, và requireví dụ này không áp dụng cho chúng vì một số lý do, bao gồm cả không gian tên ES6 có thể không được gọi, trong khi requiretrả về một đối tượng đơn giản có thể gọi được.
Aluan Haddad

1
Tôi không làm theo, bởi vì dù thứ được nhập có thể gọi được hay không thì nó vẫn đóng vai trò là không gian tên nói một cách logic . Tôi không nghĩ rằng hãy cẩn thận với câu trả lời của tôi ở trên.
Jefftopia

7

Cố gắng tổ chức theo thư mục:

baseTypes.ts

export class Animal {
    move() { /* ... */ }
}

export class Plant {
    photosynthesize() { /* ... */ }
}

chó

import b = require('./baseTypes');

export class Dog extends b.Animal {
    woof() { }
}   

cây.ts

import b = require('./baseTypes');

class Tree extends b.Plant {
}

LivingThings.ts

import dog = require('./dog')
import tree = require('./tree')

export = {
    dog: dog,
    tree: tree
}

chính

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)

Ý tưởng là bản thân mô-đun của bạn không nên quan tâm / biết rằng họ đang tham gia vào một không gian tên, nhưng điều này làm lộ API của bạn tới người tiêu dùng theo cách gọn nhẹ, hợp lý, không tin vào loại hệ thống mô-đun nào bạn đang sử dụng cho dự án.


8
LivingThings.dog.Dog là những gì bạn có ở đây.
Corey Alix

Tôi khuyên bạn nên giữ trường hợp chữ cái nhất quán, nếu bạn xuất "Cây", sau đó nhập "Cây", không phải "cây".
demisx

1
Ngoài ra, làm thế nào bạn có thể nhập bất cứ thứ gì từ tree.tskhi nó không có thành viên xuất khẩu nào cả?
demisx

Man TS chắc chắn có một số cú pháp cũ ngớ ngẩn, thích importrequirecùng nhau trong một tuyên bố.
Andy

3

Câu hỏi nhỏ về câu trả lời của Albinofblery:

cơ sở

export class Animal {
move() { /* ... */ }
}

export class Plant {
  photosynthesize() { /* ... */ }
}

chó

import * as b from './base';

export class Dog extends b.Animal {
   woof() { }
} 

điều

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

chính

import * as things from './things';

console.log(things.dog);

2
Cảm ơn vì điều đó! Chỉ muốn nói rằng những thay đổi cho câu trả lời hiện tại tốt nhất không nên được đăng dưới dạng câu trả lời mới: chúng nên được thêm vào dưới dạng một nhận xét cho câu trả lời hiện có, hoặc (tốt hơn) nên được đề xuất bằng cách đề xuất chỉnh sửa cho câu trả lời mà bạn muốn cải tiến.
a3nm

3

OP tôi với bạn người đàn ông. một lần nữa, không có gì sai với câu trả lời với hơn 300 phiếu bầu, nhưng ý kiến ​​của tôi là:

  1. Có gì sai khi đặt các lớp vào các tệp riêng ấm cúng ấm cúng của họ? Ý tôi là điều này sẽ làm mọi thứ tốt hơn nhiều phải không? (hoặc ai đó giống như một tệp 1000 dòng cho tất cả các mô hình)

  2. Vì vậy, nếu đạt được cái đầu tiên, chúng ta phải nhập nhập nhập ... nhập chỉ trong mỗi tệp mô hình như man, srsly, tệp mô hình, tệp .d.ts, tại sao có quá nhiều * Có ở đó không? nó chỉ nên đơn giản, gọn gàng, và đó là nó. Tại sao tôi cần nhập khẩu ở đó? tại sao? C # có không gian tên cho một lý do.

  3. Và sau đó, bạn thực sự sử dụng "filenames.ts" làm định danh. Là định danh ... Hãy đến năm 2017 của nó bây giờ và chúng tôi vẫn làm điều đó? Ima trở lại Sao Hỏa và ngủ thêm 1000 năm nữa.

Thật đáng buồn, câu trả lời của tôi là: không, bạn không thể làm cho "không gian tên" hoạt động nếu bạn không sử dụng tất cả các nhập đó hoặc sử dụng các tên tệp đó làm định danh (mà tôi nghĩ là thực sự ngớ ngẩn). Một lựa chọn khác là: đặt tất cả các phụ thuộc đó vào một hộp có tên là filenameasidentifier.ts và sử dụng

export namespace(or module) boxInBox {} .

bọc chúng để chúng không cố gắng truy cập các lớp khác có cùng tên khi chúng chỉ đơn giản là cố gắng để có được một tham chiếu từ lớp ngồi ngay trên đầu chúng.


3

Một số câu hỏi / nhận xét tôi đã thấy xung quanh chủ đề này nghe có vẻ như thể người đó đang sử dụng Namespaceý nghĩa của chúng là 'bí danh mô-đun'. Như Ryan Cavanaugh đã đề cập trong một trong những bình luận của mình, bạn có thể có mô-đun 'Wrapper' tái xuất một số mô-đun.

Nếu bạn thực sự muốn nhập tất cả từ cùng một tên / bí danh mô-đun, hãy kết hợp mô-đun trình bao bọc với ánh xạ đường dẫn trong tsconfig.json.

Thí dụ:

./path/to/CompanyName.Products/Foo.ts

export class Foo {
    ...
}


./path/to/CompanyName.Products/Bar.ts

export class Bar {
    ...
}


./path/to/CompanyName.Products/index.ts

export { Foo } from './Foo';
export { Bar } from './Bar';



tsconfig.json

{
    "compilerOptions": {
        ...
        paths: {
            ...
            "CompanyName.Products": ["./path/to/CompanyName.Products/index"],
            ...
        }
        ...
    }
    ...
}



main.ts

import { Foo, Bar } from 'CompanyName.Products'

Lưu ý : Độ phân giải mô-đun trong các tệp .js đầu ra sẽ cần được xử lý bằng cách nào đó, chẳng hạn như với https://github.com/ussyunen/babel-plugin-module-resolver

Ví dụ .babelrcđể xử lý độ phân giải bí danh:

{
    "plugins": [
        [ "module-resolver", {
            "cwd": "babelrc",
            "alias": {
                "CompanyName.Products": "./path/to/typescript/build/output/CompanyName.Products/index.js"
            }
        }],
        ... other plugins ...
    ]
}

1

Hãy thử mô-đun không gian tên này

không gian tênModuleFile.ts

export namespace Bookname{
export class Snows{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
export class Adventure{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
}





export namespace TreeList{
export class MangoTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
export class GuvavaTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
}

bookTreeCombine.ts

--- phần tổng hợp ---

import {Bookname , TreeList} from './namespaceModule';
import b = require('./namespaceModule');
let BooknameLists = new Bookname.Adventure('Pirate treasure');
BooknameLists = new Bookname.Snows('ways to write a book'); 
const TreeLis = new TreeList.MangoTree('trees present in nature');
const TreeLists = new TreeList.GuvavaTree('trees are the celebraties');

0

chó

import b = require('./baseTypes');

export module Living.Things {
    // Error, can't find name 'Animal', ??
    // Solved: can find, if properly referenced; exporting modules is useless, anyhow
    export class Dog extends b.Living.Things.Animal {
        public woof(): void {
            return;
        }
    }
}

cây.ts

// Error, can't use the same name twice, ??
// Solved: cannot declare let or const variable twice in same scope either: just use a different name
import b = require('./baseTypes');
import d = require('./dog');

module Living.Things {
    // Why do I have to write b.Living.Things.Plant instead of b.Plant??
    class Tree extends b.Living.Things.Plant {
    }
}

-1

Cách thích hợp để tổ chức mã của bạn là sử dụng các thư mục riêng thay cho không gian tên. Mỗi lớp sẽ nằm trong tệp riêng của nó, trong thư mục không gian tên tương ứng. index.ts sẽ chỉ xuất lại mỗi tệp; không có mã thực tế nào trong tệp index.ts. Tổ chức mã của bạn như thế này giúp điều hướng dễ dàng hơn và tự ghi lại tài liệu dựa trên cấu trúc thư mục.

// index.ts
import * as greeter from './greeter';
import * as somethingElse from './somethingElse';

export {greeter, somethingElse};

// greeter/index.ts
export * from './greetings.js';
...

// greeter/greetings.ts
export const helloWorld = "Hello World";

Sau đó, bạn sẽ sử dụng nó như vậy:

import { greeter } from 'your-package'; //Import it like normal, be it from an NPM module or from a directory.
// You can also use the following syntax, if you prefer:
import * as package from 'your-package';

console.log(greeter.helloWorld);

Điều này là sai lệch và hoàn toàn không chính xác. Đó không phải là cách các không gian tên làm việc. Ngoài ra nó không trả lời câu hỏi ops.
AndrewMcLagan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.