Angular - * ng If vs chức năng gọi đơn giản trong mẫu


14

Xin lỗi nếu điều này đã được trả lời ở đây, nhưng tôi không thể tìm thấy bất kỳ trận đấu nào cho kịch bản cụ thể của chúng tôi, vì vậy hãy đến đây!

Chúng tôi đã có một cuộc thảo luận trong nhóm phát triển của chúng tôi, liên quan đến các lệnh gọi hàm trong các mẫu góc. Bây giờ là một quy tắc chung, chúng tôi đồng ý rằng bạn không nên làm những điều này. Tuy nhiên, chúng tôi đã cố gắng thảo luận khi nào có thể ổn. Hãy để tôi cung cấp cho bạn một kịch bản.

Giả sử chúng ta có một khối mẫu được bọc trong ngNếu, kiểm tra nhiều tham số, như ở đây:

<ng-template *ngIf="user && user.name && isAuthorized">
 ...
</ng-template>

Sẽ có một sự khác biệt đáng kể trong hiệu suất so với một cái gì đó như thế này:

Bản mẫu:

<ng-template *ngIf="userCheck()">
 ...
</ng-template>

Bản thảo:

userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

Vì vậy, để tóm tắt câu hỏi, liệu lựa chọn cuối cùng có chi phí hiệu suất đáng kể nào không?

Chúng tôi muốn sử dụng cách tiếp cận thứ 2, trong các tình huống cần kiểm tra nhiều hơn 2 điều kiện, nhưng nhiều bài viết trực tuyến nói rằng các lệnh gọi hàm LUÔN là xấu trong các mẫu, nhưng nó có thực sự là vấn đề trong trường hợp này không?


7
Không, nó sẽ không. Và nó cũng sạch hơn, vì nó làm cho mẫu dễ đọc hơn, điều kiện dễ kiểm tra và tái sử dụng hơn, và bạn có nhiều công cụ theo ý của bạn (toàn bộ ngôn ngữ TypeScript) để làm cho nó dễ đọc và hiệu quả nhất có thể. Tuy nhiên, tôi sẽ chọn một tên rõ ràng hơn nhiều so với "userCheck".
JB Nizet

Cảm ơn rất nhiều về đầu vào của bạn :)
Jesper

Câu trả lời:


8

Tôi cũng đã cố gắng tránh các hàm gọi trong các mẫu càng nhiều càng tốt, nhưng câu hỏi của bạn đã thôi thúc tôi thực hiện một nghiên cứu nhanh:

Tôi đã thêm một trường hợp khác với userCheck()kết quả bộ nhớ đệm

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

Chuẩn bị một bản demo tại đây: https://stackblitz.com/edit/angular-9qgsm9

Đáng ngạc nhiên là có vẻ như không có sự khác biệt giữa

*ngIf="user && user.name && isAuthorized"

*ngIf="userCheck()"

...
// .ts
userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

Điều này có vẻ như hợp lệ cho việc kiểm tra thuộc tính đơn giản, nhưng chắc chắn sẽ có một sự khác biệt nếu nói đến bất kỳ asynchành động, getters đang chờ đợi một số api chẳng hạn.


10

Đây là một câu trả lời khá ý kiến.

Việc sử dụng các chức năng như thế này, là hoàn toàn chấp nhận được. Nó sẽ làm cho các mẫu rõ ràng hơn nhiều, và nó không gây ra bất kỳ chi phí đáng kể nào. Giống như JB đã nói trước đây, nó cũng sẽ thiết lập một cơ sở tốt hơn để thử nghiệm đơn vị.

Tôi cũng nghĩ rằng bất kỳ biểu thức nào bạn có trong mẫu của bạn, sẽ được đánh giá là một hàm theo cơ chế phát hiện thay đổi, do đó, không có vấn đề gì nếu bạn có nó trong mẫu hoặc trong logic thành phần của bạn.

Chỉ cần giữ logic bên trong chức năng ở mức tối thiểu. Nếu bạn tuy nhiên lo lắng về bất kỳ tác động hiệu suất một chức năng như vậy có thể có, tôi khuyên bạn để đưa bạn ChangeDetectionStrategyđến OnPush, được coi là tốt nhất anyways. Với điều này, hàm sẽ không được gọi mỗi chu kỳ, chỉ khi có Inputthay đổi, một số sự kiện xảy ra bên trong mẫu, v.v.

(sử dụng vv, vì tôi không biết lý do khác nữa) .


Cá nhân, một lần nữa, tôi nghĩ việc sử dụng mẫu Đài quan sát thậm chí còn đẹp hơn, sau đó bạn có thể sử dụng asyncđường ống và chỉ khi phát ra một giá trị mới, mẫu mới được đánh giá lại:

userIsAuthorized$ = combineLatest([
  this.user$,
  this.isAuthorized$
]).pipe(
  map(([ user, authorized ]) => !!user && !!user.name && authorized),
  shareReplay({ refCount: true, bufferSize: 1 })
);

Sau đó, bạn có thể sử dụng trong mẫu như thế này:

<ng-template *ngIf="userIsAuthorized$ | async">
 ...
</ng-template>

