사촌들 잘 놀다: 실험 NgRx 상점과 RTK 조회

45698 단어 reduxngrxangular
Redux은 상태 관리를 제공한다. 오랫동안 이 관리는 다양한 웹 생태계에서 널리 사용되었다.NgRx은 Redux 모델을 기반으로 하는 각도 생태계의 상태와 부작용을 관리하기 위해 더욱 자기 의견을 고집하고 배터리를 포함하는 프레임워크를 제공한다.Redux Toolkit은 Redux 사용자에게 동일한 배터리 포함 방법을 제공하여 설정 상태 관리와 부작용을 편리하게 합니다.Redux Toolkit(RTK)팀은 최근 "웹 응용 프로그램에 데이터를 로드하는 일반적인 상황을 단순화하기 위한 고급 데이터 획득 및 캐시 도구"라며 Redux Toolkit과 Redux 내부 위에 구축한 RTK Query을 발표했다.RTK Query 문서를 처음 읽었을 때 다음과 같은 사항에 관심이 생겼습니다.
  • 단순 유효
  • Redux
  • 기반 구축
  • TypeScript
  • 을 사용한 개발자 경험을 위한 설계
    RTK 조회도 프레임워크와 무관하며, 이것은 모든 프레임워크에 집적할 수 있다는 것을 의미한다.NgRx는 Redux 기반의 Angular 상태 관리 프레임워크입니다.그래서 그들은 함께 일해야 한다, 그렇지?이 글은 그들이 얼마나 잘 협력했는지, 그리고 내가 실험에서 무엇을 배웠는지 탐구했다.

    종속성 설정


    Angular 애플리케이션에서 RTK 쿼리를 사용하려면 React 패키지를 설치해야 합니다.나는 유니버설 React와 Redux 패키지를 설치했다.
    npm install @reduxjs/toolkit @rtk-incubator/rtk-query react react-dom react-redux
    
    혹은
    yarn add @reduxjs/toolkit @rtk-incubator/rtk-query react react-dom react-redux
    
    더 많은 유형의 정보를 얻기 위해 @types/react-redux 패키지를 설치했습니다.
    npm install @types/react-redux --only=dev
    
    혹은
    yarn add @types/react-redux --dev
    

    청중 설정


    RTK 쿼리를 사용하면 스누퍼가 포커스에서 데이터를 다시 추출하고 오프라인으로 다시 연결할 때 데이터를 다시 추출할 수 있습니다.이것은 setupListeners 함수를 사용하여 Store.dispatch 방법을 그에게 전달하는 것과 관련이 있다.
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    
    import { Store, StoreModule } from '@ngrx/store';
    import { EffectsModule } from '@ngrx/effects';
    import { StoreDevtoolsModule } from '@ngrx/store-devtools';
    
    import { setupListeners } from '@rtk-incubator/rtk-query';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { environment } from '../environments/environment';
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        StoreModule.forRoot({}),
        EffectsModule.forRoot([]),
        StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production })
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {
      constructor(store: Store) {
        setupListeners(store.dispatch.bind(store));
      }
    }
    
    주의: 전체 실험 과정에서 Store 실례의 작용역을 잃지 않기 위해 나는 반드시 .bind 문법을 사용해야 한다.

    제품 라인 생성


    기능 페이지에 대해, 나는 로드가 지연되는 제품 모듈과 제품 구성 요소를 생성했다.
    ng g module products --route products --module app
    

    API 서비스 설정


    RTK 쿼리에서는 상태 슬라이스의 모든 데이터 가져오기 및 상태 작업을 처리하기 위해 API 서비스를 만듭니다.이러한 서로 다른 방법은 생성된 서비스를 통해 endpoints으로 정의된다.나는 Product 인터페이스를 정의했고 productsApi 함수를 사용하여 createApi 인터페이스를 정의했다.
    import { createSelector } from '@reduxjs/toolkit';
    import { createApi, fetchBaseQuery } from '@rtk-incubator/rtk-query';
    
    export interface Product {
      id: number;
      name: string;
    }
    
    export const productsApi = createApi({
      reducerPath: 'products',
      entityTypes: ['Products'],
      baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3000/' }),
      endpoints: (builder) => ({
        getProducts: builder.query<Product[], string>({
          query: () => 'products',
          provides: (result) => result.map(({ id }) => ({ type: 'Products', id }))
        }),
        addProduct: builder.mutation<Product, Partial<Product>>({
          query: (body) => ({
            url: `products`,
            method: 'POST',
            body,
          }),
          invalidates: ['Products']
        }),
      }),
    });
    
    참고: 이 예에서 사용된 json-server 패키지에 빠른 API를 설치했습니다.
    엔드포인트 사용 빌더 모드는 http://localhost:3000/products에 API URL 경로, API 조회, 돌연변이 및 캐시 실효를 제공합니다.React에서 갈고리로 추상적인 Products의 용법을 제공했는데 이 갈고리는 조작과 선택기를 포함하지만 React 이외의 용법을 제공한다.endpoints 대상은 productsApi이라는 기능 키와 reducerPath 함수 자체를 포함한 다른 속성도 포함한다.

    행동을 정의하다

    reducerproductsApi 속성에 정의된 방법을 포함하는데 이런 방법은 본질적으로 동작 창설자이다.
    export const loadProducts = () => productsApi.endpoints.getProducts.initiate('');
    export const addProduct = (product: Product) => productsApi.endpoints.addProduct.initiate(product);
    
    endPoints 방법은 전통적인 동작 창설자처럼 보이는 내용을 되돌려주지만 실제로는 RTK 조회 중간부품에서 사용하는'Thunks'다.

    선택기 정의


    선택기도 생성된 initiate의 정의를 사용한다.endpoints은 전체 상태 자체로 돌아간다.이는 NgRx 스토어의 getProducts 기능과 원활하게 통합되어 제품에 대한 또 다른 선택기를 구축합니다.
    export const selectProductsState = productsApi.endpoints.getProducts.select('');
    export const selectAllProducts = createSelector(
      selectProductsState,
      state => state.data || []
    );
    
    RTK 쿼리는 createSelector, isLoading, isErrorisSuccess 등의 다른 정보를 얻기 위한 선택기를 제공합니다.

    악극을 다루다


    내가 만난 첫 번째 장애는 NgRx 상점과 Redux가 의견 차이를 보이기 시작했다는 것이다.Redux 스토리지는 상태 스냅샷을 가져오고 Redux Thunk를 처리하는 동기화 방법을 제공합니다.Redux Thunk 문서
    "Thunks는 기본 Redux 부작용 논리의 추천 중간부품으로 메모리에 접근해야 하는 복잡한 동기화 논리와 간단한 비동기 논리, 예를 들어 AJAX 요청 등이 포함됩니다."
    이 API를 복제하기 위해 ThunkService을 만들었습니다. 이것은 Redux 중간부품을 사용하는 데 필요한 같은 API를 제공합니다.
    import { Injectable } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { MiddlewareAPI, ThunkAction } from '@reduxjs/toolkit';
    import { SubscriptionLike } from 'rxjs';
    import { take } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root',
    })
    export class ThunkService {
      constructor(private store: Store) {}
    
      getState() {
        let state: object;
    
        this.store.pipe(take(1)).subscribe((res) => {
          state = res;
        });
    
        return state;
      }
    
      dispatch(thunkAction: ThunkAction<any, any, any, any>): SubscriptionLike {
        return thunkAction(
          (thunk: ThunkAction<any, any, any, any>) =>
            thunk(this.store.dispatch.bind(this.store), this.getState.bind(this), undefined),
          this.getState.bind(this),
          undefined
        );
      }
    
      runThunk(thunk: ThunkAction<any, any, any, any>) {
        return thunk(
          this.store.dispatch.bind(this.store),
          this.getState.bind(this),
          undefined
        );
      }
    
      middlewareApi(): MiddlewareAPI {
        return {
          dispatch: this.runThunk.bind(this),
          getState: this.getState.bind(this),
        };
      }
    }
    
    getState 방법은 저장된 스냅샷을 제공하고 dispatch 방법은 제공된'thunks'비동기 스케줄링 조작을 허용한다.

    연결 미들웨어


    Redux 중간부품을 사용하기 위해서 상점에 연결해야 합니다.나는 마침내 메타 리듀서를 사용했다.기본적으로, 메타데이터 약간기는 하나의 Reducer 함수를 수신하고 다른 Reducer 함수를 되돌려주는 방법일 뿐, 이 함수는 NgRx 저장소에서 '중간부품' 을 충당한다.InjectionToken을 사용하지 않으면 의존항 주입에 접근할 수 없기 때문에 저는 제품 기능 상태 설정을 위해 InjectionToken을 만들었습니다.
    export const PRODUCTS_FEATURE_CONFIG_TOKEN = new InjectionToken<StoreConfig<any>>('Products Feature Config');
    
    그리고 ThunkService을 사용하려면factory 함수를 만들었고 productsApi이 제공하는 중간부품을 사용하려면metareducer를 만들었습니다.등록 후 이 메타데이터 축소기는 products 상태 슬라이스에만 적용됩니다.
    export function productsMiddleware(dispatcher: ThunkService) {
      return function(reducer: ActionReducer<any>): ActionReducer<any> {
        return function(state, action) {
          const next = productsApi.middleware(dispatcher.middlewareApi());
          const nextState = next(action => reducer(state, action));
    
          return nextState(action);
        };
      }
    }
    
    productsMiddleware 함수로 정의된 원약간을 되돌려주는 공장 함수가 필요합니다.
    export function getProductsFeatureConfig(thunkService: ThunkService): StoreConfig<any> {
      return { 
        metaReducers: [productsMiddleware(thunkService)]
      };
    }
    
    제품 기능 구성을 등록하려면 공급업체가 필요합니다.
    export function provideProductsFeatureConfig() {
      return [
        {
          provide: PRODUCTS_FEATURE_CONFIG_TOKEN,
          deps: [ThunkService],
          useFactory: getProductsFeatureConfig,
        }
      ];
    }
    

    등록 기능 상태


    기능 상태는 setup이고 익숙한 ProductsModule 문법을 사용하여 StoreModule.forFeature()에 등록합니다.
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { ReactiveFormsModule } from '@angular/forms';
    import { StoreModule } from '@ngrx/store';
    
    import { ProductsRoutingModule } from './products-routing.module';
    import { ProductsComponent } from './products.component';
    
    import { PRODUCTS_FEATURE_CONFIG_TOKEN, productsApi, provideProductsFeatureConfig } from '../services/products';
    
    @NgModule({
      declarations: [ProductsComponent],
      imports: [
        CommonModule,
        ReactiveFormsModule,
        ProductsRoutingModule,
        StoreModule.forFeature(productsApi.reducerPath, productsApi.reducer, PRODUCTS_FEATURE_CONFIG_TOKEN)
      ],
      providers: [
        provideProductsFeatureConfig()
      ],
    })
    export class ProductsModule { }
    
    productsApi.reducerPath은 API 서비스에서 정의한 products으로 Reducer 함수를 완전히 생성하고metareducer를 등록할 때 PRODUCTS_FEATURE_CONFIG_TOKEN 영패와 provideProductsFeatureConfig 방법으로 등록 공급자가 방문 의존항으로 주입한다.

    제품 추가

    Product을 추가하기 위해서, 나는 리액션 폼을 사용하여 폼을 제출한 후에 제품을 출력하기 위해 작은 폼 구성 요소를 신속하게 조립했다.
    import { Component, OnInit, EventEmitter, Output } from '@angular/core';
    import { FormControl, FormGroup } from '@angular/forms';
    
    @Component({
      selector: 'app-product-form',
      template: `
        <form [formGroup]="productForm" (ngSubmit)="addProduct()">
          <div>
            Name: <input formControlName="name" type="text">
          </div>
    
          <div>
            Description: <input formControlName="description" type="text">
          </div>
    
          <button type="submit">Add Product</button>
        </form>
      `
    })
    export class ProductFormComponent implements OnInit {
      productForm = new FormGroup({
        name: new FormControl(''),
        description: new FormControl('')
      });
    
      @Output() submitted = new EventEmitter();
    
      constructor() { }
    
      ngOnInit(): void {
      }
    
      addProduct() {
        this.submitted.emit(this.productForm.value);
      }
    }
    
    ProductsFormComponent 템플릿에서 사용할 수 있도록 ProductsComponent을 등록했습니다.
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { ReactiveFormsModule } from '@angular/forms';
    import { StoreModule } from '@ngrx/store';
    
    import { ProductsRoutingModule } from './products-routing.module';
    import { ProductsComponent } from './products.component';
    import { ProductFormComponent } from './product-form.component';
    
    import { PRODUCTS_FEATURE_CONFIG_TOKEN, productsApi, provideProductsFeatureConfig } from '../services/products';
    
    
    @NgModule({
      declarations: [ProductsComponent, ProductFormComponent],
      imports: [
        CommonModule,
        ReactiveFormsModule,
        ProductsRoutingModule,
        StoreModule.forFeature(productsApi.reducerPath, productsApi.reducer, PRODUCTS_FEATURE_CONFIG_TOKEN)
      ],
      providers: [
        provideProductsFeatureConfig()
      ],
    })
    export class ProductsModule { }
    

    제품 가져오기 및 추가


    이 상태가 모두 연결되어 있기 때문에 ProductsComponent에서 이 상태를 사용했습니다.나는 app-product-form을 사용해서 상점에서 고른 제품을 열거했다.RTK 쿼리 중간부품과 함께 사용하는 Action Thunks의 스케줄러로 ThunkService을 주입했습니다.
    import { Component, OnDestroy, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { SubscriptionLike } from 'rxjs';
    import * as uuid from 'uuid';
    
    import { Product } from '../models/product';
    import { addProduct, loadProducts, selectAllProducts } from '../services/products';
    import { ThunkService } from '../services/thunk.service';
    
    @Component({
      selector: 'app-products',
      templateUrl: './products.component.html',
      styleUrls: ['./products.component.css']
    })
    export class ProductsComponent implements OnInit, OnDestroy {
      products$ = this.store.select(selectAllProducts);
      loadSub$: SubscriptionLike;
      addSub$: SubscriptionLike;
    
      constructor(private store: Store, private dispatcher: ThunkService) { }
    
      ngOnInit(): void {
        this.loadSub$ = this.dispatcher.dispatch(loadProducts());
      }
    
      onProductAdded(product: Product) {
        this.addSub$ = this.dispatcher.dispatch(addProduct({id: uuid.v4(), ...product}));
      }
    
      ngOnDestroy() {
        this.loadSub$.unsubscribe();
        this.addSub$.unsubscribe();
      }
    }
    
    products 선택기를 사용하여 selectAllProducts10dispatcher RTK 쿼리에서 제공하는 동작 Thunk을 전송합니다.만약 당신이 주의하지 않는다면, 나는 HttpClientModule 또는 NgRx Effects을 사용하여 어떠한 데이터를 얻거나 부작용을 일으키지 않을 것입니다.이러한 모든 동작은 RTK 조회를 통해 내부적으로 처리됩니다.Redux 개발 도구의 모든 변경 사항을 볼 수 있습니다.

    첫인상

  • RTK 쿼리는 NgRx 스토어와 잘 통합되어 있어 더욱 긴밀하게 통합될 수 있습니다.
  • 흥미로운 것은 기존의 Redux 중간부품은 Meta reducer를 통해 연결할 수 있고 조금만 노력하면 된다는 것이다.이는 NgRx 스토어에서 다른 Redux 중간부품을 사용할 가능성을 제공합니다.
  • 전체 예제를 보려면 GitHub Repo을 참조하십시오.
    당신은 어떻게 생각합니까?Angular 애플리케이션에서 RTK 쿼리를 사용하시겠습니까?아래에 글을 남기거나 트위터에 연락 주세요.
    참고: Saul Moro는 Angular, NgRx 및 RTK 질의에 대한 갈고리 스타일 라이브러리를 작성했습니다.재구매 계약 보기:
    https://github.com/SaulMoro/ngrx-rtk-query

    좋은 웹페이지 즐겨찾기