React Hooks 및 TypeScript 작업

게시 됨: 2022-03-11

Hooks는 코드 가독성을 향상시키기 위해 2019년 2월 React에 도입되었습니다. 이전 기사에서 React hooks에 대해 이미 논의했지만 이번에는 hooks가 TypeScript와 어떻게 작동하는지 조사합니다.

후크 이전에는 React 구성 요소에 두 가지 특징이 있었습니다.

  • 상태를 처리하는 클래스
  • props에 의해 완전히 정의된 함수

이것들의 자연스러운 사용은 클래스가 있는 복잡한 컨테이너 구성 요소와 순수한 기능을 가진 단순한 프리젠테이션 구성 요소를 만드는 것이었습니다.

React Hook이란 무엇입니까?

컨테이너 구성 요소는 상태 관리 및 서버에 대한 요청을 처리합니다. 그러면 이 문서에서 사이드 이펙트 라고 합니다. 상태는 props를 통해 컨테이너 자식으로 전파됩니다.

React Hook이란 무엇입니까?

그러나 코드가 성장함에 따라 기능적 구성 요소는 컨테이너 구성 요소로 변형되는 경향이 있습니다.

기능적 구성 요소를 더 똑똑한 구성 요소로 업그레이드하는 것은 그렇게 고통스러운 일은 아니지만 시간이 많이 걸리고 불쾌한 작업입니다. 또한 발표자와 컨테이너를 매우 엄격하게 구분하는 것은 더 이상 환영받지 못합니다.

후크는 두 가지를 모두 수행할 수 있으므로 결과 코드가 더 균일하고 거의 모든 이점이 있습니다. 다음은 인용 서명을 처리하는 작은 구성 요소에 로컬 상태를 추가하는 예입니다.

 // put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }

여기에는 큰 보너스가 있습니다. TypeScript 를 사용한 코딩은 Angular에서는 훌륭했지만 React에서는 부풀려졌습니다. 그러나 TypeScript로 React 후크를 코딩하는 것은 즐거운 경험입니다.

이전 React가 있는 TypeScript

TypeScript는 Microsoft에서 설계했으며 React가 Flow 를 개발할 때 Angular 경로를 따랐습니다. 순진한 TypeScript로 React 클래스를 작성하는 것은 많은 키가 같더라도 React 개발자가 propsstate 를 모두 입력해야 했기 때문에 상당히 고통스러웠습니다.

다음은 간단한 도메인 개체입니다. 우리는 state 및 props가 있는 일부 crud 구성 요소에서 관리되는 Quotation 유형의 견적 을 만듭니다. Quotation 을 생성할 수 있으며 관련 상태가 서명됨으로 변경될 수 있습니다.

 interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }

그러나 이제 QuotationPage가 서버에 ID 를 요청한다고 상상해 보십시오. 예를 들어 회사에서 만든 678번째 인용문입니다. 글쎄, 그것은 QuotationProps가 그 중요한 숫자를 알지 못한다는 것을 의미합니다. 그것은 Quotation을 정확하게 래핑하지 않습니다. QuotationProps 인터페이스에서 훨씬 더 많은 코드를 선언해야 합니다.

 interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }

id를 제외한 모든 속성을 새 유형으로 복사합니다. 흠. 그것은 많은 DTO를 작성하는 것과 관련된 오래된 Java를 생각하게 합니다. 그것을 극복하기 위해 우리는 고통을 우회하기 위해 TypeScript 지식을 늘릴 것입니다.

후크가 있는 TypeScript의 이점

후크를 사용하여 이전 QuotationState 인터페이스를 제거할 수 있습니다. 이를 위해 QuotationState를 상태의 두 부분으로 나눕니다.

 interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

상태를 분할하면 새 인터페이스를 만들 필요가 없습니다. 로컬 상태 유형은 종종 기본 상태 값으로 유추됩니다.

