velopert / react-tutorial

벨로퍼트와 함께하는 모던 리액트 튜토리얼 문서
https://react.vlpt.us/
347 stars 98 forks source link

6.타입스크립트에서 리덕스 미들웨어 사용하기 (redux-thunk, redux-saga) · GitBook #39

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

6.타입스크립트에서 리덕스 미들웨어 사용하기 (redux-thunk, redux-saga) · GitBook

https://react.vlpt.us/using-typescript/06-ts-redux-middleware.html

sjung7674 commented 3 years ago

saga 리팩토링 시 typesafe-actions 버전 5.1.0 에서 AsyncActionCreator 가 AsyncActionCreatorBuilder로 rename 되었습니다.

rename 하면

Type '[T1, P1]' does not satisfy the constraint '[T1, [T1, P1] extends [T1, [any, any]] ? ([any, any] & P1)[0] : P1] | [T1, [[T1, P1] extends [T1, [any, any]] ? ([any, any] & P1)[0] : P1, [...] extends [...] ? ([...] & P1)[1] : never]] | [...] | [...]'.
  Type '[T1, P1]' is not assignable to type '[T1, [T1, P1] extends [T1, [any, any]] ? ([any, any] & P1)[0] : P1]'.
    Type 'P1' is not assignable to type '[T1, P1] extends [T1, [any, any]] ? ([any, any] & P1)[0] : P1'.

라는 에러가 발생하는데 AsyncActionCreatorBuilder 제너릭스 배열에서 P1, P2, P3의 타입을 지정해줘야 에러가 안나는것같습니다..

export default function createAsyncSaga<T1, P1, T2, P2, T3, P3>(
  asyncActionCreator: AsyncActionCreatorBuilder<
    [T1, string/* 이 부분 */],
    [T2, GithubProfile/* 이 부분 */],
    [T3, AxiosError/* 이 부분 */]
  >,
  promiseCreator: PromiseCreatorFunction<P1, P2>
) {
  return function* saga(action: ReturnType<typeof asyncActionCreator.request>) {
    try {
      const result = isPayloadAction<P1>(action)
        ? yield call(promiseCreator, action.payload)
        : yield call(promiseCreator);
      yield put(asyncActionCreator.success(result));
    } catch (e) {
      yield put(asyncActionCreator.failure(e));
    }
  };
}

다른 더 좋은 방법이 있다면 댓글 달아 주세요.

cjyoung414 commented 3 years ago

아래와 같은 방법이 있네요 (동작 확인 함)