Tuy nhiên, một tùy chọn khác sẽ được sử dụng ngOnChanges, nếu tất cả các biến phụ thuộc vào thành phần là Đầu vào và bạn có rất nhiều logic đang diễn ra để tính toán một biến mẫu nhất định (không phải là trường hợp bạn đã hiển thị):

export class UserComponent implements ngOnChanges {
  userIsAuthorized: boolean = false;

  @Input()
  user?: any;

  @Input()
  isAuthorized?: boolean;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.user || changes.isAuthorized) {
      this.userIsAuthorized = this.userCheck();
    }
  }

  userCheck(): boolean {
    return this.user && this.user.name && this.isAuthorized || false;
  }
}

Mà bạn có thể sử dụng trong mẫu của bạn như thế này:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>

Cảm ơn bạn đã trả lời, rất sâu sắc. Đối với trường hợp cụ thể của chúng tôi, thay đổi chiến lược phát hiện không phải là một tùy chọn, vì thành phần được đề cập thực hiện yêu cầu nhận và do đó thay đổi không liên quan đến đầu vào cụ thể, mà là yêu cầu nhận. Tuy nhiên, đây là thông tin rất hữu ích để phát triển các thành phần trong tương lai, nơi thay đổi phụ thuộc vào các biến đầu vào
Jesper

1
@Jesper nếu thành phần thực hiện yêu cầu get, thì bạn đã có một Observableluồng, điều này sẽ làm cho nó trở thành một ứng cử viên hoàn hảo cho tùy chọn thứ 2 tôi đã hiển thị. Dù bằng cách nào, vui mừng tôi có thể cung cấp cho bạn một số hiểu biết
Poul Kruijt

6

Không được đề xuất vì nhiều lý do hiệu trưởng:

Để xác định xem userCheck () có cần được kết xuất lại hay không, Angular cần thực thi biểu thức userCheck () để kiểm tra xem giá trị trả về của nó có thay đổi hay không.

Vì Angular không thể dự đoán liệu giá trị trả về của userCheck () có thay đổi hay không, nên nó cần thực thi chức năng mỗi khi phát hiện thay đổi chạy.

Vì vậy, nếu phát hiện thay đổi chạy 300 lần, hàm được gọi là 300 lần, ngay cả khi giá trị trả về của nó không bao giờ thay đổi.

Giải thích mở rộng và nhiều vấn đề hơn https://medium.com/showpad-engineering/why-you-should-never-use-feft-calls-in-angular-template-expressions-e1a50f9c0496

Vấn đề sẽ xảy ra khi thành phần của bạn lớn và tham dự nhiều sự kiện thay đổi, nếu thành phần của bạn sẽ được bật sáng và chỉ tham dự một vài sự kiện thì không có vấn đề gì.

Ví dụ với đài quan sát

user$;
isAuth$
userCheck$;

userCheck$ = user$.pipe(
switchMap((user) => {
    return forkJoin([of(user), isAuth$]);
 }
)
.map(([user, isAuthenticated])=>{
   if(user && user.name && isAuthenticated){
     return true;
   } else {
     return false;
   }
})
);

Sau đó, bạn có thể sử dụng nó có thể quan sát được với ống async trong mã của bạn.


2
Xin chào, chỉ muốn chỉ ra rằng tôi thấy đề xuất sử dụng biến là sai lệch nghiêm trọng .. Biến không cập nhật là giá trị khi bất kỳ giá trị kết hợp nào thay đổi
nsndvd

1
Và cho dù biểu thức là trực tiếp trong mẫu, hoặc được trả về bởi một hàm, nó sẽ phải được đánh giá tại mỗi lần phát hiện thay đổi.
JB Nizet

Vâng, xin lỗi thực sự của nó sẽ chỉnh sửa vì không thực hiện các hành vi xấu
anthony willis muñoz

@ anthonywillismuñoz Vậy bạn sẽ tiếp cận một tình huống như thế này như thế nào? Chỉ cần sống với nhiều điều kiện khó đọc trong * ng If?
Jesper

1
điều đó phụ thuộc vào bạn trong tình huống bạn có một số tùy chọn trong bài trung bình. Nhưng tôi nghĩ rằng bạn đang sử dụng đài quan sát. Sẽ chỉnh sửa bài với một ví dụ để giảm điều kiện. nếu bạn có thể chỉ cho tôi từ nơi bạn có được các điều kiện.
anthony willis muñoz

0

Tôi nghĩ rằng JavaScript được tạo ra với mục tiêu để nhà phát triển không nhận thấy sự khác biệt giữa biểu hiện và lời gọi hàm liên quan đến hiệu suất.

Trong C ++ có một từ khóa inlineđể đánh dấu một chức năng. Ví dụ:

inline bool userCheck()
{
    return isAuthorized;
}

Điều này đã được thực hiện để loại bỏ một cuộc gọi chức năng. Kết quả là, trình biên dịch thay thế tất cả các cuộc gọi userCheckbằng phần thân của hàm. Lý do đổi mới inline? Tăng hiệu suất.

Do đó, tôi nghĩ rằng thời gian thực hiện của một lệnh gọi hàm với một biểu thức, có lẽ, chậm hơn so với chỉ thực hiện expresion. Nhưng, tôi cũng nghĩ rằng bạn sẽ không nhận thấy sự khác biệt về hiệu suất nếu bạn chỉ có một biểu thức trong hàm.

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.