후크가 있는 구성 요소는 모두 함수입니다. 따라서 React 라이브러리에 정의된 FC<P> 유형을 반환하는 동일한 구성 요소를 작성할 수 있습니다. 함수는 props 유형에 따라 설정하여 반환 유형을 명시적으로 선언합니다.

 const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

분명히 React 후크와 함께 TypeScript를 사용하는 것이 React 클래스와 함께 사용하는 것보다 쉽습니다. 강력한 타이핑은 코드 안전성을 위한 귀중한 보안이므로 새 프로젝트에서 후크를 사용하는 경우 TypeScript 사용을 고려해야 합니다. TypeScript를 원한다면 확실히 후크를 사용해야 합니다.

React를 사용하든 사용하지 않든 TypeScript를 피할 수 있는 여러 가지 이유가 있습니다. 그러나 사용하기로 결정했다면 후크도 사용해야 합니다.

Hooks에 적합한 TypeScript의 특정 기능

이전 React hooks TypeScript 예제에서 나는 여전히 QuotationProps에 number 속성을 가지고 있지만 그 숫자가 실제로 무엇인지에 대한 단서는 아직 없습니다.

TypeScript는 유틸리티 유형의 긴 목록을 제공하며 그 중 3개는 많은 인터페이스 설명의 노이즈를 줄여서 React에서 도움이 됩니다.

  • Partial<T> : T의 모든 하위 키
  • Omit<T, 'x'> : 키 x 를 제외한 T의 모든 키
  • Pick<T, 'x', 'y', 'z'> : T 에서 정확히 x, y, z

Hooks에 적합한 TypeScript의 특정 기능

우리의 경우 Omit<Quotation, 'id'> 가 인용의 id를 생략하기를 원합니다. type 키워드를 사용하여 즉석에서 새 유형을 만들 수 있습니다.

Partial<T>Omit<T> 는 Java와 같은 대부분의 유형이 지정된 언어에는 존재하지 않지만 프론트 엔드 개발에서 Forms를 사용하는 예제에 많은 도움이 됩니다. 타이핑의 부담을 덜어줍니다.

 type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }

이제 ID가 없는 견적이 있습니다. 그래서 우리는 Quotation 을 디자인할 수 있고 PersistedQuotation extends Quotation 을 확장합니다. 또한 반복되는 if 또는 undefined 문제를 쉽게 해결할 것입니다. 전체 객체는 아니지만 여전히 변수 인용문을 호출해야 합니까? 그것은 이 기사의 범위를 벗어나지만 어쨌든 나중에 언급할 것입니다.

그러나 이제 우리는 number 가 있다고 생각한 객체를 퍼뜨리지 않을 것이라고 확신합니다. Partial<T> 를 사용한다고 해서 이러한 모든 보장이 제공되는 것은 아니므로 신중하게 사용하십시오.

Pick<T, 'x'|'y'> 는 새 인터페이스를 선언할 필요 없이 즉석에서 형식을 선언하는 또 다른 방법입니다. 구성 요소인 경우 견적 제목을 편집하기만 하면 됩니다.

 type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

또는 그냥:

 function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

저를 판단하지 마십시오. 저는 Domain Driven Design의 열렬한 팬입니다. 새 인터페이스에 대해 두 줄을 더 작성하고 싶지 않을 정도로 게으르지 않습니다. 나는 도메인 이름을 정확하게 설명하기 위해 인터페이스를 사용하고 노이즈를 피하면서 로컬 코드 정확성을 위해 이러한 유틸리티 기능을 사용합니다. 독자는 Quotation 이 표준 인터페이스라는 것을 알게 될 것입니다.

React Hooks의 다른 이점

React 팀은 항상 React를 기능적 프레임워크로 보고 처리했습니다. 그들은 구성 요소가 자체 상태를 처리할 수 있도록 클래스를 사용했으며 이제는 기능이 구성 요소 상태를 추적할 수 있도록 하는 기술로 후크합니다.

 interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }

다음은 Partial 을 사용하는 것이 안전하고 좋은 선택인 경우입니다.

