Helpex - Trao Đổi & Giúp Đỡ - là một trang web tổng hợp, chia sẻ kiến thức và hỏi đáp dành cho các lĩnh vực ở mọi cấp độ và các chuyên gia trong các lĩnh vực liên quan. Chỉ mất một phút để đăng ký.

Tham gia cộng đồng
Ai cũng có thể viết bài và chia sẻ
Ai cũng có thể đặt câu hỏi
Ai cũng có thể trả lời
Các nội dung hay nhất được bình chọn và vươn lên trên đầu
Đã viết
Hoạt động 21 giờ trước
Đã xem 5.7k
3
Trong bài đăng này, chúng ta sẽ đi qua khái niệm Lập trình phản ứng chức năng theo quan điểm của một nhà phát triển Angular 2. Hầu hết điều này có thể áp dụng cho các ứng dụng Angular 1, nhưng các ví dụ trong bài đăng này nằm ở Angular 2. Chúng ta hãy đi qua các chủ đề sau:
  • Một khái niệm lập trình không đồng bộ mới: luồng
  • Một kiểu nguyên thủy mới: Đài quan sát
  • Lập trình phản ứng chức năng và RxJs
  • Bản chất của cách thức hoạt động của Đài quan sát
  • Toán tử thường được sử dụng: bản đồ, bộ lọc, giảm, quét
  • Việc sử dụng RxJ phổ biến trong Angular 2: Forms và Http
  • Toán tử chia sẻ và Đài quan sát nóng vs lạnh
  • Cách tiếp cận việc học của RxJs
  • Kết luận

Lập trình chức năng trong Thế giới Frontend

Mặc dù Lập trình chức năng (FP) đã xuất hiện trong nhiều năm, việc áp dụng đã chậm trong phát triển chính thống. Một số thực tiễn tốt nhất của nó ngày nay ít nhiều đồng thuận, nhưng chẳng hạn, chỉ gần đây chúng tôi mới bắt đầu thấy các thư viện có khả năng cấu thành chức năng thực sự, như bản lưu trữ mới nhất có hỗ trợ FP hạng nhất, hoặc Ramda .

Lập trình Frontend vốn đã không đồng bộ và luôn thiếu một thứ gì đó để cho phép xây dựng các frontend theo cách giống như chức năng.

Một khái niệm lập trình không đồng bộ mới - Luồng

Khái niệm còn thiếu và là trung tâm của Lập trình phản ứng chức năng rất có thể là luồng. Luồng là một chuỗi các giá trị theo thời gian, đơn giản như vậy. Lấy ví dụ luồng giá trị số sau đây, trong đó mỗi giá trị được phát ra cứ sau một giây:

0, 1, 2, 3 ,4 

Một ví dụ khác về luồng là một chuỗi các sự kiện nhấp chuột, với tọa độ chuột x và y của nhấp chuột:

(100,200), (110, 300), (400, 50) ...

Mọi thứ xảy ra trong trình duyệt có thể được xem như một luồng: chuỗi các sự kiện trình duyệt được kích hoạt khi người dùng tương tác với trang, dữ liệu đến từ máy chủ, thời gian chờ được kích hoạt.

Các luồng dường như là một mô hình tốt để mô tả cách một chương trình frontend thực sự hoạt động. Nhưng, có thể thoải mái xây dựng một chương trình dễ đọc xung quanh khái niệm đó không?

Một nguyên thủy phát triển không đồng bộ mới - Có thể quan sát được

Để khái niệm luồng có ích để xây dựng chương trình, chúng ta cần một cách để tạo luồng, đăng ký chúng, phản ứng với các giá trị mới và kết hợp các luồng với nhau để xây dựng các luồng mới.

Ngoài ra, hãy chú ý sự giống nhau của luồng số ở trên với một thứ quen thuộc hơn:

[0, 1, 2, 3, 4]

Nó trông rất giống một mảng Javascript đơn giản!

