Tìm kiếm trang web

javascript - Mục đích của thư viện ngrx/effects là gì?


Tôi đã không tìm thấy bất kỳ thông tin hữu ích nào về thư viện này hoặc mục đích của nó là gì. Dường như ngrx/effects giải thích thư viện này cho các nhà phát triển đã quen thuộc với khái niệm này và đưa ra một ví dụ cho người mới bắt đầu về cách viết mã.

Câu hỏi của tôi:

  1. Các nguồn gốc của hành động là gì?
  2. Mục đích của thư viện ngrx/effects là gì; nhược điểm của việc chỉ sử dụng ngrx/store là gì?
  3. Khi nào nên được khuyên sử dụng?
  4. Nó có hỗ trợ Angular RC 5+ không? Chúng ta cấu hình nó trong RC 5+ như thế nào?

Cảm ơn!

Câu trả lời #1

Chủ đề quá rộng. Nó sẽ giống như một hướng dẫn. Dù sao tôi vẫn sẽ thử. Trong một trường hợp bình thường, bạn sẽ có một action, reducer và store. Actions được store gửi đi, mà reducer sẽ đăng ký nhận từ store. Sau đó, reducer sẽ thực hiện trên action và hình thành một state mới. Trong các ví dụ, tất cả các state đều ở frontend, nhưng trong một ứng dụng thực sự, nó cần gọi đến backend DB hoặc MQ, v.v., những lệnh gọi này có các tác động phụ. Framework được sử dụng để tách các tác động phụ này ra một nơi chung.

Giả sử bạn lưu một Bản Ghi Người vào cơ sở dữ liệu của mình, action: Action={type: SAVE_PERSON, payload: person}. Thông thường, component của bạn sẽ không trực tiếp gọi this.store.dispatch({type: SAVE_PERSON, payload: person}) để reducer gọi dịch vụ HTTP, thay vào đó nó sẽ gọi this.personService.save(person).subscribe(res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json})). Logic của component sẽ trở nên phức tạp hơn khi thêm xử lý lỗi thực tế. Để tránh điều này, sẽ tốt hơn nếu chỉ cần gọi this.store.dispatch({type: SAVE_PERSON, payload: person}) từ component của bạn.

Đó là lý do thư viện hiệu ứng tồn tại. Nó hoạt động giống như một bộ lọc servlet JEE trước reducer. Nó khớp với loại ACTION (bộ lọc có thể khớp với các URL trong thế giới Java) và sau đó thực hiện hành động trên nó, và cuối cùng trả về một action khác, hoặc không có action nào, hoặc nhiều action. Sau đó reducer phản hồi các action đầu ra của hiệu ứng.

Để tiếp tục ví dụ trước, với thư viện hiệu ứng:

@Effects() savePerson$ = this.stateUpdates$.whenAction(SAVE_PERSON)
   .map<Person>(toPayload)
   .switchMap( person => this.personService.save(person) )
   .map( res => {type: SAVE_PERSON_OK, payload: res.json} )
   .catch( e => {type: SAVE_PERSON_ERR, payload: err} )

Logic dệt được tập trung vào tất cả các lớp Effects và Reducers. Nó có thể dễ dàng trở nên phức tạp hơn, và đồng thời thiết kế này làm cho các phần khác đơn giản hơn và dễ tái sử dụng hơn.

Ví dụ, nếu giao diện người dùng có chức năng tự động lưu cùng với lưu thủ công, để tránh việc lưu không cần thiết, phần tự động lưu của giao diện người dùng có thể chỉ được kích hoạt bởi bộ hẹn giờ và phần thủ công có thể được kích hoạt bằng cách người dùng nhấn. Cả hai sẽ phát đi một hành động SAVE_CLIENT. Bộ chặn hiệu ứng có thể là:

@Effects() savePerson$ = this.stateUpdates$.whenAction(SAVE_PERSON)
   .debounce(300).map<Person>(toPayload)
   .distinctUntilChanged(...)
   .switchMap( see above )
   // at least 300 milliseconds and changed to make a save, otherwise no save

Cuộc gọi

...switchMap( person => this.personService.save(person) )
   .map( res => {type: SAVE_PERSON_OK, payload: res.json} )
   .catch( e => Observable.of( {type: SAVE_PERSON_ERR, payload: err}) )

chỉ hoạt động một lần nếu có lỗi. Luồng sẽ chết sau khi một lỗi được ném ra vì catch thử trên luồng bên ngoài. Cuộc gọi nên được

...switchMap( person => this.personService.save(person)
   .map( res => {type: SAVE_PERSON_OK, payload: res.json} )
   .catch( e => Observable.of( {type: SAVE_PERSON_ERR, payload: err}) ) )

Hoặc cách khác: thay đổi tất cả các phương thức dịch vụ của ServiceClass để trả về ServiceResponse, trong đó chứa mã lỗi, thông báo lỗi và đối tượng phản hồi được đóng gói từ phía server, tức là

export class ServiceResult {    
    error:     string;    
    data:      any;

    hasError(): boolean {
       return error != undefined && error != null;    }

