2016-09-17 8 views
24

मैं इस पुस्तकालय के बारे में कोई उपयोगी जानकारी खोजने में विफल रहा हूं या इसका उद्देश्य क्या है। ऐसा लगता है कि ngrx/effects इस लाइब्रेरी को उन डेवलपर्स को समझाएं जो पहले से ही इस अवधारणा को जानते हैं और कोड कैसे करें पर एक बड़ा उदाहरण देते हैं।ngrx/प्रभाव लाइब्रेरी का उद्देश्य क्या है?

मेरे सवालों का:

  1. क्या कार्रवाई की स्रोत क्या हैं?
  2. एनजीआरएक्स/प्रभाव पुस्तकालय का उद्देश्य क्या है; केवल ngrx/store का उपयोग करने का नकारात्मक पक्ष क्या है?
  3. जब इसे उपयोग करने की अनुशंसा की जाती है?
  4. क्या यह कोणीय आरसी 5+ का समर्थन करता है? हम इसे आरसी 5+ में कैसे कॉन्फ़िगर करते हैं?

धन्यवाद!

+0

मैंने वीडियो को https://www.youtube.com/watch?v=cyaAhXHhxgk पर ngrx स्टोर और प्रभावों के इन रचनाकारों से देखा। मेरे लिए पहली बार, जहां ngrx प्रभाव सही ढंग से समझाया गया है। तो साथी कृपया इसे जांचें यदि आपको अभी भी अपने सिर को लपेटने की ज़रूरत है। – trungk18

उत्तर

67

विषय बहुत व्यापक है। यह एक ट्यूटोरियल की तरह होगा। मुझे इसे एक मौका और देना होगा। सामान्य स्थिति में, आपके पास एक्शन, रेड्यूसर और स्टोर होगा। क्रियाएं स्टोर द्वारा भेजी जाती हैं, जो कि रेड्यूसर द्वारा सब्सक्राइब की जाती है, और उसके बाद कार्रवाई पर रेड्यूसर कार्य करता है, एक नया राज्य बनाता है। ट्यूटोरियल के लिए, सभी राज्य सामने की ओर हैं, लेकिन वास्तविक ऐप में, इसे बैकएंड डीबी या एमक्यू, आदि को कॉल करने की आवश्यकता है, इन कॉलों के दुष्प्रभावों को एक आम जगह में कारक बनाने के लिए साइड इफेक्ट्स हैं, जो कि फ्रेमवर्क का उपयोग किया जाता है।

मान लें कि डेटाबेस में एक व्यक्ति रिकॉर्ड को सहेजने के लिए कहें, action: Action = {type: SAVE_PERSON, payload: person}। आम तौर पर घटक this.store.dispatch({type: SAVE_PERSON, payload: person}) को रेड्यूसर कॉलिंग HTTP सेवा देने के लिए सीधे कॉल नहीं करेगा, इसके बजाय यह this.personService.save(person).subscribe(res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json})) पर कॉल करेगा। असली जीवन में त्रुटि संभाल के बारे में सोचते समय घटक तर्क अधिक जटिल हो जाएगा। इससे बचने के लिए, यह अच्छा होगा अगर घटक से this.store.dispatch({type: SAVE_PERSON, payload: person}) पर कॉल किया जाए।

प्रभाव लाइब्रेरी के लिए आता है। यह reducer के सामने जेईई सर्वलेट फिल्टर की तरह काम करता है। यह क्रिया प्रकार से मेल खाता है (फ़िल्टर जावा दुनिया में यूआरएल से मेल खाता है) और फिर उस पर कार्य करें, और अंततः एक अलग कार्रवाई, या कोई कार्रवाई या एकाधिक क्रियाएं वापस न करें, और उसके बाद प्रभाव के आउटपुट कार्यों को प्रतिक्रिया दें।

पिछले उदाहरण पर जारी रखें, प्रभाव पुस्तकालय रास्ते में:

@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}) 

बुनाई तर्क सभी प्रभाव और कम करने वाली वर्गों में केंद्रीकृत है। यह आसानी से अधिक जटिल हो सकता है, और साथ ही यह डिज़ाइन अन्य भागों को अधिक सरल और अधिक पुनः उपयोग करने योग्य बनाता है।

उदाहरण के लिए यदि यूआई में अनावश्यक बचत से बचने के लिए मैन्युअल रूप से सहेजने के लिए एक ऑटो सेविंग प्लस है, तो यूआई ऑटो सेव पार्टर को टाइमर द्वारा ट्रिगर किया जा सकता है और मैन्युअल रूप से भाग को उपयोगकर्ता क्लिक द्वारा ट्रिगर किया जाता है, दोनों प्रभाव इंटरसेप्टर में SAVE_CLIENT कार्रवाई भेजता है, यह हो सकता है:

@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 

कॉल यदि कोई त्रुटि होती है

...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})) 