Mảng là các cấu trúc dữ liệu thực sự dễ thao tác và kết hợp để tạo ra các mảng mới, nhờ API mở rộng của nó . Nhìn vào tất cả các phương thức thao tác dữ liệu đó và tưởng tượng rằng các luồng có thể được kết hợp bằng cách sử dụng chúng và nhiều toán tử lập trình chức năng khác.

Sự kết hợp giữa một luồng này với một tập hợp các toán tử chức năng để biến đổi các luồng dẫn chúng ta đến khái niệm có thể quan sát được. Hãy nghĩ về nó như API của bạn để khai thác vào một luồng. Bạn có thể sử dụng nó để xác định luồng, đăng ký và chuyển đổi nó.

Những gì chúng ta cần tại thời điểm này là một thư viện thực hiện nguyên thủy quan sát được, và đó là nơi RxJs đến.

Giới thiệu RxJs

RxJs là viết tắt của Reactive Extension cho Javascript và đó là một triển khai của Observables cho Javascript. Nếu Observables đưa nó vào ES7, bạn có thể thấy RxJs ít nhiều là một pollyfill.

Để xem nó hoạt động, đây là cùng một luồng số mà chúng tôi đã đề cập ở trên, được xác định bằng RxJs:

var obs = Rx.Observable.interval(1000).take(5);

Bây giờ chúng ta có các khái niệm về Stream và có thể quan sát được, giờ chúng ta đã sẵn sàng để giới thiệu khái niệm về lập trình phản ứng chức năng.

Giới thiệu lập trình phản ứng chức năng

Lập trình phản ứng chức năng (FRP) là một mô hình cho phát triển phần mềm nói rằng toàn bộ các chương trình có thể được xây dựng độc đáo xung quanh khái niệm về luồng. Không chỉ các chương trình frontend, mà bất kỳ chương trình nói chung.

Trong khi phát triển theo mô hình này, phát triển bao gồm tạo hoặc xác định các luồng giá trị mà chương trình của bạn quan tâm, kết hợp chúng lại với nhau và cuối cùng đăng ký các luồng đó để tạo ra phản ứng với các giá trị mới.

Mục tiêu cốt lõi của FRP

Ý tưởng chính của FRP là xây dựng các chương trình theo cách chỉ khai báo, bằng cách xác định các luồng là gì, chúng được liên kết với nhau như thế nào và điều gì xảy ra nếu một giá trị luồng mới đến theo thời gian.

Các chương trình như thế này có thể được xây dựng với rất ít hoặc không có biến trạng thái ứng dụng, nói chung là một nguồn gây ra lỗi. Để làm cho nó rõ ràng hơn: ứng dụng không có nhà nước, nhưng trạng thái đó thường được lưu trữ trên suối nhất định hoặc trong DOM, không phải trên mã ứng dụng riêng của mình.

UI không trạng thái, nhưng phần nào?

Sự vắng mặt của nhà nước này chủ yếu dành cho các thành phần thông minh có dịch vụ dữ liệu được đưa vào. Các thành phần thuần túy có thể tiêu thụ các vật thể quan sát nhưng có thể muốn giữ một số biến trạng thái bên trong trong thực tế, ví dụ như một isOpenlá cờ cho một danh sách thả xuống.

Bản chất của cách thức quan sát làm việc

Chúng ta hãy quay lại chuỗi số đơn giản Có thể quan sát được trình bày trước đó và thêm hiệu ứng phụ cho nó:

var obs = Rx.Observable.interval(500)
           .take(5)
           .do(i => console.log(i) );

Lưu ý rằng bạn có thể muốn tránh người do()vận hành vì mục đích duy nhất của nó là tạo ra các tác dụng phụ.

Nếu chúng tôi chạy chương trình này, bạn có thể ngạc nhiên bởi thực tế là không có gì được in ra bàn điều khiển! Điều này là do một trong những tính chất chính của loại Quan sát này.

