TypeScript로 유연한 구성 요소를 빌드하는 데 유용한 React API
게시 됨: 2022-03-10 React.createElement
를 직접 사용한 적이 있습니까? React.cloneElement
는 어떻습니까? React는 JSX를 HTML로 변환하는 것 이상입니다. 훨씬 더 많이 그리고 덜 알려진(그러나 매우 유용한) API에 대한 지식 수준을 높이는 데 도움을 주기 위해 React 라이브러리와 함께 제공됩니다. 구성 요소의 통합과 유용성을 크게 향상시킬 수 있는 몇 가지 사용 사례와 몇 가지 사용 사례를 살펴보겠습니다.
이 기사에서는 일반적으로 알려져 있지는 않지만 웹 개발자에게 매우 유용한 몇 가지 유용한 React API를 살펴보겠습니다. 독자는 React 및 JSX 구문에 대해 경험이 있어야 합니다. Typescript 지식은 도움이 되지만 필수는 아닙니다. 독자는 React 애플리케이션에서 사용할 때 React 구성 요소를 크게 향상시키기 위해 알아야 할 모든 것을 다룰 것입니다.
React.cloneElement
대부분의 개발자는 cloneElement
에 대해 들어본 적이 없거나 사용한 적이 없습니다. 현재 사용되지 않는 cloneWithProps
기능을 대체하기 위해 비교적 최근에 도입되었습니다. cloneElement
는 요소를 복제합니다. 또한 새 소품을 기존 요소와 병합하여 필요에 따라 수정하거나 재정의할 수 있습니다. 이는 기능적 구성 요소를 위한 세계적 수준의 API를 구축하기 위한 매우 강력한 옵션을 제공합니다. 서명을 살펴보십시오.
function cloneElement( element, props?, ...children)
압축된 Typescript 버전은 다음과 같습니다.
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
요소를 가져와 수정하고 자식을 재정의한 다음 새 요소로 반환할 수도 있습니다. 다음 예를 살펴보십시오. 링크의 TabBar 구성 요소를 만들고 싶다고 가정해 보겠습니다. 다음과 같이 보일 수 있습니다.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
TabBar는 링크 목록이지만 링크 제목과 URL이라는 두 가지 데이터를 정의하는 방법이 필요합니다. 따라서 우리는 이 정보와 함께 전달되는 데이터 구조를 원할 것입니다. 따라서 개발자는 구성 요소를 그렇게 만들 것입니다.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
이것은 훌륭하지만 사용자 a
요소 대신 button
요소를 렌더링하려면 어떻게 해야 할까요? 렌더할 요소의 유형을 구성 요소에 알려주는 또 다른 속성을 추가할 수 있습니다.
그러나 이것이 얼마나 빨리 다루기 힘든지 알 수 있습니다. 최대한의 유연성을 위해 다양한 사용 사례와 에지 사례를 처리하기 위해 점점 더 많은 속성을 지원해야 합니다.
React.cloneElement
를 사용하는 더 좋은 방법이 있습니다.
ReactNode
유형을 참조하도록 인터페이스를 변경하는 것으로 시작하겠습니다. 이것은 React가 렌더링할 수 있는 모든 것, 일반적으로 JSX Elements를 포괄하는 일반 유형이지만 문자열 및 null
일 수도 있습니다. 이는 React 구성 요소 또는 JSX를 인라인 인수로 수락하도록 지정하는 데 유용합니다.
export interface ITabbarProps { links: ReactNode[] }
이제 우리는 사용자에게 일부 React Elements를 제공하도록 요청하고 있으며 원하는 방식으로 렌더링할 것입니다.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
이것은 완벽하게 유효하며 요소를 렌더링합니다. 그러나 우리는 몇 가지를 잊고 있습니다. 하나는 key
! React가 목록을 효율적으로 렌더링할 수 있도록 키를 추가하고 싶습니다. 또한 요소를 변경하여 필요한 변환을 수행하여 className
등과 같은 스타일에 맞도록 하고 싶습니다.
우리는 이것을 React.cloneElement
로 할 수 있고 인자가 우리가 기대하는 것과 일치하는지 확인하기 위한 또 다른 함수 React.isValidElement
를 사용할 수 있습니다!
React.isValidElement
이 함수는 요소가 유효한 React 요소이고 React가 이를 렌더링할 수 있는 경우 true
를 반환합니다. 다음은 이전 예의 요소를 수정하는 예입니다.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
여기에서 전달하는 각 요소에 키 소품을 추가하고 모든 링크를 동시에 굵게 만듭니다! 이제 다음과 같이 임의의 React Elements를 소품으로 받아들일 수 있습니다.
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
요소에 설정된 모든 props를 재정의할 수 있으며 다양한 종류의 요소를 쉽게 수용하여 구성 요소를 훨씬 더 유연하고 사용하기 쉽게 만들 수 있습니다.
"
여기서 장점은 사용자 정의 onClick
핸들러를 버튼에 설정하려는 경우 그렇게 할 수 있다는 것입니다. React 요소 자체를 인수로 받아들이는 것은 구성 요소 디자인에 유연성을 제공하는 강력한 방법입니다.
useState
설정기 기능
후크를 사용하세요! useState
후크는 다음과 같이 구성 요소에 상태를 빠르게 구축하는 데 매우 유용하고 환상적인 API입니다.
const [myValue, setMyValue] = useState()
JavaScript 런타임으로 인해 약간의 딸꾹질이 있을 수 있습니다. 폐쇄를 기억하십니까?
일반적으로 for-loop 또는 비동기 이벤트와 같이 특정 상황에서 변수가 있는 컨텍스트로 인해 변수가 올바른 값이 아닐 수 있습니다. 이것은 어휘 범위 때문입니다. 새 함수가 생성되면 어휘 범위가 유지됩니다. 새로운 함수가 없기 때문에 newVal
의 어휘 범위가 유지되지 않으므로 값은 실제로 사용되는 시점에서 역참조됩니다.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
당신이해야 할 일은 setter를 함수로 활용하는 것입니다. 새로운 함수를 생성함으로써 변수 참조는 어휘 범위에서 보존되고 currentVal은 React useState Hook 자체에 의해 전달됩니다.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
이렇게 하면 setter 함수가 올바른 컨텍스트에서 호출되기 때문에 값이 올바르게 업데이트됩니다. 여기서 React가 하는 일은 React 상태 업데이트가 발생하도록 올바른 컨텍스트에서 함수를 호출하는 것입니다. 이것은 현재 값에 대해 조치를 취하는 데 도움이 되는 다른 상황에서도 사용할 수 있습니다. React는 첫 번째 인수를 현재 값으로 사용하여 함수를 호출합니다.
참고 : 비동기 및 클로저 주제에 대한 추가 정보를 보려면 Kent C. Dodds의 " useState
Lazy Initialization And Function Updates"를 읽는 것이 좋습니다.
JSX 인라인 함수
다음은 JSX 인라인 함수의 Codepen 데모입니다.
정확히 말하면 React API는 아닙니다.
JSX는 인라인 함수를 지원하며 JSX 요소를 반환하는 한 인라인 변수를 사용하여 간단한 논리를 선언하는 데 정말 유용할 수 있습니다.
"
다음은 예입니다.
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
여기에서 JSX 내부의 코드를 선언하고 임의의 코드를 실행할 수 있으며 렌더링할 JSX 함수를 반환하기만 하면 됩니다.
조건부로 만들거나 단순히 논리를 수행할 수 있습니다. 인라인 함수를 둘러싼 괄호에 유의하십시오. 또한 특히 여기에서 이 함수를 호출하는 경우 원하는 경우 주변 컨텍스트에서 이 함수로 인수를 전달할 수도 있습니다!
})()}
이것은 표준 .map
이 JSX 요소 내부에서 허용하는 것보다 더 복잡한 방식으로 컬렉션 데이터 구조에 대해 작업하려는 상황에서 유용할 수 있습니다.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
여기서 코드를 실행하여 숫자 집합을 반복한 다음 인라인으로 표시할 수 있습니다. Gatsby와 같은 정적 사이트 생성기를 사용하는 경우 이 단계도 미리 계산됩니다.
component extends type
자동 완성 친화적인 구성 요소를 만드는 데 매우 유용한 이 기능을 사용하면 기존 HTMLElements
또는 기타 구성 요소를 확장하는 구성 요소를 만들 수 있습니다. Typescript에서 요소 인터페이스를 올바르게 입력하는 데 주로 유용하지만 실제 응용 프로그램은 JavaScript에서도 동일합니다.
다음은 간단한 예입니다. button
요소의 하나 또는 두 개의 속성을 재정의하지만 여전히 개발자에게 버튼에 다른 속성을 추가할 수 있는 옵션을 제공한다고 가정해 보겠습니다. type='button'
또는 type='submit'
설정과 같은 것입니다. 우리는 분명히 전체 버튼 요소를 다시 만들고 싶지 않고 기존 속성을 확장하고 소품을 하나 더 추가하기를 원합니다.
import React, { ButtonHTMLAttributes } from 'react'
먼저 React와 HTMLButtonElement 의 props를 포함하는 유형인 ButtonHTMLAttributes
클래스를 가져 HTMLButtonElement
. 이 유형의 인터페이스에 대한 소스 코드는 여기에서 읽을 수 있습니다.
그리고 React 팀이 TypeScript에서 모든 웹 API를 다시 구현하여 유형을 확인할 수 있음을 알 수 있습니다.
다음으로 status
속성을 추가하여 인터페이스를 선언합니다.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
마지막으로 몇 가지 작업을 수행합니다. 우리는 ES6 Destructuring을 사용하여 관심 있는 props( status
, children
)를 꺼내고 다른 모든 속성을 rest
로 선언합니다. 그리고 JSX 출력에서 버튼 요소를 반환하고 ES6 구조를 사용하여 이 요소에 추가 속성을 추가합니다.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
이제 개발자는 버튼에 일반적으로 있는 type
소품이나 기타 소품을 추가할 수 있습니다. 버튼의 스타일을 설정하기 위해 className
에서 사용한 추가 소품을 제공했습니다.
전체 예는 다음과 같습니다.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
이것은 전체 HTML 요소를 다시 작성하지 않고도 스타일 지침을 준수하는 재사용 가능한 내부 구성 요소를 만드는 좋은 방법입니다! 상태에 따라 className
을 설정하는 것과 같은 전체 props를 재정의하거나 추가 클래스 이름도 전달되도록 허용할 수 있습니다.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
여기에서 우리는 Button 요소에 전달된 prop className
을 가져와서 prop이 undefined
인 경우 안전 검사와 함께 다시 삽입합니다.
결론
React는 매우 강력한 라이브러리이며 빠르게 인기를 얻은 데에는 그럴만한 이유가 있습니다. 성능이 뛰어나고 유지 관리가 쉬운 웹 앱을 구축할 수 있는 훌륭한 도구 세트를 제공합니다. 매우 유연하면서도 동시에 매우 엄격하여 사용법을 안다면 매우 유용할 수 있습니다. 이것들은 주목할만한 몇 가지 API일 뿐이며 대부분 간과됩니다. 다음 프로젝트에서 시도해 보세요!
최신 React API인 hooks에 대한 자세한 내용은 useHooks()를 읽는 것이 좋습니다. Typescript Cheatsheet에는 React 및 Typescript Hooks에 대한 훌륭한 정보도 있습니다.