React의 컨텍스트 API에 문제가 있습니다.


기본 제공 leewarrick.com/blog
React의 컨텍스트 API는 매우 좋습니다.Redux를 초급 개발자로 간주하고 즉각 실패를 느끼는 사람들에게 상하문을 이해하는 것은 해탈이다.나는 응용 프로그램에서 그것을 사용했는데, 곧 Redux를 잊어버렸고, 다시는 돌아오지 않았다.
즉, 컨텍스트 API의 성능 문제를 들을 때까지.이제 React 커뮤니티의 유명 인사들이 문제를 발견하기 시작하지 않으면 성능을 걱정하지 말라고 알려줄 것이다.그러나 나는 다른 개발자들로부터 상하문 문제를 끊임없이 들었다.한 동료는 심지어 그의 사장이 그들의 프로젝트에서 언어 환경을 사용하는 것을 금지한다고 언급했다.
이 문제를 논의하기 전에 먼저 컨텍스트 API를 검토하여 숙지하지 않도록 하겠습니다.

컨텍스트 API를 사용하는 이유는 무엇입니까?


컨텍스트 API는 구성 요소 간에 공유되어 도구와 쉽게 공유할 수 없는 상태에 유용합니다.다음은 원조 상태를 설정해야 하는 단추 구성 요소의 예입니다.
(참고: 이 세션의 실시간 버전을 보려면 original post
const { useState } = React

function CountDisplay({ count }) {
  return <h2>The Count is: {count}</h2>
}

function CountButton({ setCount }) {
  return (
    <button onClick={() => setCount(count => count + 1)}>
      Increment
    </button>
  )
}

const OuterWrapper = ({setCount}) => <InnerWrapper setCount={setCount}/>
const InnerWrapper = ({setCount}) => <CountButton setCount={setCount}/>

function App() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <CountDisplay count={count} />
      <OuterWrapper setCount={setCount}/>
    </div>
  )
}

render(App)
버튼 구성 요소는 트리 아래에 있는 다른 여러 구성 요소에 있지만 응용 프로그램의 더 높은 액세스 상태가 필요합니다.따라서 우리는 setCount 을 모든 구성 요소에 전달하고 최종적으로 CountButton 구성 요소에 전달해야 한다.이것은 친절하게'버팀목 시추'라고 불리는데, 일찍이React의 커다란 통증이었다.
고맙습니다. Context API는 이러한 상황을 신속하게 처리할 수 있습니다.

컨텍스트 API 사용 방법


Kent C. Dodds는 상하문 API를 실현할 때마다 아주 좋은 blog post을 가지고 있습니다.만약 읽을 시간이 없다면, 여기에 간단한 버전이 있습니다. 상하문은 상관없거나 먼 구성 요소 사이에서 상태를 공유하는 방법입니다.구성 요소를 Context.Provider 에 봉인하고 이 구성 요소에서 useContext(Context) 를 호출하여 액세스 상태와 도움말 함수에 접근하는 것입니다.
다음은 컨텍스트의 예입니다.
const {useContext, useState, createContext} = React

const AppContext = createContext()

function AppProvider(props) {
  const [count, setCount] = useState(0)
  const value = { count, setCount }
  return (
    <AppContext.Provider value={value}>
      {props.children}
    </AppContext.Provider>
  )
}

function CountDisplay() {
  const { count } = useContext(AppContext)
  return <h2>The Count is: {count}</h2>
}

function CountButton() {
  const { setCount } = useContext(AppContext)
  return (
    <button onClick={() => setCount(count => count + 1)}>
      Increment
    </button>
  )
}

const OuterWrapper = () => <InnerWrapper />

const InnerWrapper = () => <CountButton />

function App() {
  return (
    <div>
      <AppProvider>
        <CountDisplay/>
        <OuterWrapper/>
      </AppProvider>
    </div>
  )
}

render(App)
여기에는 CountDisplayCountButton 구성 요소가 있습니다. 이 구성 요소는 모두 우리의 상하문에서 더 높은 단계count 상태와 상호작용을 해야 합니다.우리는 우선 createContext 상하문을 만든 다음에 AppProvider 에서 공급자 구성 요소를 만들어서 우리의 의존 구성 요소를 포장하고, 마지막으로 모든 구성 요소에서 useContext 를 호출하여 우리가 필요로 하는 값을 추출합니다.구성 요소가 공급자에 포장되기만 하면 그들 사이의 거리는 중요하지 않다.
좋죠?