    static ok(data: any): ServiceResult {
       let ret = new ServiceResult();
       ret.data = data;
       return ret;    
    }

    static err(info: any): ServiceResult {
       let ret = new ServiceResult();
       ret.error = JSON.stringify(info);
       return ret;    
   } 
}

@Injectable()
export class PersonService {
   constructor(private http: Http) {}
   savePerson(p: Person): Observable<ServiceResult> {
       return http.post(url, JSON.stringify(p)).map(ServiceResult.ok);
              .catch( ServiceResult.err ); 
   }
}

@Injectable()
export class PersonEffects {
  constructor(
    private update$: StateUpdates<AppState>,
    private personActions: PersonActions,
    private svc: PersonService
  ){
  }

@Effects() savePerson$ = this.stateUpdates$.whenAction(PersonActions.SAVE_PERSON)
   .map<Person>(toPayload)
   .switchMap( person => this.personService.save(person) )
   .map( res => {
       if (res.hasError()) {
           return personActions.saveErrAction(res.error);
       } else {
           return personActions.saveOkAction(res.data);
       }
   });

@Injectable()
export class PersonActions {
    static SAVE_OK_ACTION = "Save OK";
    saveOkAction(p: Person): Action {
       return {type: PersonActions.SAVE_OK_ACTION,
               payload: p};
    }

    ... ...
}

Một chỉnh sửa cho bình luận trước của tôi: Effect-Class và Reducer-Class, nếu bạn có cả Effect-class và Reducer-class phản ứng với cùng một loại action, Reducer-class sẽ phản ứng trước, sau đó Effect-class. Đây là một ví dụ: Một component có một nút bấm, khi được nhấn, gọi: this.store.dispatch(this.clientActions.effectChain(1)); sẽ được xử lý bởi effectChainReducer, và sau đó ClientEffects.chainEffects$, điều này tăng payload từ 1 lên 2; chờ 500 ms để phát ra một action khác: this. clientActions.effectChain(2), sau khi được xử lý bởi effectChainReducer với payload=2 và sau đó ClientEffects.chainEffects$, tăng lên 3 từ 2, phát ra this.clientActions.effectChain(3), ..., cho đến khi nó lớn hơn 10, ClientEffects.chainEffects$ phát ra this.clientActions.endEffectChain(), thay đổi trạng thái store thành 1000 thông qua effectChainReducer, cuối cùng dừng ở đây.

    export interface AppState {
      ... ...

      chainLevel:     number;
    }

    // In NgModule decorator
    @NgModule({
       imports: [...,
            StoreModule.provideStore({
                ... ...
                chainLevel: effectChainReducer
              }, ...],
       ...
       providers: [... runEffects(ClientEffects) ],
       ...
    })
    export class AppModule {}


    export class ClientActions {
      ... ...
      static EFFECT_CHAIN = "Chain Effect";
      effectChain(idx: number): Action {
        return {
              type: ClientActions.EFFECT_CHAIN,
              payload: idx
        };
      }

      static END_EFFECT_CHAIN = "End Chain Effect";
      endEffectChain(): Action {
        return {
          type: ClientActions.END_EFFECT_CHAIN,
        };
      }

  static RESET_EFFECT_CHAIN = "Reset Chain Effect";
  resetEffectChain(idx: number = 0): Action {
    return {
      type: ClientActions.RESET_EFFECT_CHAIN,
      payload: idx
    };

    }

    export class ClientEffects {
      ... ...
      @Effect()
      chainEffects$ = this.update$.whenAction(ClientActions.EFFECT_CHAIN)
        .map<number>(toPayload)
        .map(l => {
          console.log(`effect chain are at level: ${l}`)
          return l + 1;
        })
        .delay(500)
        .map(l => {
          if (l > 10) {
             return this.clientActions.endEffectChain();
          } else {
             return this.clientActions.effectChain(l);
          }
        });
    }

    // client-reducer.ts file
    export const effectChainReducer = (state: any = 0, {type, payload}) => {
      switch (type) {
        case ClientActions.EFFECT_CHAIN:
          console.log("reducer chain are at level: " + payload);
          return payload;
        case ClientActions.RESET_EFFECT_CHAIN:
          console.log("reset chain level to: " + payload);
          return payload;
        case ClientActions.END_EFFECT_CHAIN:
          return 1000;
        default:
          return state;
      }
    }

Nếu bạn chạy đoạn mã trên, kết quả đầu ra sẽ trông như sau:

client-reducer.ts:51 chuỗi reducer ở cấp: 1
client-effects.ts:72 chuỗi effect ở cấp: 1
client-reducer.ts:51 chuỗi reducer ở cấp: 2
client-effects.ts:72 chuỗi effect ở cấp: 2
client-reducer.ts:51 chuỗi reducer ở cấp: 3
client-effects.ts:72 chuỗi effect ở cấp: 3
... ...
client-reducer.ts:51 chuỗi reducer ở cấp: 10
client-effects.ts:72 chuỗi effect ở cấp: 10

Điều này chỉ ra rằng reducer chạy trước khi các effect, Effect-Class là một bộ chặn hậu, không phải bộ chặn tiên. Xem sơ đồ luồng:

Bài viết liên quan