Đài quan sát là nóng hoặc lạnh

Nếu Observable đơn giản này không có người đăng ký, nó sẽ không được kích hoạt!

Quan sát được cho là lạnh vì nó không tạo ra các giá trị mới nếu không có đăng ký tồn tại. Để có được các giá trị số được in ra bàn điều khiển, chúng ta cần đăng ký vào Đài quan sát:

obs.subscribe();

Với điều này, chúng tôi nhận được các giá trị số được in ra bàn điều khiển. Nhưng, điều gì xảy ra nếu chúng ta thêm hai người đăng ký vào điều này có thể quan sát được:

var obs = Rx.Observable.interval(500).take(5)
            .do(i => console.log("obs value "+ i) );

obs.subscribe(value => console.log("observer 1 received " + value));

obs.subscribe(value => console.log("observer 2 received " + value));

Điều đang xảy ra ở đây là cái tên Observable obscó tác dụng phụ: bản in của nó tới bàn điều khiển thông qua do()toán tử. Sau đó, hai người đăng ký được thêm vào obs, mỗi người cũng in giá trị kết quả lên bàn điều khiển. Đây là đầu ra của giao diện điều khiển:

obs value 0
observer 1 received 0
obs value 0
observer 2 received 0

obs value 1
observer 1 received 1
obs value 1
observer 2 received 1

Có vẻ như tác dụng phụ đang được gọi hai lần! Điều này dẫn đến một tài sản quan trọng thứ hai của Đài quan sát.

Khi chúng tôi tạo một thuê bao, chúng tôi đang thiết lập một chuỗi xử lý riêng hoàn toàn mới. Các obsbiến chỉ là một định nghĩa, một kế hoạch chi tiết về cách thức một chuỗi xử lý chức năng của nhà khai thác phải được thiết lập từ các nguồn của sự kiện cho đến khi chìm của sự kiện, khi mà chìm (người quan sát) được đính kèm.

obschỉ là một kế hoạch chi tiết về làm thế nào để xây dựng một chuỗi hoạt động, những gì xảy ra khi chúng ta đăng ký hai nhà quan sát là hai dây chuyền chế biến riêng biệt được thiết lập, gây ra tác dụng phụ được in hai lần, một lần cho mỗi chuỗi.

Có nhiều cách để xác định các loại Đài quan sát khác trong đó hiệu ứng phụ sẽ chỉ được gọi một lần (xem thêm). Điều quan trọng là nhận ra rằng chúng ta nên ghi nhớ hai điều mọi lúc khi tiếp xúc với các vật quan sát:

  • nóng hay lạnh có thể quan sát được?
  • có thể quan sát được chia sẻ hay không?

Các toán tử RxJ thường được sử dụng trong Angular 2

Có nhiều toán tử chức năng có thể được sử dụng để kết hợp các Đài quan sát, vì vậy hãy tập trung ở đây vào một số toán tử được sử dụng phổ biến nhất và cách chúng có thể được sử dụng trong ứng dụng Angular 2.

Cụ thể, chúng ta sẽ xem làm thế nào một số toán tử có thể được sử dụng trong các tác vụ phổ biến như xác thực mẫu.

Angular 2 sử dụng kính quan sát như thế nào

Angular 2 hiện đang sử dụng RxJs Observables theo hai cách khác nhau:

  • như một cơ chế thực hiện nội bộ, để thực hiện một số logic cốt lõi của nó như EventEmitter
  • là một phần của API công khai, cụ thể là trong Biểu mẫu và mô-đun HTTP

Người vận hành bản đồ

Toán tử bản đồ có lẽ là toán tử lập trình chức năng nổi tiếng nhất hiện có, và tất nhiên Observable cung cấp nó. Toán tử bản đồ chỉ cần lấy một Observable và thêm một hàm biến đổi để xử lý đầu ra của luồng. Ví dụ:

