<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Angular: 最佳實(shí)踐

    共 9644字,需瀏覽 20分鐘

     ·

    2022-08-08 14:46

    Note: 本文中,我將盡量避免官方在 Angular Style Guide 提及的模式和有用的實(shí)踐,而是專注我自己的經(jīng)驗(yàn)得出的東西,我將用例子來(lái)說(shuō)明。如果你還沒(méi)讀過(guò)官網(wǎng)指引,我建議你在閱讀本文之前讀一下。因?yàn)楣倬W(wǎng)涵蓋了本文很多沒(méi)介紹的東西。

    本文將分為幾個(gè)章節(jié)來(lái)講解,這些章節(jié)根據(jù)應(yīng)用核心需求和生命周期來(lái)拆分。現(xiàn)在,我們開始吧!

    類型規(guī)范 Typing

    我們主要是用 TypeScript 去編寫 Angular(也許你只是用 JavaScript 或者谷歌的 Dart 語(yǔ)言去寫),Angular 被稱為 TYPEScript 也是有原因的。我們應(yīng)該為我們數(shù)據(jù)添加類型限定,下面有些有用的知識(shí)點(diǎn):

    使用類型聯(lián)合和交集。官網(wǎng)解釋了如何使用 TS 編譯器組合類型以輕松工作。這在處理來(lái)自 RESTful API 數(shù)據(jù)的時(shí)非常有用。如下例子:

    interface User {
    fullname: string,
    age: number,
    createDate: string | Date
    }

    上面 createdDate 字段的類型不是 JS Date 就是字符串。這很有用,因?yàn)楫?dāng)服務(wù)端提供一個(gè) User 實(shí)例數(shù)據(jù)給你,它只能返回字符串類型的時(shí)間給你,但是你可能有一個(gè) datepicker 控件,它將日期作為有效的 JS Date 對(duì)象返回,并且為了避免數(shù)據(jù)被誤解,我們需要在 interface 里面可選指明。

    限制你的類型。在 TypeScript 中,你可以限制字段的值或者變量的值,比如:

    interface Order {
    status: 'pending' | 'approved' | 'rejected'
    }

    這實(shí)際上變成了一個(gè)標(biāo)志。如果我們有一個(gè) Order 類型的變量,我們只能將這三個(gè)字符串中的一個(gè)分配給 status 字段,分配其他的類型 TS 編輯器都會(huì)跑出錯(cuò)誤。

    enum Statuses {
    Pending = 1,
    Approved = 2,
    Rejected = 3
    }

    interface Order {
    status: Statuses;
    }

    **考慮設(shè)置 noImplicitAny: true**。在應(yīng)用程序的 tsconfig.json 文件中,我們可以設(shè)置這個(gè)標(biāo)志,告訴編輯器在未明確類型時(shí)候拋出錯(cuò)誤。否則,編輯器堅(jiān)定它無(wú)法推斷變量的類型,而認(rèn)為是 any 類型。實(shí)際情況并非如此,盡管將該標(biāo)志設(shè)置為 true 會(huì)導(dǎo)致發(fā)生意想不到的復(fù)雜情況,當(dāng)會(huì)讓你的代碼管理得很好。

    嚴(yán)格類型的代碼不容易出錯(cuò),而 TS 剛好提供了類型限制,那么我們得好好使用它。

    組件 Component

    組件是 Angular 的核心特性,如果你設(shè)法讓它們被組織得井井有條,你可以認(rèn)為你工作已經(jīng)完成了一半。

    考慮擁有一個(gè)或者幾個(gè)基本組件類。如果你有很多重復(fù)使用的內(nèi)容,這將很好用,我們可不想講相同的代碼編寫多次吧。假設(shè)有這么一個(gè)場(chǎng)景:我們有幾個(gè)頁(yè)面,都要展示系統(tǒng)通知。每個(gè)通知都有已讀/未讀兩種狀態(tài),當(dāng)然,我們已經(jīng)枚舉了這兩種狀態(tài)。并且在模版中的每個(gè)地方都會(huì)顯示通知,你可以使用 ngClass 設(shè)置未通知的樣式?,F(xiàn)在,我們想將通知的狀態(tài)與枚舉值進(jìn)行比較,我們必須將枚舉導(dǎo)入組件。

    enum Statuses {
    Unread = 0,
    Read = 1
    }

    @Component({
    selector: 'component-with-enum',
    template: `
    <div *ngFor="notification in notifications"
    [ngClass]="{'unread': notification.status == statuses.Unread}">
    {{ notification.text }}
    </div>
    `

    })
    class NotificationComponent {
    notifications = [
    {text: 'Hello!', status: Statuses.Unread},
    {text: 'Angular is awesome!', status: Statuses.Read}
    ];
    statuses = Statuses
    }

    這里,我們?yōu)槊總€(gè)包含未讀通知的 HTML 元素添加了 unread 類。注意我們是怎么在組件類上創(chuàng)建一個(gè) statuses 字段,以便我們可以在模版中使用這個(gè)枚舉。但是假如我們?cè)诙鄠€(gè)組件中使用這個(gè)枚舉呢?或者假如我們要在不同的組件使用其他枚舉呢?我們需要不停創(chuàng)建這些字段?這似乎很多重復(fù)代碼。我們看看下面例子:

    enum Statuses {
    Unread = 0,
    Read = 1
    }

    abstract class AbstractBaseComponent {
    statuses = Statuses;
    someOtherEnum = SomeOtherEnum;
    ... // lots of other reused stuff
    }

    @Component({
    selector: 'component-with-enum',
    template: `
    <div *ngFor="notification in notifications"
    [ngClass]="{'unread': notification.status == statuses.Unread}">
    {{ notification.text }}
    </div>
    `

    })
    class NotificationComponent extends AbstractBaseComponent {
    notifications = [
    {text: 'Hello!', status: Statuses.Unread},
    {text: 'Angular is awesome!', status: Statuses.Read}
    ];
    }

    所以,現(xiàn)在我們有一個(gè)基本組件(實(shí)際上就是一個(gè)容器),我們的組件可以從中派生以重用應(yīng)用程序的全局值和方法。

    另一種情況經(jīng)常在 forms 表單中被發(fā)現(xiàn)。如果在你的 Angular 組件中有個(gè)表單,你可能有像這樣的字段或者方法:

    @Component({
    selector: 'component-with-form',
    template: `...omitted for the sake of brevity`
    })
    class ComponentWithForm extends AbstractBaseComponent {
    form: FormGroup;
    submitted: boolean = false; // a flag to be used in template to indicate whether the user tried to submit the form

    resetForm() {
    this.form.reset();
    }

    onSubmit() {
    this.submitted = true;
    if (!this.form.valid) {
    return;
    }
    // perform the actual submit logic
    }
    }

    當(dāng)然,如果你正在大量組件中使用 Angular 表單,那么將這些邏輯移動(dòng)到一個(gè)基礎(chǔ)類會(huì)更友好...但是你不需要繼承 AbstractBaseComponent,因?yàn)椴皇敲總€(gè)組件都有 form 表單。像下面這樣做比較好:

    abstract class AbstractFormComponent extends AbstractBaseComponent {
    form: FormGroup;
    submitted: boolean = false; // a flag to be used in template to indicate whether the user tried to submit the form

    resetForm() {
    this.form.reset();
    }

    onSubmit() {
    this.submitted = true;
    if (!this.form.valid) {
    return;
    }
    }
    }

    @Component({
    selector: 'component-with-form',
    template: `...omitted for the sake of brevity`
    })
    class ComponentWithForm extends AbstractFormComponent {

    onSubmit() {
    super.onSubmit();
    // continue and perform the actual logic
    }

    }

    現(xiàn)在,我們?yōu)槭褂帽韱蔚慕M件創(chuàng)建了一個(gè)單獨(dú)的類(注意:AbstractFormComponent 是如何繼承 AbstractBaseComponent ,因此我們不會(huì)丟失應(yīng)用程序的值)。這是一個(gè)不錯(cuò)的示范,我們可以在真正需要的地方廣泛使用它。

    容器組件。 這可能有些爭(zhēng)議,但是我們?nèi)匀豢梢钥紤]它是否適合我們。我們知道一個(gè)路由對(duì)應(yīng)一個(gè) Angular 組件,但是我推薦你使用容器組件,它將處理數(shù)據(jù)(如果有數(shù)據(jù)需要傳遞的話)并將數(shù)據(jù)傳遞給另外一個(gè)組件,該組件將使用輸入所包含的真實(shí)視圖和 UI 邏輯。下面就是一個(gè)例子:

    const routes: Routes = [
    {path: 'user', component: UserContainerComponent}
    ];



    @Component({
    selector: 'user-container-component',
    template: `<app-user-component [user]="user"></app-user-component>`
    })
    class UserContainerComponent {

    constructor(userService: UserService) {}
    ngOnInit(){
    this.userService.getUser().subscribe(res => this.user = user);
    /* get the user data only to pass it down to the actual view */
    }

    }

    @Component({
    selector: 'app-user-component',
    template: `...displays the user info and some controls maybe`
    })
    class UserComponent {
    @Input() user;
    }

    在這里,容器執(zhí)行數(shù)據(jù)的檢索(它也可能執(zhí)行一些其他常見的任務(wù))并將實(shí)際的工作委托給另外一個(gè)組件。當(dāng)你重復(fù)使用同一份 UI 并再次使用現(xiàn)有的數(shù)據(jù)時(shí),這可能派上用場(chǎng),并且是關(guān)注點(diǎn)分離的一個(gè)很好的例子。

    小經(jīng)驗(yàn):當(dāng)我們?cè)趲в凶釉氐?HTML 元素上編寫 ngFor 指令時(shí),請(qǐng)考慮將該元素分離為單獨(dú)的組件,就像下面:

    <-- instead of this -->
    <div *ngFor="let user of users">
    <h3 class="user_wrapper">{{user.name}}</h3>
    <span class="user_info">{{ user.age }}</
    span>
    <span class="user_info">{{ user.dateOfBirth | date : 'YYYY-MM-DD' }}</span>
    </
    div>

    <-- write this: -->

    <user-detail-component *ngFor="let user of users" [user]="user"></user-detail-component>

    這在父組件中寫更少的代碼,讓后允許委托任何重復(fù)邏輯到子組件。

    服務(wù) Services

    服務(wù)是 Angular 中業(yè)務(wù)邏輯存放和數(shù)據(jù)處理的方案。擁有提供數(shù)據(jù)訪問(wèn)、數(shù)據(jù)操作和其他可重用邏輯的結(jié)構(gòu)良好的服務(wù)非常重要。所以,下面有幾條規(guī)則需要考慮下:

    有一個(gè) API 調(diào)用的基礎(chǔ)服務(wù)類。將簡(jiǎn)單的 HTTP 服務(wù)邏輯放在基類中,并從中派生 API 服務(wù)。像下面這樣:

    abstract class RestService {

    protected baseUrl: 'http://your.api.domain';

    constructor(private http: Http, private cookieService: CookieService){}

    protected get headers(): Headers {
    /*
    * for example, add an authorization token to each request,
    * take it from some CookieService, for example
    * */

    const token: string = this.cookieService.get('token');
    return new Headers({token: token});
    }

    protected get(relativeUrl: string): Observable<any> {
    return this.http.get(this.baseUrl + relativeUrl, new RequestOptions({headers: this.headers}))
    .map(res => res.json());
    // as you see, the simple toJson mapping logic also delegates here
    }

    protected post(relativeUrl: string, data: any) {
    // and so on for every http method that your API supports
    }

    }

    當(dāng)然,你可以寫得更加復(fù)雜,當(dāng)用法要像下面這么簡(jiǎn)單:

    @Injectable()
    class UserService extends RestService {

    private relativeUrl: string = '/users/';

    public getAllUsers(): Observable<User[]> {
    return this.get(this.relativeUrl);
    }

    public getUserById(id: number): Observable<User> {
    return this.get(`${this.relativeUrl}${id.toString()}`);
    }

    }

    現(xiàn)在,你只需要將 API 調(diào)用的邏輯抽象到基類中,現(xiàn)在就可以專注于你將接收哪些數(shù)據(jù)以及如何處理它。

    考慮有方法(Utilites)服務(wù)。有時(shí)候,你會(huì)發(fā)現(xiàn)你的組件上有一些方法用于處理一些數(shù)據(jù),可能會(huì)對(duì)其進(jìn)行預(yù)處理或者以某種方式進(jìn)行處理。示例可能很多,比如,你的一個(gè)組件中可能具有上傳文件的功能,因此你需要將 JS File 對(duì)象的 Array 轉(zhuǎn)換為 FormData 實(shí)例來(lái)執(zhí)行上傳?,F(xiàn)在,這些沒(méi)有涉及到邏輯,不會(huì)以任何的方式影響你的視圖,并且你的多個(gè)組件中都包含上傳文件功能,因此,我們要考慮創(chuàng)建 Utilities 方法或者 DataHelper 服務(wù)將此類功能移到那里。

    使用 TypeScript 字符串枚舉規(guī)范 API url。你的應(yīng)用程序可以和不同的 API 端進(jìn)行交互,因此我們希望將他們移動(dòng)到字符串枚舉中,而不是在硬編碼中體現(xiàn),如下:

    enum UserApiUrls {
    getAllUsers = 'users/getAll',
    getActiveUsers = 'users/getActive',
    deleteUser = 'users/delete'
    }

    這能更好得了解你的 API 是怎么運(yùn)作的。

    盡可能考慮緩存我們的請(qǐng)求。Rx.js 允許你去緩存 HTTP 請(qǐng)求的結(jié)果(實(shí)際上,任何的 Observable 都可以,但是我們現(xiàn)在說(shuō)的是 HTTP 這內(nèi)容),并且有一些示例你可能想要使用它。比如,你的 API 提供了一個(gè)接入點(diǎn),返回一個(gè) Country 對(duì)象 JSON 對(duì)象,你可以在應(yīng)用程序使用這列表數(shù)據(jù)實(shí)現(xiàn)選擇國(guó)家/地區(qū)的功能。當(dāng)然,國(guó)家不會(huì)每天都會(huì)發(fā)生變更,所以最好的做法就是拉取該數(shù)據(jù)并緩存,然后在應(yīng)用程序的生命周期內(nèi)使用緩存的版本,而不是每次都去調(diào)用 API 請(qǐng)求該數(shù)據(jù)。Observables 使得這變得很容易:

    class CountryService {

    constructor(private http: Http) {}

    private countries: Observable<Country[]> = this.http.get('/api/countries')
    .map(res => res.json())
    .publishReplay(1) // this tells Rx to cache the latest emitted value
    .refCount(); // and this tells Rx to keep the Observable alive as long as there are any Subscribers

    public getCountries(): Observable<Country[]> {
    return this.countries;
    }

    }

    所以現(xiàn)在,不管什么時(shí)候你訂閱這個(gè)國(guó)家列表,結(jié)果都會(huì)被緩存,以后你不再需要發(fā)起另一個(gè) HTTP 請(qǐng)求了。

    模版 Templates

    Angular 是使用 html 模版(當(dāng)然,還有組件、指令和管道)去渲染你應(yīng)用程序中的視圖 ,所以編寫模版是不可避免的事情,并且要保持模版的整潔和易于理解是很重要的。

    從模版到組件方法的委托比原始的邏輯更難。請(qǐng)注意,這里我用了比原始更難的詞語(yǔ),而不是復(fù)雜這個(gè)詞。這是因?yàn)槌藱z查直接的條件語(yǔ)句之外,任何邏輯都應(yīng)該寫在組件的類方法中,而不是寫在模版中。在模版中寫 *ngIf=”someVariable === 1” 是可以的,其他很長(zhǎng)的判斷條件就不應(yīng)該出現(xiàn)在模版中。

    比如,你想在模版中為未正確填寫表單控件添加 has-error 類(也就是說(shuō)并非所有的校驗(yàn)都通過(guò))。你可以這樣做:

    @Component({
    selector: 'component-with-form',
    template: `
    <div [formGroup]="form"
    [ngClass]="{
    'has-error': (form.controls['firstName'].invalid && (submitted || form.controls['firstName'].touched))
    }">
    <input type="text" formControlName="firstName"/>
    </div>
    `

    })
    class SomeComponentWithForm {
    form: FormGroup;
    submitted: boolean = false;

    constructor(private formBuilder: FormBuilder) {
    this.form = formBuilder.group({
    firstName: ['', Validators.required],
    lastName: ['', Validators.required]
    });
    }


    }

    上面 ngClass 聲明看起來(lái)很丑。如果我們有更多的表單控件,那么它會(huì)使得視圖更加混亂,并且創(chuàng)建了很多重復(fù)的邏輯。但是,我們也可以這樣做:

    @Component({
    selector: 'component-with-form',
    template: `
    <div [formGroup]="form" [ngClass]="{'has-error': hasFieldError('firstName')}">
    <input type="text" formControlName="firstName"/>
    </div>
    `

    })
    class SomeComponentWithForm {
    form: FormGroup;
    submitted: boolean = false;

    constructor(private formBuilder: FormBuilder) {
    this.form = formBuilder.group({
    firstName: ['', Validators.required],
    lastName: ['', Validators.required]
    });
    }

    hasFieldError(fieldName: string): boolean {
    return this.form.controls[fieldName].invalid && (this.submitted || this.form.controls[fieldName].touched);
    }


    }

    現(xiàn)在,我們有了個(gè)不錯(cuò)的模版,甚至可以輕松地測(cè)試我們的驗(yàn)證是否與單元測(cè)試一起正常工作,而無(wú)需深入查看視圖。

    讀者可能意識(shí)到我并沒(méi)有寫關(guān)于 DirectivesPipes 的相關(guān)內(nèi)容,那是因?yàn)槲蚁雽懫敿?xì)的文章,關(guān)于 AngularDOM 是怎么工作的。所以本文著重介紹 Angular 應(yīng)用中的 TypeScript 的內(nèi)容。

    希望本文能夠幫助你編寫更干凈的代碼,幫你更好組織你的應(yīng)用結(jié)構(gòu)。請(qǐng)記住,無(wú)論你做了什么決定,請(qǐng)保持前后一致(別鉆牛角尖...)。

    本文是譯文,采用的是意譯的方式,其中加上個(gè)人的理解和注釋,原文地址是:https://medium.com/codeburst/angular-best-practices-4bed7ae1d0b7

    往期精彩推薦

    • Dart 知識(shí)點(diǎn) - 數(shù)據(jù)類型
    • Flutter 開發(fā)出現(xiàn)的那些 Bugs 和解決方案「持續(xù)更新... 」

    如果讀者覺(jué)得文章還可以,不防一鍵三連:關(guān)注?點(diǎn)贊?收藏


    瀏覽 51
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    欧美色图第一页 | 性爱综合网| 爱视频国产在线 | 亚洲AV成人无码久久精品麻豆 | 黄色大片免费网站 |