केवल एक बार काम करता है। त्रुटि फेंकने के बाद स्ट्रीम मर जाती है क्योंकि पकड़ बाहरी धारा पर कोशिश करती है। कॉल

...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}))) 

होना चाहिए; या एक और तरीका: ServiceResponse को वापस करने के लिए सभी ServiceClasses सेवाओं विधि को बदलें जिसमें त्रुटि कोड, त्रुटि संदेश और सर्वर पक्ष से लपेटा प्रतिक्रिया ऑब्जेक्ट शामिल है, यानी।मेरी पिछली टिप्पणी पर

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}; 
    } 

    ... ... 
} 

एक सुधार: प्रभाव क्लास और प्रसारण-क्लास, यदि आप एक ही क्रिया प्रकार पर प्रतिक्रिया प्रभाव वर्ग और प्रसारण स्तरीय दोनों है, प्रसारण स्तरीय पहली प्रतिक्रिया करेंगे, और फिर प्रभाव -कक्षा। यहां एक उदाहरण दिया गया है: एक घटक में एक बार क्लिक किया जाता है, जिसे एक बार क्लिक किया जाता है, जिसे कहा जाता है: this.store.dispatch(this.clientActions.effectChain(1)); जिसे effectChainReducer द्वारा संभाला जाएगा, और फिर ClientEffects.chainEffects$, जो 1 से 2 तक पेलोड बढ़ाता है; 500 एमएस के लिए प्रतीक्षा एक और कार्रवाई फेंकना: this.clientActions.effectChain(2), के बाद पेलोड = 2 और उसके बाद ClientEffects.chainEffects$, जो 2 से 3 तक बढ़ जाती है, उत्सर्जन this.clientActions.effectChain(3), ..., जब तक यह 10 से अधिक है, ClientEffects.chainEffects$ का उत्सर्जन करता है this.clientActions.endEffectChain(), जो बदल जाता है के साथ effectChainReducer ने संभाला स्टोर राज्य 1000 से effectChainReducer के माध्यम से, अंत में यहां बंद हो जाता है।

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; 
     } 
    } 

चलाने के लिए ऊपर दिए गए कोड है, उत्पादन तरह दिखना चाहिए:

क्लाइंट-reducer.ts:

उत्पादन की तरह दिखना चाहिए 51 कम करने श्रृंखला के स्तर पर हैं: 1
क्लाइंट-effects.ts: 1
क्लाइंट-reducer.ts:: 72 प्रभाव श्रृंखला के स्तर पर हैं 2
क्लाइंट-effects.ts:: 51 कम करने श्रृंखला के स्तर पर हैं 72 प्रभाव श्रृंखला के स्तर पर हैं: 2
क्लाइंट-reducer.ts: 3
क्लाइंट-effects.ts:: 51 कम करने श्रृंखला के स्तर पर हैं 72 प्रभाव श्रृंखला के स्तर पर हैं: 3
... ...
क्लाइंट-reducer.ts: 51 कम करने श्रृंखला के स्तर पर हैं: 10
क्लाइंट-effects.ts: 72 प्रभाव श्रृंखला के स्तर पर कर रहे हैं: 10

यह इंगित करता है कम करने प्रभाव से पहले पहले से चलाता है, प्रभाव-क्लास के बाद इंटरसेप्टर, नहीं पूर्व इंटरसेप्टर है। प्रवाह आरेख देखें: enter image description here

+0

यह एक उत्कृष्ट जवाब है! एक सवाल - आपने लिखा "त्रुटि फेंकने के बाद स्ट्रीम मर गई है" - त्रुटि के बाद यह क्यों मर जाएगा? किसी अन्य मामले में, कृपया मेरा प्रश्न यहां देखें http://codereview.stackexchange.com/questions/141969/designing-redux-state-tree –

+0

अपडेट के लिए धन्यवाद! –

+0

अद्भुत जवाब! एक बात अभी भी मुझे स्पष्ट नहीं है। तो कल्पना करें कि मेरे पास एक पृष्ठ पर एक बटन है जो उपयोगकर्ता द्वारा दर्ज किए गए कुछ डेटा को बचाता है। मैं SOME_DATA_SAVED क्रिया को प्रेषित करता हूं जो प्रभाव से पकड़ा जाता है (reducers इसे नहीं सुनते हैं), तो यह इसे सहेजने का प्रयास करता है। यदि यह सफल होता है तो SOME_DATA_SAVED_OK प्रेषित किया जाता है (त्रुटि केस समान रूप से) और एक reducer इसे पकड़ता है और एक स्टोर में नया डेटा बचाता है। यूआई से हालांकि, मुझे सहेजने या त्रुटि के दौरान सफलता प्रदर्शित करने का एक उचित तरीका नहीं दिख रहा है। सहेजने के बाद बटन क्लिक करने के बाद मैं SOME_DATA_SAVED_OK/ERROR पर सब्सक्राइब कर सकता हूं, लेकिन यह बहुत अजीब लगता है – eddyP23

संबंधित मुद्दे