var obs = Rx.Observable.interval(500).take(5)
            .map(i => 2 * i );

Điều quan trọng là phải nhận ra rằng đầu ra của mapvẫn là một thứ khác có thể quan sát được. Những gì chúng ta có ở đây vẫn chỉ là một định nghĩa về chuỗi hoạt động. Chúng tôi vẫn cần phải đăng ký để có thể quan sát này để có được một đầu ra từ nó.

Bản đồ và Bộ lọc được sử dụng để xác thực mẫu

Một toán tử thường được sử dụng là filter. Trong Angular 2, các biểu mẫu có thể được xử lý như các vật quan sát mà chúng tôi đăng ký. Có nghĩa là giá trị của toàn bộ biểu mẫu là một giá trị có thể quan sát được và giá trị của từng trường riêng lẻ là một giá trị có thể quan sát được. Ví dụ, hãy lấy một hình thức đơn giản:

<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
   <p>
        <label>First Name:</label>
        <input type="text" ngControl="firstName">
   </p>
</form>

Việc sử dụng NgFormModelcho phép liên kết biểu mẫu với một biến loại
ControlGrouptrong bộ điều khiển thành phần. Ở đó chúng ta có thể sử dụng biến đó để truy cập vào mẫu có thể quan sát thông qua form.valueChanges.

Sử dụng có thể quan sát, chúng ta có thể kết hợp các hoạt động của bản đồ và bộ lọc để có được một phiên bản chữ hoa và xác thực của nội dung biểu mẫu:

this.form.valueChanges
        .map((value) => {
            value.firstName = value.firstName.toUpperCase();
            return value;
        })
        .filter((value) => this.form.valid)
        .subscribe(validValue => ...);

Xem bài đăng này để biết thêm chi tiết về cách sử dụng NgFormModelđể làm điều này.

Toán tử rút gọn và tại sao bạn có thể không cần nó

Gần đây đã có một số cuộc nói chuyện về kiến ​​trúc Flux và cách sử dụng nó để xây dựng các ứng dụng Angular 2, kiểm tra ví dụ bài đăng trước đây . Ý tưởng chính là có một nguyên tử trạng thái duy nhất cho toàn bộ ứng dụng, đăng ký và tạo các giá trị mới của nó bằng các hàm giảm tốc.

Quan trọng đối với cách xây dựng tiền tuyến đó là reducenhà điều hành chức năng, là trung tâm của Redux . Các đài quan sát của RxJ cũng cung cấp một toán tử rút gọn, vì vậy hãy xem nó trông như thế nào:

var obs = Rx.Observable.interval(500).take(5);

var reduced = obs.reduce((state, value) => state + value , 0);

reduced.subscribe(total => console.log("total =" + total));

Những gì đang xảy ra ở đây là khi obscó thể quan sát được, chúng ta tạo ra một thứ có thể quan sát được thứ hai reduced. Giảm phát ra một giá trị khi luồng obsđóng và có giá trị duy nhất là tổng của tất cả các phần tử trong luồng. Đầu ra trong giao diện điều khiển là thế này:

total = 10

Vì vậy, giảm phát ra tổng cuối của tích lũy, điều này đúng với định nghĩa chức năng của toán tử. Nhưng, đây không phải là điều chúng ta muốn trong trường hợp có thể quan sát được chứa trạng thái ứng dụng thay vì giá trị số.

Toán tử quét

Bạn có thể quan tâm đến các giá trị trung gian của quá trình khử và có thể muốn biết trạng thái có thể quan sát được sau khi mỗi phần tử được giảm và phản ứng với điều đó thay vì chỉ kết quả cuối cùng của hoạt động khử. Đặc biệt là vì dòng giảm có thể không bao giờ đóng!

Đây là những gì người vận hành quét thực hiện và đó là cốt lõi của cách chúng ta có thể xây dựng các ứng dụng giống Redux bằng RxJs :

var obs = Rx.Observable.interval(500).take(5);