Kent C.Dodd 최적화📈


우리는 켄트가 주 관리에 관한 글 중의 일부를 실현함으로써 이 점을 개선할 수 있다.살펴보겠습니다.
const {useContext, useState, createContext, useMemo} = React
const AppContext = createContext()

// instead of calling useContext directly in our components,
// we make our own hook that throws an error if we try to
// access context outside of the provider
function useAppContext() {
  const context = useContext(AppContext)
  if (!context)
    throw new Error('AppContext must be used with AppProvider!')
  return context
}

function AppProvider(props) {
  const [count, setCount] = useState(0)
  // here we use useMemo for... reasons.
  // this says don't give back a new count/setCount unless count changes
  const value = useMemo(() => ({ count, setCount }), [count])
  return <AppContext.Provider value={value} {...props} />
}

function CountDisplay() {
  const { count } = useAppContext()
  return <h2>The Count is: {count}</h2>
}

function CountButton() {
  const { setCount } = useAppContext()
  return (
    <button onClick={() => setCount(count => count + 1)}>
      Increment
    </button>
  )
}

const OuterWrapper = () => <InnerWrapper />

const InnerWrapper = () => <CountButton />

function App() {
  return (
    <div>
      <AppProvider>
        <CountDisplay />
        <OuterWrapper />
      </AppProvider>
    </div>
  )
}

render(App)
우리가 해야 할 첫 번째 일은 공급자 이외의 상하문에 접근하려고 하면 오류가 발생한다는 것이다.이것은 응용 프로그램 개발자의 체험을 개선하는 좋은 생각이다. (즉, 상하문이 어떻게 작동하는지 잊어버렸을 때 컨트롤러가 당신을 향해 소리를 지르게 하는 것이다.)
두 번째는 count 변경할 때만 다시 렌더링할 수 있도록 상하문 값을 기억하는 것입니다.useMemo는 이해하기 어려운 일이지만, 기본적인 요점은 당신이 어떤 것을 기억할 때, 당신이 지정한 값이 변하지 않으면 다시 이 값으로 돌아오지 않는다고 말하는 것이다.더 많이 읽고 싶으면 Kent도 하나 있습니다great article.
나는 사용useMemo과 사용하지 않는 것 사이에 어떤 차이가 있는지 알 수 없지만, 만약 당신이 상하문 제공자 중에서 무거운 일을 한다면, 기억을 응용하는 것이 유익할 것이라고 감히 말할 수 있다.켄트가 useMemouseCallback에 쓴 글을 읽었다면, 성능상의 성공을 보지 않으면 사용하지 말라고 일깨워 준다.(완전공개: 나는 지금까지 어떤 것도 사용할 필요가 없다.)
Kent는 또한 그props를 공급자에게 전파하는 것이지 사용props.children이 아니다. 이것은 교묘한 기교이기 때문에 나도 이 점을 포함한다.

컨텍스트 API의 더러운 비밀🤫



세상에, 이 API 정말 좋아요.Redux에 비해 사용하기 쉽고 코드가 더 적기 때문에 왜 사용하지 않습니까?
상하문의 문제는 매우 간단하다. 상하문의 상태가 바뀔 때마다 상하문을 사용하는 모든 내용이 다시 나타난다.
이것은 응용 프로그램의 모든 곳에서 상하문을 사용하거나 더 나쁜 것은 전체 응용 프로그램의 상태에서 상하문을 사용하면 대량의 재렌더링을 초래할 수 있다는 것을 의미한다.
간단한 응용 프로그램으로 가시화합시다.계수기와 메시지로 상하문을 만듭니다.메시지는 영원히 변경되지 않지만, 세 개의 구성 요소에 의해 사용됩니다. 이 구성 요소들은 모든 렌더링에서 무작위 색으로 메시지를 표시합니다.계수는 하나의 구성 요소에 의해 소모되고 유일하게 변경된 값입니다.
이것은 마치 중학교 수학 문제처럼 들리지만, 이 코드와 생성된 응용 프로그램을 보면 문제가 명백해진다.
const {useContext, useState, createContext} = React
const AppContext = createContext()