export default function createAsyncSaga<T1, P1, T2, P2, T3, P3>(
  asyncActionCreator: AsyncActionCreatorBuilder<
    [T1, [P1, undefined]],
    [T2, [P2, undefined]],
    [T3, [P3, undefined]]
    ...
syi0808 commented 3 years ago

안녕하세요 액션을 리팩토링 하고 thunks.ts를 수정하는 과정에서 TypeScript error in D:/react-practice/redux-ts/react-redux-ts/src/modules/github/thunks.ts(8,53):
Argument of type '{ request: EmptyActionCreator<"github/GET_USER_PROFILE">; success: PayloadActionCreator<"github/GET_USER_PROFILE_SUCCESS", GithubProfile>; failure: PayloadActionCreator<...>; }' is not assignable to parameter of type '{ request: (...args: any) => PayloadAction<any, any>; success: (...args: any) => PayloadAction<any, any>; failure: (...args: any) => PayloadAction<any, any>; }'.
The types returned by 'request(...)' are incompatible between these types. Property 'payload' is missing in type 'EmptyAction<"github/GET_USER_PROFILE">' but required in type 'PayloadAction<any, any>'. TS2345 이라는 오류가 뜨는데 몇 시간째 해메네요 도움 부탁드립니다.

syi0808 commented 3 years ago

안녕하세요 saga 리팩토링 도중 import { call, put } from 'redux-saga/effects' import { AsyncActionCreatorBuilder, PayloadAction } from 'typesafe-actions' type PromiseCreatorFunction<P, T> = ((payload: P) => Promise | (() => Promise))

function isPayloadAction

(action: any): action is PayloadAction<string, P> { return action.payload !== undefined }

export default function createAsyncSaga<T1, P1, T2, P2, T3, P3>( asyncActionCreator: AsyncActionCreatorBuilder<[T1, [P1, undefined]], [T2, [P2, undefined]], [T3, [P3, undefined]]>, promiseCreator: PromiseCreatorFunction<P1, P2> ) { return function* saga(action: ReturnType) { try { const result = isPayloadAction(action) ? yield call(promiseCreator, action.payload) : yield call(promiseCreator); yield put(asyncActionCreator.success(result)) } catch (e) { yield put(asyncActionCreator.failure(e)) } } } 이런 오류가 뜨는데 도저히 모르겠습니다

wooogler commented 3 years ago

앞서 말하신 부분에서 에러가 나는 이유는 저희가 앞서 createAsyncAction에서 Request type을 정할 때 undefined로 하는 바람에 type inference가 EmptyActionCreator로 되어서 그런 것 같네요. 원래 AsyncActionCreatorBuilder로 만들어진 type은 PayloadActionCreator여야 하는데요. 저는 우선 createAsyncAction에서 Request type이 undefined로 되어 있는 걸 any로 바꾸니까 되네요.

jung-hunsoo commented 3 years ago

@sjung7674 Promise의 type을 특정 해 주면 createAsyncSaga()의 재사용이 불가능해져 refactoring의 의미가 사라집니다.

@cjyoung414 이 코드는 본문의 코드와 작동합니다. 다만 meta의 type을 undefined로 한정하기에, meta를 쓰는 코드에는 활용하지 못할 것 같습니다.

seniya commented 3 years ago

좋은 내용 잘 정리해 주셔서 감사합니다.

i0boy commented 3 years ago

https://github.com/devtaehyeok/velopert-ts-redux-study에 작동하는 최종 실습 코드를 냄겨두었으니 혹시 도움이 되실까 싶으시면 참조해 주세용

Darcyu83 commented 2 years ago

좋은글 감사합니다. yield call( func , params) Effect 타입오류나시는 분 참고하세요. (변수 및 타입명은 기억나는대로 임의로 적습니다. )

function getUserProfileSaga( action : {type: GET_USER_PROFILE, payload }) { try { //꺽쇠가 보안때문에 안적히네요 참고하세요. const uerInfo = yield call "<" typeof callAPI ">"(callAPI , action.payload) //여기서 payload는 username string yield put( 성공 액션 크리에이터 ) }catch{ yield put( 에러 액션 크리에이터 ) } }

ByoungSuk commented 1 year ago

thunk.ts

// import { ThunkAction } from 'redux-thunk'; // import { GithubAction } from './types'; import { RootState } from '..'; import { getUserProfile } from '../../api/github'; import { getUserProfileAsync } from './actions';

// 1. ThunkAction 사용한 경우 // export function getUserProfileThunk( // username: string // ): ThunkAction<void, RootState, null, GithubAction> { // return async (dispatch, state) => { // const { request, success, failure } = getUserProfileAsync; // dispatch(request()); // try { // const userProfile = await getUserProfile(username); // dispatch(success(userProfile)); // } catch (e: any) { // dispatch(failure(e)); // } // }; // }

// 2. ThunkAction 사용하지 않는 경우 export function getUserProfileThunk(username: string) { return async (dispatch: Dispatch, getState: () => RootState) => { // const state = getState(); const { request, success, failure } = getUserProfileAsync; dispatch(request()); try { const userProfile = await getUserProfile(username); dispatch(success(userProfile)); } catch (e: any) { dispatch(failure(e)); } }; }

ByoungSuk commented 1 year ago

GithubProfileInfo.tsx

import React from 'react'; import './GithubProfileInfo.css';

type GithubProfileInfoProps = { name: null; thumbnail: string; bio: null; blog: string; };

function GithubProfileInfo({ name, thumbnail, bio, blog, }: GithubProfileInfoProps) { return (

user thumbnail
{name}

{bio}

{blog !== '' && 블로그}

); }

export default GithubProfileInfo;

ByoungSuk commented 1 year ago

GithubProfileLoader.txt // import type {} from 'redux-thunk/extend-redux'; 추가 해야지 에러가 나지 않네요.

import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../modules'; import GithubUsernameForm from '../components/GithubUsernameForm'; import GithubProfileInfo from '../components/GithubProfileInfo'; import { getUserProfileThunk } from '../modules/github'; import type {} from 'redux-thunk/extend-redux';

const GithubProfileLoader = () => { const { data, loading, error } = useSelector( (state: RootState) => state.github.userProfile );

console.log('data', data); const dispatch = useDispatch();

const onSubmitUsername = (username: string) => { dispatch(getUserProfileThunk(username)); };

return ( <>

  {loading && <p style={{ textAlign: 'center' }}>로딩중..</p>}
  {error && <p style={{ textAlign: 'center' }}>에러 발생!</p>}
  {data && (
    <GithubProfileInfo
      bio={data.bio}
      blog={data.blog}
      name={data.name}
      thumbnail={data.avatar_url}
    />
  )}
</>

); };

export default GithubProfileLoader;

ByoungSuk commented 1 year ago

AsyncActionCreator -> AsyncActionCreatorBuilder

ByoungSuk commented 1 year ago

action.ts 에 에러가 나는 경우 타입으로 ... undefined 를 any로 변경 ...

import { createAsyncAction } from 'typesafe-actions'; import { GithubProfile } from '../../api/github'; import { AxiosError } from 'axios';

export const GET_USER_PROFILE = 'github/GET_USER_PROFILE'; export const GET_USER_PROFILE_SUCCESS = 'github/GET_USER_PROFILE_SUCCESS'; export const GET_USER_PROFILE_ERROR = 'github/GET_USER_PROFILE_ERROR';

export const getUserProfileAsync = createAsyncAction( GET_USER_PROFILE, GET_USER_PROFILE_SUCCESS, GET_USER_PROFILE_ERROR )<any, GithubProfile, AxiosError>(); // undefined 에러시 조치 any로 변경

ByoungSuk commented 1 year ago

saga refactoring 시 소스

import { AsyncActionCreatorBuilder, PayloadAction } from 'typesafe-actions'; import { GithubProfile } from '../api/github'; import { AxiosError } from 'axios'; import { call, put } from 'redux-saga/effects'; type PromiseCreatorFunction<P, T> = | ((payload: P) => Promise) | (() => Promise); function isPayloadAction(action: any): action is PayloadAction<string, any> { return action.payload !== undefined; } export default function createAsyncSaga<T1, P1, T2, P2, T3>( asyncActionCreator: AsyncActionCreatorBuilder< [T1, string / 이 부분 /], [T2, GithubProfile / 이 부분 /], [T3, AxiosError / 이 부분 /]

, promiseCreator: PromiseCreatorFunction<P1, P2> ) { return function* saga(action: ReturnType) { try { const result: GithubProfile = isPayloadAction(action) ? yield call(promiseCreator, action.payload) : yield call(promiseCreator); yield put(asyncActionCreator.success(result)); } catch (error: any) { yield put(asyncActionCreator.failure(error)); } }; }