var scanObs = obs.scan((state, value) => state + value , 0);

scanObs.subscribe(total => console.log(total));

Một lần nữa, chúng tôi đã tạo một thứ hai có thể quan sát được dựa trên obsvà đã đăng ký nó. Kết quả là:

0
1
3
6
10

Chúng ta có thể thấy trong bảng điều khiển các kết quả trung gian của quá trình tích lũy, và không chỉ kết quả cuối cùng.

Một thuộc tính quan trọng mà chúng ta đã thấy ở đầu bài này, là khi chúng ta đăng ký vào một thứ có thể quan sát được, nó kích hoạt việc khởi tạo một chuỗi xử lý riêng biệt. Nhà shaređiều hành cho phép chia sẻ một thuê bao của chuỗi xử lý với các thuê bao khác. Lấy ví dụ như sau:

var obs = Rx.Observable.interval(500).take(5)
            .do(i => console.log("obs value "+ i) )
            .share();

obs.subscribe(value => console.log("observer 1 received " + value));

obs.subscribe(value => console.log("observer 2 received " + value));

Điều này tạo ra đầu ra sau:

obs value 0
observer 1 received 0
observer 2 received 0

obs value 1
observer 1 received 1
observer 2 received 1

Chúng ta có thể thấy hiệu ứng phụ bên trong docuộc gọi chỉ được in một lần thay vì hai lần.

Tất cả cùng nhau ngay bây giờ

Có thể thực hiện ứng dụng Angular 2 giống như Flux với một nguyên tử trạng thái theo mọi cách tương tự như Redux, chỉ bằng cách kết hợp một vài toán tử RxJ được trình bày ở đây. Đây là chủ đề của một bài sắp tới.

Kết luận

RxJS và FRP là những khái niệm thực sự mạnh mẽ được bộc lộ trong một số phần nhất định của API Angular 2 và có thể được sử dụng để cấu trúc các ứng dụng được xây dựng theo cách rất khác so với trước đây.

Có nhiều lựa chọn có sẵn để cấu trúc các ứng dụng Angular 2. Một tùy chọn là phản ứng hoàn toàn trong trường hợp bạn sẽ sử dụng RxJs rộng rãi, nhưng một tùy chọn khác là giữ cho nó đơn giản hơn và chỉ sử dụng RxJ thông qua các phần của Angular hiển thị nó trong API công khai của nó (ví dụ: Biểu mẫu và HTTP) .

Bạn cũng có thể kết hợp và kết hợp: sử dụng cách tiếp cận phản ứng trong các phần của ứng dụng nơi kiến ​​trúc Flux có lợi (xem ở đây để biết khi nào nên sử dụng nó) và một cách nói truyền thống hơn ở nơi khác.

Trong cả hai trường hợp và khi xây dựng các ứng dụng Angular 2 nói chung, điều quan trọng là phải làm quen với RxJs.

Cách tiếp cận việc học của RxJs

Đôi khi người ta đề cập rằng RxJs có tiềm năng cho một đường cong học tập lớn. Điều này có thể có thể được tiếp cận dần dần, bằng cách tập trung đầu tiên vào một vài khái niệm cốt lõi chính: sự lười biếng có thể quan sát được và quan sát nóng so với lạnh.

Sau đó, vấn đề tập trung vào các toán tử RxJ được sử dụng phổ biến nhất, có lẽ có khoảng 10 đến 15 toán tử đủ để xây dựng hầu hết các chương trình.

Một cách để làm điều đó là thử RxJ bằng JsBin , thử một khái niệm / toán tử tại một thời điểm và lấy nó từ đó.

Tài liệu tham khảo

Quản lý nhà nước trong các ứng dụng Angular 2 của Victor Savkin ( @victorsavkin )

Giới thiệu về lập trình phản ứng mà bạn đã bị mất bởi Andre Staltz ( @andrestaltz )

Bài học của RxJs về egghead.io

|