function useAppContext() {
  const context = useContext(AppContext)
  if (!context)
    throw new Error('useAppContext must be used within AppProvider!')
  return context
}

function AppProvider(props) {
  // the count for our counter component
  const [count, setCount] = useState(0)
  // this message never changes!
  const [message, setMessage] = useState('Hello from Context!')
  const value = {
    count,
    setCount,
    message,
    setMessage
  }
  return <AppContext.Provider value={value} {...props}/>
}

function Message() {
  const { message } = useAppContext()
  // the text will render to a random color for
  // each instance of the Message component
  const getColor = () => (Math.floor(Math.random() * 255))
  const style = {
    color: `rgb(${getColor()},${getColor()},${getColor()})`
  }
  return (
    <div>
      <h4 style={style}>{message}</h4>
    </div>
  )
}

function Count() {
  const {count, setCount} = useAppContext()
  return (
    <div>
      <h3>Current count from context: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

function App() {
  return (
    <div>
      <AppProvider>
        <h2>Re-renders! 😩</h2>
        <Message />
        <Message />
        <Message />
        <Count />
      </AppProvider>
    </div>
  )
}
render(App)
변동분 을 클릭하면 모든 내용이 다시 렌더링됩니다.😱.
메시지 구성 요소는 심지어 우리의 상하문 count 을 사용하지 않지만, 그것들은 어쨌든 다시 나타날 것이다.에이

기억은요?


아마도 우리는 단지 사용useMemo을 잊어버렸을 것이다. 켄트가 그의 예에서 한 것처럼.우리의 배경을 회상하고 무슨 일이 일어날지 봅시다.
const {useContext, useState, createContext, useMemo} = React
const AppContext = createContext()

function useAppContext() {
  const context = useContext(AppContext)
  if (!context) throw new Error('useAppContext must be used within AppProvider!')
  return context
}

function AppProvider(props) {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState('Hello from Context!')
  // here we pass our value to useMemo,
  // and tell useMemo to only give us new values
  // when count or message change
  const value = useMemo(() => ({
    count,
    setCount,
    message,
    setMessage
  }), [count, message])
  return <AppContext.Provider value={value} {...props}/>
}

function Message() {
  const { message } = useAppContext()
  const getColor = () => (Math.floor(Math.random() * 255))
  const style = {
    color: `rgb(${getColor()},${getColor()},${getColor()})`
  }
  return (
    <div>
      <h4 style={style}>{message}</h4>
    </div>
  )
}

function Count() {
  const {count, setCount} = useAppContext()
  return (
    <div>
      <h3>Current count from context: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

function App() {
  return (
    <div>
      <AppProvider>
        <h2>Re-renders! 😩</h2>
        <Message />
        <Message />
        <Message />
        <Count />
      </AppProvider>
    </div>
  )
}
render(App)
아니!useMemo 기억은 아예 소용없어!

컨텍스트를 사용하지 않는 어셈블리의 경우 다시 렌더링됩니까?


이것은 매우 좋은 문제입니다. 상하문을 사용하지 않는 메시지 구성 요소로 그것을 테스트합시다.
const {useContext, useState, createContext, useMemo} = React
const AppContext = createContext()

function useAppContext() {
  const context = useContext(AppContext)
  if (!context) throw new Error('useAppContext must be used within AppProvider!')
  return context
}

function AppProvider(props) {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState('Hello from Context!')
  const value = useMemo(() => ({
    count,
    setCount,
    message,
    setMessage
  }), [count, message])
  return <AppContext.Provider value={value} {...props}/>
}

// this component does NOT consume the context
// but is still within the Provider component
function IndependentMessage() {
  const getColor = () => (Math.floor(Math.random() * 255))
  const style = {
    color: `rgb(${getColor()},${getColor()},${getColor()})`
  }
  return (
    <div>
      <h4 style={style}>I'm my own Independent Message!</h4>
    </div>
  )
}

function Message() {
  const { message } = useAppContext()
  const getColor = () => (Math.floor(Math.random() * 255))
  const style = {
    color: `rgb(${getColor()},${getColor()},${getColor()})`
  }
  return (
    <div>
      <h4 style={style}>{message}</h4>
    </div>
  )
}

function Count() {
  const {count, setCount} = useAppContext()
  return (
    <div>
      <h3>Current count from context: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

function App() {
  return (
    <div>
      <AppProvider>
        <h2>Re-renders! 😩</h2>
        <Message />
        <Message />
        <Message />
        <IndependentMessage />
        <Count />
      </AppProvider>
    </div>
  )
}
render(App)
이것은 지금까지의 유일한 좋은 소식이다.컨텍스트의 상태가 변경되면 호출된 구성 요소만 다시 렌더링됩니다.
그러나 이것은 우리 응용 프로그램에 있어서 나쁜 소식이다.우리는 상하문을 사용하는 어느 곳에서도 불필요한 재렌더링을 촉발하고 싶지 않다.
만약 이 메시지 구성 요소가 계산 애니메이션이나 거대한 React 응용 프로그램이 있다면, 그 중 많은 구성 요소가 우리의 상하문에 의존하고 있다고 상상해 보세요.이것은 상당히 심각한 성능 문제를 초래할 수 있다. 그렇지?

우리는 상하문 사용을 멈춰야 합니까?



나는 지금 계속해서 말했다. 아니, 이것은 상하문 사용을 멈추는 이유가 아니다.현재 많은 응용 프로그램이 상하문을 사용하고 있으며, 나 자신의 응용 프로그램을 포함하여 잘 실행되고 있다.
그럼에도 불구하고 성능은 여전히 큰일이다.나는 너로 하여금 밤잠을 이루지 못하게 상하문 API의 더러운 작은 비밀을 걱정하게 하고 싶지 않다.그렇다면 이런 재렌더링 업무를 처리하는 몇 가지 방법을 이야기합시다.

옵션 1: 걱정 마세요.너처럼 버텨.요로🤪!


나는 기본적으로 많은 다른 응용 프로그램에서 대량의 상하문을 사용했고 기억이 없다. 내 응용 프로그램의 맨 윗부분에서 한 무더기의 구성 요소에 의해 소비되어 성능의 영향을 전혀 알아차리지 못했다.앞서 말한 바와 같이, 많은 React 직원들은 성능의 영향을 보기 전에, 성능 최적화를 걱정해서는 안 된다고 말한다.
그러나 이 전략은 모든 사람에게 적용되지 않을 것이다.응용 프로그램에 성능 문제가 존재할 수도 있고, 응용 프로그램이 대량의 논리나 애니메이션을 처리한다면, 응용 프로그램이 증가함에 따라 성능 문제를 볼 수도 있고, 최종적으로 심각한 재구성을 할 수도 있다.

옵션 2: Redux 또는 Mobx 사용


Redux와 Mobx는 모두 컨텍스트 API를 사용합니다. 그러면 어떤 도움이 될까요?이러한 상태 관리 라이브러리와 컨텍스트가 공유하는 저장소는 컨텍스트와 직접 공유하는 상태와 약간 다릅니다.Redux와 Mobx를 사용할 때 실제 렌더링이 필요한 구성 요소만 다시 렌더링하는 차분 알고리즘이 있습니다.
그럼에도 불구하고 상하문은 우리가 Redux와 Mobx를 배울 필요가 없게 해야 한다!주 관리 도서관을 사용하는 것은 많은 추상적이고 견본 문서와 관련되어 있기 때문에 일부 사람들에게 매력적이지 않은 해결 방안이다.
그 밖에 우리 모든 주를 전 세계 상태로 만드는 것은 나쁜 방법이 아니겠는가?

옵션 3: 여러 컨텍스트를 사용하여 의존 구성 요소에 가까운 상태로 만들기


이 해결 방안은 가장 교묘한 기교가 있어야만 실현할 수 있지만, Redux와 Mobx를 사용하지 않는 상황에서 최상의 성능을 제공할 수 있다.이것은 상태 관리 선택의 지능화에 의존하고 원격 구성 요소 간에 상태를 공유해야 할 때만 상하문에 상태를 전달한다.
이 전략에는 다음과 같은 몇 가지 주요 임차인이 있습니다.

  • 가능하다면 구성 요소로 하여금 자신의 상태를 관리하게 하세요.어떤 상태 관리를 선택하든지 간에 이것은 따를 만한 좋은 실천이다.예를 들어 열기/닫기 상태를 추적해야 하는 모드가 있지만 모드가 열려 있는지 알 다른 구성 요소가 없으면 열기/닫기 상태를 모드에 유지합니다.필요하지 않으면 컨텍스트 (또는 Redux) 로 상태를 미루지 마십시오!

  • 만약 당신의 주가 한 부모와 몇 명의 아이들이 공유한다면, 그것을 지지합시다.이것은 상태를 공유하는 낡은 방법이다.그것을 필요로 하는 어린이 부품에 도구로 전달하기만 하면 된다.깊게 박힌 구성 요소에 대해 도구를 전달하거나'아이템 드릴링'은 큰일날 수 있지만, 아래로 몇 단계의 내용을 전달하기만 하면 이렇게 해야 할 수도 있습니다.

  • 만약 앞의 두 가지 일이 실패한다면, 상하문을 사용하지만, 그것에 의존하는 구성 요소에 접근하도록 하세요.이것은 폼과 여러 구성 요소 같은 상태를 공유해야 한다면, 폼을 위한 단독 상하문을 계속 만들고 폼 구성 요소를 공급자에 포장하십시오.
  • 마지막 예가 될 만하다.우리는 그것을 이전의 문제 응용 프로그램에 응용할 것이다.우리는 useContextmessage 를 각각의 상하문으로 분리하여 이러한 렌더링을 수정할 수 있다.
    const { useContext, useState, createContext } = React
    const CountContext = createContext()
    
    // Now count context only worries about count!
    function useCountContext() {
      const context = useContext(CountContext)
      if (!context)
        throw new Error('useCountContext must be used within CountProvider!')
      return context
    }
    
    function CountProvider(props) {
      const [count, setCount] = useState(0)
      const value = { count, setCount }
      return <CountContext.Provider value={value} {...props}/>
    }
    
    // And message context only worries about message!
    const MessageContext = createContext()
    
    function useMessageContext() {
      const context = useContext(MessageContext)
      if (!context)
        throw new Error('useMessageContext must be used within MessageProvider!')
      return context
    }
    
    function MessageProvider(props) {
      const [message, setMessage] = useState('Hello from Context!')
      const value = { message, setMessage }
      return <MessageContext.Provider value={value} {...props}/>
    }
    
    function Message() {
      const { message } = useMessageContext()
      const getColor = () => (Math.floor(Math.random() * 255))
      const style = {
        color: `rgb(${getColor()},${getColor()},${getColor()})`
      }
      return (
        <div>
          <h4 style={style}>{message}</h4>
        </div>
      )
    }
    
    function Count() {
      const {count, setCount} = useCountContext()
      return (
        <div>
          <h3>Current count from context: {count}</h3>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      )
    }
    
    function App() {
      return (
        <div>
          <h2>No Unnecessary Re-renders! 😎</h2>
          <MessageProvider>
            <Message />
            <Message />
            <Message />
          </MessageProvider>
          <CountProvider>
            <Count />
          </CountProvider>
        </div>
      )
    }
    render(App)
    
    현재, 우리의 상태는 이 상태에 관심을 가진 구성 요소와만 공유됩니다.우리가 증가할 때, 메시지 구성 요소의 색깔은 변하지 않는다. 왜냐하면 countcount 밖에 있기 때문이다.

    마지막 생각


    비록 이 글의 제목이 약간 선동적이고, 언어 환경의'문제'는 일부 사람들이 상상하는 것처럼 그렇게 되지는 않을 수도 있지만, 나는 여전히 이것이 토론할 만한 것이라고 생각한다.React의 유연성은 초보자의 절호의 틀이 될 뿐만 아니라 내부 작업 원리를 모르는 사람들에게 파멸적인 도움을 제공한다.나는 많은 사람들이 이 특정한 세부 사항에 걸려 넘어지는 것을 보고 싶지 않지만, 만약 당신이 상하문을 사용하고 성능 문제를 보고 있다면, 이 점을 아는 것은 좋은 일이다.
    이거 좋아요?Please subscribe to my newslettercheck out my podcast!

    좋은 웹페이지 즐겨찾기