10줄의 코드로 입력된 상태 관리 반응

목표



이 튜토리얼의 목표는 자바스크립트 코드에서 100% 유형 유추로 "강력한"상태 관리를 작성하는 것입니다.

TLDR:



Final example of the state management is available on github

또는 이 문서의 끝에서 완전히 작동하는 예제를 찾을 수 있습니다.

역사적 배경



React는 약 2년 전에 후크를 도입했습니다.
그것은 전체 생태계를 변화시켰고 외부를 사용하지 않고도 애플리케이션을 작성할 수 있음을 나타냅니다.
redux 또는 mobx와 같은 상태 관리 라이브러리와 우리는 여전히 멋진 미니멀리스트 코드를 갖게 될 것입니다.

후크가 도입되기 전에도 동일한 작업을 수행할 수 있었습니다.
하지만 문제는 renderProps/HOC/Classes API가 후크만큼 훌륭하고 우아하지 않다는 것입니다.

바닐라 리액트의 툴링은 여전히 ​​꽤 강력하지만 애플리케이션이 있다면
평범한 인간에게는 너무 복잡한 수많은 코드 라인으로 다음을 수행할 수 있습니다.
일부 타사 상태 관리 라이브러리에 대해 생각하기 시작합니다.

사용자 정의 상태 관리 래퍼



React context는 글로벌 애플리케이션 로직의 일부를 다른 부분으로 분할하는 방법에 대한 좋은 옵션입니다.
파일을 만들고 각 모듈에 대해 새 파일React.createContext을 정의합니다.
그런 다음 컨텍스트 인스턴스를 가져와 useContext 후크를 통해 구성 요소 인스턴스에서 사용합니다.
이 패턴의 큰 특징은 변경된 상태에 직접 연결되지 않은 구성 요소를 다시 렌더링하지 않는다는 것입니다.

순수한 바닐라 React에서는 이와 같은 컨텍스트를 통해 상태 관리를 작성할 수 있습니다.

import React, { useState, useContext } from 'react'
const MyContext = React.createContext(null)

const LogicStateContextProvider = (props) => {
  const [logicState, setLogicState] = useState(null)

  return (
    <MyContextontext.Provider value={{ logicState, setLogicState }}>
      {...props}
    </MyContextontext.Provider>
  )
}

const Child = () => {
  const logic = useContext(MyContext)
  return <div />
}

const App = () => (
  <LogicStateContextProvider>
    <Child />
  </LogicStateContextProvider>
)


Typescript 정적 유형을 추가하기 전까지는 모든 것이 좋아 보입니다.
그런 다음 각 정의React.createContext에 대해 새 데이터 유형을 정의해야 한다는 것을 알게 됩니다.


/* redundant unwanted line of static type */
type DefinedInterfaceForMyCContext = {
  /* redundant unwanted line of static type */
  logicState: null | string
  /* redundant unwanted line of static type */
  setLogicState: React.Dispatch<React.SetStateAction<boolean>>
  /* redundant unwanted line of static type */
}

const MyContext = React.createContext<BoringToTypesTheseCha>(
  null as any /* ts hack to omit default values */
)

const LogicStateContextProvider = (props) => {
  const [logicState, setLogicState] = useState(null as null | string)

  return (
    <MyContext.Provider value={{ logicState, setLogicState }}>
      {...props}
    </MyContext.Provider>
  )
}

/* ... */


보시다시피 각각React.createContext은 Typescript 정적 유형을 정의하기 위해 몇 줄을 추가로 사용합니다.
원시 Javascript 구현에서 직접 쉽게 유추할 수 있습니다.

무엇보다 추론의 모든 문제가 JSX에서 비롯된다는 것을 알 수 있습니다. 데이터 유형을 추론하는 것은 불가능하지 않습니다!

따라서 구성 요소에서 원시 논리를 직접 추출하여 useLogicState라는 사용자 지정 후크에 넣어야 합니다.