함수를 여러 번 실행할 수 있지만 연결된 useReducer 후크는 한 번만 생성됩니다.

컴포넌트에서 리듀서 함수를 자연스럽게 추출함으로써, 코드는 클래스 내부의 여러 함수 대신 여러 독립 함수로 나눌 수 있으며 모두 클래스 내부의 상태에 연결됩니다.

이것은 테스트 가능성을 위해 분명히 더 좋습니다. 일부 기능은 JSX를 처리하고 다른 기능은 동작을 처리하고 다른 기능은 비즈니스 로직을 처리하는 등입니다.

당신은 (거의) 더 이상 고차 구성 요소가 필요하지 않습니다. Render props 패턴은 함수로 작성하는 것이 더 쉽습니다.

따라서 코드를 읽는 것이 더 쉽습니다. 코드는 클래스/함수/패턴의 흐름이 아니라 함수의 흐름입니다. 그러나 함수가 개체에 연결되어 있지 않기 때문에 이러한 모든 함수의 이름을 지정하기 어려울 수 있습니다.

TypeScript는 여전히 JavaScript입니다

JavaScript는 어떤 방향으로든 코드를 찢을 수 있기 때문에 재미있습니다. TypeScript를 사용하면 여전히 keyof 를 사용하여 개체의 키로 재생할 수 있습니다. 유형 공용체를 사용하여 읽을 수 없고 유지 관리할 수 없는 것을 만들 수 있습니다. 유형 별칭을 사용하여 문자열이 UUID인 것처럼 가장할 수 있습니다.

그러나 null 안전으로 할 수 있습니다. tsconfig.json"strict":true 옵션이 있는지 확인하십시오. 프로젝트 시작 전에 확인하십시오. 그렇지 않으면 거의 모든 라인을 리팩토링해야 합니다!

코드에 입력하는 수준에 대한 논쟁이 있습니다. 모든 것을 입력하거나 컴파일러가 유형을 유추하도록 할 수 있습니다. 린터 구성 및 팀 선택에 따라 다릅니다.

또한 여전히 런타임 오류를 만들 수 있습니다! TypeScript는 Java보다 간단하고 Generics와의 공분산/반공분 문제를 방지합니다.

이 Animal/Cat 예제에는 Cat 목록과 동일한 Animal 목록이 있습니다. 불행히도, 그것은 두 번째가 아니라 첫 번째 줄에 있는 계약입니다. 그런 다음 Animal 목록에 오리를 추가하므로 Cat 목록은 false입니다.

 interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }

TypeScript에는 단순하고 JavaScript 개발자 채택에 도움이 되는 제네릭에 대한 이변량 접근 방식만 있습니다. 변수 이름을 올바르게 지정하면 listOfCatsduck 를 추가하는 경우가 거의 없습니다.

또한, 공분산과 반공변에 대한 계약을 추가하고 빼는 제안이 있습니다.

결론

나는 최근에 좋은 타이핑 언어인 Kotlin으로 돌아왔습니다. 복잡한 제네릭을 올바르게 타이핑하는 것은 복잡했습니다. 오리 타이핑과 이변량 접근 방식의 단순성 때문에 TypeScript에는 일반적인 복잡성이 없지만 다른 문제가 있습니다. 아마도 TypeScript와 함께 설계되고 TypeScript를 위해 설계된 Angular에서는 많은 문제가 발생하지 않았지만 React 클래스에서는 문제가 있었습니다.

TypeScript는 아마도 2019년의 가장 큰 승자일 것입니다. React를 얻었지만 Node.js와 아주 간단한 선언 파일로 오래된 라이브러리를 입력하는 기능으로 백엔드 세계를 정복하고 있습니다. 일부는 ReasonML을 사용하지만 일부는 Flow를 묻고 있습니다.

이제 hook+TypeScript가 Angular보다 더 즐겁고 생산적이라고 생각합니다. 6개월 전에는 그런 생각을 하지 않았을 것이다.