const useLogicState = () => {
  const [logicState, setLogicState] = useState(null as null | string)

  return {
    logicState,
    setLogicState
  }
}

const MyContext = React.createContext<
  /* some Typescript generic magic */
  ReturnType<typeof useLogicState>
>(
  null as any /* ts hack to bypass default values */
)

const LogicStateContextProvider = (props) => {
  const value = useLogicState()

  return (
    <MyContext.Provider value={value}>
      {...props}
    </MyContext.Provider>
  )
}

const Child = () => {
  const logic = useContext(MyContext)
  return <div />
}

const App = () => (
  <LogicStateContextProvider>
    <Child />
  </LogicStateContextProvider>
)


보시다시피 논리를 사용자 정의 후크로 분리하면 ReturnType<typeof customHook> 로 데이터 유형을 유추할 수 있습니다.


이 TS 코드 줄ReturnType<typeof useLogicState>을 완전히 이해하지 못하는 경우 다른 Typescript 자습서를 확인할 수 있습니다.




  • 또한 코드에 포함해야 하는 중복 문자가 많다는 사실도 마음에 들지 않습니다.
    새로운 React 컨텍스트를 생성할 때마다 자체 JSXProvider 구성 요소가 우리의 <App />를 래핑하는 데 사용됩니다.

    그래서 나는 모든 더러운 코드를 자체 함수로 추출하고 래핑하기로 결정했습니다.
    덕분에 우리는 마법의 Typescript 제네릭을 이 함수로 옮길 수 있고 전체 상태 관리를 추론할 수 있습니다.

    type Props = { 
      children: React.ReactNode 
    }
    
    export const genericHookContextBuilder = <T, P>(hook: () => T) => {
      const Context = React.createContext<T>(undefined as never)
    
      return {
        Context,
        ContextProvider: (props: Props & P) => {
          const value = hook()
    
          return <Context.Provider value={value}>{props.children}</Context.Provider>
        },
      }
    }
    
    


    그래서 우리는 읽기 어려운 이 모든 마법을 10줄 함수로 감쌀 수 있습니다.

    이제 genericHookContextBuilder 함수는 상태 후크를 인수로 사용하고 작동할 구성 요소를 생성합니다.useContext로 가져올 수 있는 앱 래퍼 및 컨텍스트로 사용됩니다.

    다음 예제에서 사용할 준비가 되었습니다.

    전체 예




    import React, { useState, useContext } from 'react';
    
    
    type Props = {
      children: React.ReactNode
    }
    
    export const genericHookContextBuilder = <T, P>(hook: () => T) => {
      const Context = React.createContext<T>(undefined as never)
    
      return {
        Context,
        ContextProvider: (props: Props & P) => {
          const value = hook()
    
          return <Context.Provider value={value}>{props.children}</Context.Provider>
        },
      }
    }
    
    const useLogicState = () => {
      const [logicState, setLogicState] = useState(null as null | string)
    
      return {
        logicState,
        setLogicState
      }
    }
    
    export const {
      ContextProvider: LogicStateContextProvider,
      Context: LogicStateContext,
    } = genericHookContextBuilder(useLogicState)
    
    const Child = () => {
      const logic = useContext(LogicStateContext)
      return <div />
    }
    
    const App = () => (
      <LogicStateContextProvider>
        <Child />
      </LogicStateContextProvider>
    )
    
    






    보시다시피 기본 React 컨텍스트 기본 상세 API 주위에 작은 래퍼를 작성했습니다.
    래퍼는 코드를 복제하지 않고 많은 추가 줄을 저장할 수 있는 즉시 사용 가능한 Typescript 유형 유추로 이를 향상시켰습니다.

    이 기사를 나와 똑같이 즐기고 새로운 것을 배웠기를 바랍니다. 그렇다면 이 기사를 좋아하는 것을 잊지 마세요

    좋은 웹페이지 즐겨찾기