반응형

이 포스트는 아래 출처의 글을 번역한 글 입니다. 오역과 의역이 있을 수 있습니다.

 

 

이 포스트는 기록을 남기기 위한 이유로 이곳에 존재합니다. an updated version of this blog post with React Hooks
이 글을 읽어주세요! 또한 보다 일반적인 개념인 "제어의 역전"에도 관심이 있을 수 있습니다.

 

지난주 @notruth(downshift 프로젝트의 새로운 컨트리뷰터)는 "closeOnSelection" 속성(박스에서 복수 선택을 했을시) 이슈를 추가해주었습니다. 여러분이 진정으로 알아야 할 것은 특정 시나리오에서 유저의 동작에 기초하여 downshift가 어떻게 상태를 업데이트하는지에 대한 결정이 @notruth가 구현에 대해 원하는 것과 일치하지 않는다는 것입니다.

 

Why do we use UI libraries?

같은 UI 라이브러리들을 이용하면 두개를 제공해 줄 수 있습니다.

 

  1. 이것이 동작하는 방식
  2. 어떻게 이것을 보일 것인지

UI 라이브러리들은 이러한 것들에 대해 결정을 내려야 합니다. 그러나 의사 결정이 적을수록 일반적으로 유용하고 유연한 라이브러리를 사용할 수 있습니다. 그러나 이것은 섬세한 균형입니다. 의사결정이 많을 수록 특정 경우에는 더 유용할 수 있지만 하지만 다른 사용사례에서는 완전히 사용을 못할 정도가 될 수도 있습니다. 만약 아무런 결정도 내리지 않는다면 음... 왜 라이브러리를 설치해야 할까요? 🤔

 

downshift 에서는 render prop을 이용하여 보여주는 방식에 대해 어떠한 결정도 내리지 않기로 결정하였습니다. 이렇게 결정한 이유는 "enhance input components" (autocomplete 같이) 에서 우리가 추상화 하고자 하는 부분은 동작하는 방식이고 좀 더 유연성을 부여해야 하는 부분은 보여지는 방식이기 때문입니다. 게다가 render prop을 사용하면 다른 사람들이 downshift위에 컴포넌트를 만들어 보기 좋게 제공하려 하는 것은 사소한 일입니다.(아직 아무도 그렇게 하지 않은 것에 대해 약간 놀랐습니다.) 🤨

 

Imperfect assumptions

가끔 downshift의 동작에 대해 결정을 내렸던 것이 사람들이 downshift를 사용하고자 하는 모든 경우에 완벽히 만족되지 못하는 경우가 있습니다. 예를들면 downshift는 사용자들이 item을 선택하거나 @notruth가 게시한 이슈와 같은 상황일때 `isOpen` 메뉴 상태를 `false`로 설정할 것입니다. 그들은 결정이 그들의 경우에 들어맞지 않는다고 말하곤 합니다. 🤷‍♂️

 

이게 제가 downshift에 제어 props를 제공하는 이유중 하나입니다. 이로 인해 downshift의 내부 상태를 완벽히 제어할 수 있습니다. 이러한 경우 @notruth는 `isOpen` 상태를 제어할 수 있고 `onStateChange`를 이용하여 상태 버전을 언제 업데이트 해야하는지 알 수 있습니다.  하지만 그것은 양이 많은 작업이기에 왜 @notruth가 더 쉬운 방법을 선호하는지 이해할 수 있습니다. 그러나 새로운 prop을 추가하는 것에 대한 제안은 downshift의 측면에서 API 비용을 증가시켜 이득을 제공해주는것 같지 않아보입니다. 그래서 어떻게 하면 단순화 할수 있고 boilerplate를 줄일 수 있을지 좀 더 고민해보기로 하였습니다. 😈 

 

A simpler API

그때 저는 `modifyStateChange`라고 부르는 새로운 prop을 떠올렸습니다. downshift는 이미 control pops를 지원하고 있었기에 상태 변화를 `internalSetState`라고 불리는 내부 메서드로 분리 하였습니다. 이것은 놀랍게도 오래된 method 입니다. 고립은 새로운 기능의 구현을 사소한 것으로 만들어 줍니다. 우리가 상태변화를 만들때마다 먼저 Downshift 사용자가 곧 일어날 상태 변경에 관심이 있는지 보기 위한 method를 호출합니다.  🤓

 

이것을 위해 중요한 요소는 어떠한 종류의 상태 변화가 일어나는지 결정하는 사용자의 능력입니다. @notruth의 경우 사용자가 아이템을 선택했을때 `isOpen`이 `false`로 바뀌지 않기를 원했습니다. 그러므로 그들은 상황이 발생했을때 어떠한 종류의 변화가 필요한지 알아야 합니다. 운이 좋게도, 우리는 `onStateChange`에서도 이 구별이 필요했고 이미 이 메커니즘을 가지고 있었습니다! 이것은 `stateChangeTypes`로 불립니다. (여기에 리스트가 있습니다.) 🤖

 

그래서 @notruth는 pull request를 생성하고 `modifyStatechange`를 추가하였습니다. 조금 더 고민후에 다른 라이브러리들에게 유용할 수 있는 패턴으로 일반화 하는것이 가능하다고 결론지었습니다. 패턴들은 이름이 있어야 전달이 훨씬 쉬워져서 찾아보았습니다. 🕵️

Introducing the state reducer pattern

결국 `state reduer`라는 이름을 지었고 API를 악간 변경하였습니다. 당신의 함수는 두개의 인자를 받습니다 1) downshift의 현재상태, 2) 발생할 변화. 여러분의 일은 발생시키길 원하는 변화를 줄이는 것입니다.  또한 발생할 변화는 `stateChangeTypes`와 상응하는 `type`을 가지고 있습니다. 그래서 로직을 적용할지 안할지를 알수 있습니다. `changes`를 redux의 "action"으로서 생각할 수도 있습니다. 그러나 당신이 반환할것은 전체 상태가 아닙니다 변경하길 원하는 상태만 반환하면 됩니다. 🔁

 

A few people have since let me know that `reason-react` has something similar to this called simply "reducer" which is validating because I think Reason is pretty neat. 💡

 

더 이상 말할것 없이 아이템 선택후에 메뉴가 닫히는것을 예방하는 downshift에서의 단순한 "state reducer" 실행이 있습니다. 

`stateReducer` prop이 있습니다.

 

import Downshift from 'downshift'

function stateReducer(state, changes) {
  switch (changes.type) {
    case Downshift.stateChangesTypes.keyDownEnter :
    case Downshift.stateChangesTypes.clickItem:
      return {
        ...changes,
        isOpen: state.isOpen,
        highlightedIndex: state.highlightedIndex,
      }
    default :
      return chages
  }
}

 

이것은 느슨한 API입니다. 그러나 downshift 내의 상태들이 제어될수 있기 때문에(control props를 통하든) 이것은 실제로 당신이 아직 성취할 수 없었던 것을 할 수 있게 해주지는 않고 boilerplate를 줄여주고 downshift에서 동작을 위해 필요한 변경사항을 줄여줄 뿐입니다. 👌

 

downshift에서의 구현은 간단하지는 않을 수 있습니다(downshift는 단순한 컴포넌트가 아닙니다). 이것이 제가 토글 컴포넌트의 간단한 구현 예시를 만든 이유입니다. https://codesandbox.io/s/4qo58nvl3x  토글 컴포넌트 치고는 과한 작업이지만, 이 패턴을 구현하는데 하나의 방법을 이해할 수 있기를 바랍니다. 🤝

 

Conclusion

이 새로운 패턴에 정말 흥분되고 제어 컴포넌트와 비제어컴포넌트 사이에 이 패턴이 존재할 수 있음을 발견하였습니다. 제어 props 패턴을 사용함으로써 요구되는 boilerplate와 추가 작업 없이 우리의 라이브러리가 이것이 동작하는 방식의 더 많은 경우를 충족시켰다는 것에 잘한일이라고 생각합니다.  Good luck! 👍

 

 

 

 

 


 

출처 : https://kentcdodds.com/blog/the-state-reducer-pattern

 

The state reducer pattern ⚛️ 🏎

A new pattern has been implemented in downshift and it's awesome. Use the state reducer pattern to make your components more useful.

kentcdodds.com

 

728x90
반응형

React 쿼리의 InfiniteQuery 사용에 대한 한글로 된 예시가 많은것 같지 않아 작성해보려 합니다.

 

React-query의 InfiniteQuery를 사용하면 다음 page의 데이터를 간편하게 불러올 수 있습니다.

준비해줘야 할것은 pageParam을 입력받는 api 요청 함수, getNextPageParam에서 pageParam을 올려주면 됩니다.

 

keepPreviousData 옵션을 이용하면 다음 데이터가 fetching 되기 전까지 이전 데이터를 유지할 수 있고

getPreviousPageParam와 fetchPreviousPage를 이용하면 이전 페이지의 데이터를 가져올 수 도 있습니다.

정말 다양한 옵션과 쓰임새가 있을 수 있는데 기본이 가장 중요하므로 기본만 구현된 예제를 작성해 보려 합니다.

 


 

 

InifniteData의 정보는 pages에 담겨있습니다. 

jsx를 리턴하는 부분에서 map의 사용이 너무 많아져서 Array.flatMap을 이용하여 1차 depth의 평탄화를 시켜준 다음 평탄화된 배열은 map으로 순회하였습니다.

 

 

fetchNextPage에 { pageParam: customNumber } 를 인자로 넘겨준다면 원하는 페이지를 요청해주게 됩니다.

물론 이렇게 되었을 경우 백엔드 쪽에서 받아서 처리해주어야 합니다.

[skip, take], [offset, limit]과 같은 키워드로 검색하여 sql 혹은 Orm을 이용하면 백엔드에서 수월하게 처리할 수 있을 겁니다.

 

 


 

keepPreviousData는 paginated Query를 더 잘 사용하기 위한 도구입니다.

keepPreviousData를 설정함으로써 아래와 같은 기대효과를 가질 수 있습니다.

  • 쿼리 키가 변경되었을 지라도 새 데이터를 요청하는 동안 가장 최근에 요청에 성공한 데이터에 접근이 가능합니다.
  • 새로운 데이터가 도착하면 이전의 데이터가 원할하게 교체되어 표시가 됩니다.
  • isPreviousData가 현재 어떤 데이터가 제공되고 있는지를 알 수 있게 해줍니다.
const {
     isLoading,
     isError,
     error,
     data,
     isFetching,
     isPreviousData,
   } = useQuery(['projects', page], () => fetchProjects(page), { keepPreviousData : true })

 

<button
    onClick={() => {
        if (!isPreviousData && data.hasMore) {
         	setPage(old => old + 1)
        }
    }}
    // Disable the Next Page button until we know a next page is available
	disabled={isPreviousData || !data?.hasMore}
>

 

출처: https://react-query.tanstack.com/guides/infinite-queries

 

Infinite Queries

Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.

react-query.tanstack.com

https://react-query.tanstack.com/guides/paginated-queries#better-paginated-queries-with-keeppreviousdata

 

Paginated / Lagged Queries

Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.

react-query.tanstack.com

 

728x90
반응형

제목이 클릭을 유발하는 자극적인 글일 수 있지만 부분적으로는 사실이다. 아래 두개의 예제를 살펴보자

 

// 예제 1

const Counter = () => {
  const [count, setCount] = useState(
    Number.parseInt(window.localStorage.getItem(cacheKey))
  )
  
  useEffect(() => {
    window.localStorage.setItem(cacheKey, count)
  }, [cacheKey, count])
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
    </div>
  )
}
// 예제 2

const Counter = () => {
  const [count, setCount] = useState(() => 
    Number.parseInt(window.localStorage.getItem(cacheKey))
  )
  
  useEffect(() => {
    window.localStorage.setItem(cacheKey, count)
  }, [cacheKey, count])
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
    </div>
  )
}

 

두개의 차이점을 눈치챌수 있나요? 만약 그렇다면 눈썰미가 좋네요 🔬 그렇지 않다면 useState 호출 부분을 힌트로 주겠습니다.

// 예제 1
const [count, setCount] = useState(
  Number.parseInt(window.localStorage.getItem(cacheKey))
)
// 예제 2
const [count, setCount] = useState(() => 
  Number.parseInt(window.localStorage.getItem(cacheKey))
)

지금은 어떤가요? 😊 둘이 같아 보이나요? 좋아요 바로 해답을 알려드릴게요 😂

 

두 코드의 차이는 상태의 초기화에 있습니다. 첫번째 예제는 localStorage로 부터 값을 가져오고 이걸 integer로 파싱 합니다. 그리고 count 상태에 초깃값으로 설정을 하게 됩니다.

// 예제 1

const [count, setCount] = useState(
  Number.parseInt(window.localStorage.getItem(cacheKey))
)

 

두번째 예제는 거의 유사합니다. localStorage로 부터 값을 가져오고 integer로 변환하여 값을 변환해주는 함수를 전달하였다는것 외에는 말이죠

// 예제 2

const [count, setCount] = useState(() => 
  Number.parseInt(window.localStorage.getItem(cacheKey))
)

 

화살표 함수의 값을 반환해주는 간결함 덕분에 첫번째 예제와 두번째 예제의 차이는 4글자 밖에 안됩니다. 초깃값으로 어떤 값을 가지려하는지에 따라 단지 4글자를 추가해주는 것은 React 함수 컴포넌트의 성능을 증가시켜줄 수 있습니다.

 

useState 함수에 값 대신에 함수를 전달해주는 것을 지연 초기화라고 합니다. useState문서에 따르면 초깃값을 가져오는데 비용이 비싼 연산을 수행해야 하는 경우 useState와 지연 초기화를 사용하라고 하고 있습니다. 지연 초기화 함수는 상태가 생성될때 한번만 수행되기 때문입니다. 연속된 리렌더링에서 함수는 무시됩니다.

 

리액트 공식문서 내용

 

주의 사항으로써 useState hook은 Counter가 렌더 되었을때 첫번째만 동작합니다. 초깃값과 함께 count 상태를 생성합니다. 그리고 나서 setCount를 호출하면 Counter 함수는 다시 호출되어지고 count는 업데이트된 값을 가지게 됩니다. 그리고 이 리렌더링은 count 상태가 바뀔때마다 발생합니다. 그 동안, 그 초깃값은 다시는 사용되지 않습니다.

 

첫번째 예제에서는 모든 리렌더에 대해 localStorage의 값을 읽지만 우리는 최초 렌더에만 필요합니다. 우리는 불필요한 연산을 많이 수행하게 되는 것입니다.  지연 초기화를 사용한 두번째 예제는 불필요한 연산을 예방해줍니다.

 

아직 조금 헷갈리나요? 첫번째 예제를 재작성 해보고 이게 좀 더 명확히 해주길 희망합니다. localStorage로부터 useState로 직접 값을 전달해주는 대신에 변수에 값을 저장하고 useState에 대신 전달해주겠습니다.

 

// 예제 1
const Counter = () => {
  const initialValue = Nubmer.parseInt(window.localStorage.getItem(cacheKey))
  const [count, setCount] = useState(initialValue)
  
  // 생략
}

 

이제 리렌더하고 Counter를 다시 호출할때마다 localStorage에서 value를 매번 가져온다는 것을 알 수 있습니다. 최초 실행시에만 필요할뿐인데도 말이죠.

두번째 예제는 useState에 리렌더링 될때마다 함수를 전달합니다. 그렇지만 useStaet는 최초에만 실행합니다. 이게 지연 초기화를 사용하는 이유입니다.

 

그리고 만약 지연 초기화 함수 전체를 작성한다면 두 예제의 차이점은 좀 더 명확해질 것입니다.

 

// 예제 2

const Counter = () => {
  const [count, setCount] = useState(function() {
    return Number.pasreInt(window.localStorage.getItem(cacheKey))
  })
  
  // 생략
}

 

지연 초기화가 최초에만 실행이 되기 때문에 매번 사용할 수 도 있을 것입니다.

 

// 원시값 반환 

const Counter = () => {
  const [count, setCount] = useState(() => 0)
  
  // 생략
}
// prop이나 존재하던 변수로부터 반환

const Counter = ({ initialCount }) => {
  const [count, setCount] = useState(() => initialCount)
  
  // 생략
}

 

이러한 경우에 초기값은 단순한 값이나 이미 계산된 값입니다. 함수가 최초 한번만 호출이 될지라도 함수를 매번 생성하는 비용이 여전히 발생합니다. 단순한 값을 반환하는 것보다 함수를 매번 생성하는 비용이 더 높을수 있습니다. 과최적화가 될 수 있습니다.

 

그럼 언제 지연 초기화를 사용하여야 할까요?  문서에 따르면 "비용이 비싼 연산"에 사용하라고 합니다. localStorage로 부터 값을 읽는것은 비용이 비싼 연산일 수 있습니다. map, filter, find등을 배열을 사용하는 것도 마찬가지 입니다. 값을 얻기 위해 함수를 호출해야 하는 경우, 지연 초기화를 사용할 수 있을만큼 충분히 비싼 계산일 수 있습니다.

 

저는 현재 date/time을 상태에 부여해야할때 사용합니다.

const Clock = () => {
  const [time, setTime] = useState(() => new Date())
  
  useEffect(() => {
    const intervalId = setInteral(() => {
      setTime(new Date())
    }, 1000)
    
    return () => {
      clearInterval(intervalId)
    }
  }, [tickAmount])
  
  return <p>{time.toLocaleTimeString()}</p>
}

 

 

 

 

 

출처: https://www.benmvp.com/blog/four-characters-optimize-react-component/

 

Four characters can optimize your React component | Ben Ilegbodu

How making use of useState lazy initialization can speed up your React function component

www.benmvp.com

 

728x90
반응형

React 에서 Presentational/Container Component로 관리를 해야한다는 이야기를 듣게 되었습니다.

두개의 컴포넌트는 어떤 컴포넌트를 의미하는지 알고 싶어 예전의 medium글을 읽게 되었습니다.

 

그리고 이 글을 읽으며 훅의 도입 이유중 하나인 상태로직의 재사용에 대해 이해하게 되었고

컴포넌트는 바보 컴포넌트로 생성을 해야 한다는 생각을 더 강하게 하게 되었습니다.

바보 컴포넌트란 주어진 props를 보여주기만 하는 컴포넌트를 의미합니다.

바보 컴포넌트는 재사용이 쉬워진다는 장점이 있습니다.

 

글을 살펴보면서 훅은 왜 등장하게 되었고 훅을 주로 사용하는 현재의 시점에서는 저 당시에 존재했던 개념을 어떻게 활용하면 더 좋은 코드를 작성할 수 있을지 알아보려 합니다.

여기서 말하는 더 좋은 코드란 유지보수가 간편하고 재사용성이 뛰어난 코드를 의미합니다.

 


 

프로그래밍에서 중요한 개념중 하나는 관심사의 분리입니다.

관심사의 분리란 컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙으로, 각 부문은 개개의 관심사를 해결한다

 

리액트에서는 이를 적용하기 위해 Presentational/Container Component를 도입할 수 있습니다.

Presentational 컴포넌트는 표현하는 역할만 해결하고 

Container 컴포넌트는 상태를 관리하는 역할만 해결하는 것입니다.

 

이 개념을 언급한 Dan Abramov의 글에서는 2019년에 새로운 내용이 글에 추가 됩니다.

 

더이상 Presentatial/Container 컴포넌트의 사용을 제안하지 않는다는 것입니다. 
필요한 경우에 즉 컴포넌트의 재사용성을 필요로 할때 사용해야합니다.
필요하지 않은 경우에도 이와 같은 방식으로 작성되는 것을 많이 보게 되었고 React hook이 그 역할을 대신할 수 있다는 내용입니다.

 

하지만 저는 훅과 두개의 개념을 같이 사용하면 더 좋은 코드를 작성할 수 있지 않을까 하는 생각을 아래의 출처에 포함된 글을 읽고 생각해보게 되었습니다. 이 글을 빌려 아래의 글을 작성해주신 분께 감사를 표하고 싶네요

 

 


 

기존의 Presentational/Container 방식이 이용된 코드 입니다.

import { useSelector, useDispatch } from 'react-redux'

const TodoContainer = () => {
  const todos = useSelector(
    // 생략
  )
  
  const processTodos = util(todos) // 복잡한 로직
  
  const handleClick = () => {
    // 생략
  }
  
  return (
    <TodoPresentation
      todos={processTodos}
      onClick={handleClick}
    />
  )
    
}


const TodoPresentation = ({ todos, onClick}) => {
  return (
    <ul>
      {todos && todos.map(({ id, title }) => (
          <li key={id}>
            {title}
          </li>
        )
      )}
      <button type="button" onClick={onClick}>
        추가
      </button>
    </ul>
  )
}

Todos의 상태는 TodoContainer Component에서 관리를 합니다.

TodoPresentation Component는 주어진 props를 그려주기만 할뿐 입니다.

 

TodoPresentation Component는 재사용이 수월하지만 TodoConatiner Component는 재사용을 할 수가 없습니다.

(Presetnation Component를 인자로 받고 관리되는 todos를 넘겨주면 할 수 있을것 같기도 한데 시나리오만 짰는데 벌써 복잡해져 버렸네요)

 

두개의 관심사는 분리되어 있습니다. Container는 상태의 관리, Presentation은 보여주기만 합니다.

하지만 개선점도 확인되었습니다.

이를 훅을 사용하면 개선할 수 있습니다

 

 

아래의 코드는 커스텀 훅으로 작성된 방식입니다.

import { useSelector, useDispatch } from 'react-redux'

const useTodos = () => {
  const todos = useSelector(
    // 생략
  )
  
  const processTodos = util(todos) // 복잡한 로직
  
  const handleClick = () => {
    // 생략
  }
  
  return { 
    todos, 
    onClick: handleClick
  }
}


const TodoPresentation = () => {
  const { todos, onClick } = useTodos()
  
  return (
    <ul>
      {todos && todos.map(({ id, title }) => (
          <li key={id}>
            {title}
          </li>
        )
      )}
      <button type="button" onClick={onClick}>
        추가
      </button>
    </ul>
  )
}

useTodo 커스텀 훅이 작성되었습다.

이제 상태를 관리하던 로직은 TodoPresentation 에서 사용이 되어집니다. 

useTodo 커스텀 훅은 다른 곳에서 재사용이 가능해졌고 독립적인 테스트가 가능해졌습니다.

하지만 TodoPresentation 컴포넌트는 재사용이 불가능 합니다.

상태관련 로직을 컴포넌트 내부에서 불러와서 리스트를 보여주고 추가해주는 역할 외에는 수행할 수 가 없습니다.

 

Presentational/Container + 커스텀 훅 방식을 이용한 경우입니다.

 

import { useSelector, useDispatch } from 'react-redux'

import { useSelector, useDispatch } from 'react-redux'

const useTodos = () => {
  const todos = useSelector(
    // 생략
  )
  
  const processTodos = util(todos) // 복잡한 로직
  
  const handleClick = () => {
    // 생략
  }
  
  return { 
    todos: processTodos, 
    onClick: handleClick
  }
}

const TodoContainer = () => {
  const { todos, onClick } = useTodos()
    
  return (
    <TodoPresentation
      todos={todos}
      onClick={onClick}
    />
  )
    
}


const TodoPresentation = ({ todos, onClick}) => {
  return (
    <ul>
      {todos && todos.map(({ id, title }) => (
          <li key={id}>
            {title}
          </li>
        )
      )}
      <button type="button" onClick={onClick}>
        추가
      </button>
    </ul>
  )
}

 

그래서 합쳐보았습니다.

재사용 가능한 두개를 얻게 되었습니다. 상태관련 로직과 Presentation 컴포넌트는 재사용이 가능해졌습니다.

또한 둘다 독립적인 테스트 또한 시행할 수 가 있습니디ㅏ.

 

상태관련 로직과 Presentation 컴포넌트는 서로의 관심사가 분리되었고 재사용이 가능합니다. 

 

독립적인 여러 상태관련 로직과 Presentation 컴포넌트를 조합하면 다양한 곳에서 이미 작성되어있는 것들의 재사용이 가능해졌습니다.

 

React에서 훅의 도입과정중 이해가 되지 않던 부분이 1번 요인인 컴포넌트의 상태관련 로직의 재사용 이었는데

위의 예시와 같이 접하니 이해하기가 더 수월해진것 같습니다!

 

 


 

출처: https://yujonglee.com/socwithhooks.html

 

React Hook VS Container Component

관심사의 분리

yujonglee.com

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

 

Presentational and Container Components

You’ll find your components much easier to reuse and reason about if you divide them into two categories.

medium.com

 

https://ko.wikipedia.org/wiki/%EA%B4%80%EC%8B%AC%EC%82%AC_%EB%B6%84%EB%A6%AC

 

관심사 분리 - 위키백과, 우리 모두의 백과사전

컴퓨터 과학에서 관심사 분리(separation of concerns, SoC)는 컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙으로, 각 부문은 개개의 관심사를 해결한다. 관심사란 컴퓨터 프로그램 코드

ko.wikipedia.org

 

728x90
반응형
컴포넌트에서 dispatch를 통해 action을 넘겼을때 동작하는 순서

1. ui에서 action creator를 통해 action을 생성하고 이를 dispatch를 통해 Store로 넘깁니다.
2. store로 넘어오면 이를 감지하고 있던 saga(watch)에서 action의 type에 맞는 effect를 실행하게 됩니다.
3. redux-saga에서는 effect를 통해 객체를 반환하기만 하고 이 객체는 middle-ware로 전달이 됩니다.
4. 실질적인 함수의 실행은 미들웨어에서 실행이 됩니다.
5. put을 통해 store에 dispatch를 전달하고 store에서는 리듀서를 통해 상태를 변화시킵니다.
6. 상태가 변화가 되면 상태를 참조하고 있던 컴포넌트는 리렌더링이 발생하게 됩니다.

--------------------------------- 

component에서 action creator가 호출이 됨
action이 store로 이동됩니다.
saga의 effect인 take(Every, Latest)등이 이를 확인합니다.
saga에서 작성한 제너레이터 함수들이 동작합니다(네트워크 요청 등)
saga의 제너레이터 함수가 put을 통해 store로 결과 값을 (순수한 객체) dispatch 합니다.
reducer에서는 이를 확인하고 state값을 변경합니다.
변경 된 state를 통해 component는 리렌더링 됩니다.

 

 

redux는 어플리케이션에 예측가능한 상태를 제공하기 위한 가벼운 라이브러리 이다.

 

 

redux를 사용하게 되면 아래와 같은 사이클이 형성되는데 (action은 객체, reducer는 순수함수)

ui -> action -> reducer -> state -> ui

이때 비동기적인 부분이 들어갈 수 있는 부분은 존재하지 않게 된다.

 

미들웨어는 어떤걸까??

미들웨어를 설명하기 전에 이해해야 하는 것은 리덕스 안에 baseDispatch라는 함수가 있고 baseDispatch는 Reducer에 직접적으로 연결이 되는 함수이다. 

미들웨어는 이 baseDispatch를 감싸는 고차 함수, higher order function의 일종입니다

 

redux-thunk는 Test가 어렵고, action creator가 action object를 반환하는 것이 아닌, 함수를 실행시켰다.

그래서 redux-saga가 등장하게 되었다.

 

saga는 side effect을 관리하기 위한 모델로서 어플리케이션의 스레드와 같은 역할을 수행한다

이때 side effect은 브라우저 캐시, 로컬스토리지, 비동기 api요청등 부수함수를 일으키는 것이다.

자바스크립트의 경우에는 코드가 외부에 영향을 주거나 받는 것을 side effect이라 할 수 있다.

이때 외부라는 것은 스코프의 바깥을 의미

부작용은 negative side effect으로 프로그래밍 분야에서는 언급되는것으로 이해하였다.

 

sagas라는 논문은 장기 트랜잭션들을 어떻게 관리하고 실패했을때 이들에 대한 처리를 어떻게 할것이냐에 관한 내용이다.

redux-saga에서는 이 논문에 영향을 받았을것 같고 여기에서 스코프를 줄여서 side effect에 관한 Handling에 대한 개념

 

 

action creator에 의해 action이 발생하면 saga에서는 이를 지켜보고 있다가 발생했다는 것을 확인하고 action의 type에 맞는 동작을 수행한다. 

이때 effect를 사용하여 원하는 동작을 수행하게 할 수 있다.

 

 

effect는 redux-saga에게 어떤 동작을 수행해달라고 표현하는 방법

saga는 effect를 yield하고 middleware는 effect를 처리한다.

 

call effect는 async/await의 await과 같은 역할. 비동기작업을 내려주고 계속 기다린다.

call을 사용하면 위의 객체와 같은 정보만 미들웨어 에게 전달이 된다.

미들웨어에서 실제로 이 함수를 호출하고 작업을 기다려준다.

 

take, put, call, fork, join과 같은 effect들이 있다.

take는 action을 가져오고 put은 dispatch를 발생시킨다.

 

call은 비동기 처리를 기다려주고

fork는 작업을 실행은 시켜주지만 끝날때까지 기다려주지 않는다. task만 반환할 뿐

 



 

fork를 통해 othersaga가 다른 공간에서 실행이 된다. 근데 이게 끝날때까지 기다려주지는 않는다.

 

channel 개념

 

 

Generator 내부에서 try/catch 사용이 가능하다. 그래서 에러처리 나면 기존처리 뒤짚고 하는 것과 같은 동작을 수행할것 같다.

toolkit의 createSlice를 이용하면 boilerplate 코드를 매우 줄일수 있다.

 

takeLatest를 사용하면 작업이 들어왔을때 가장 마지막에 작업만 해서 수행하겠다는 뜻이다.

 

yield는 generator 함수에서만 사용할 수 있다.

yield all을 사용하면 스레드별로 생성해서 동작을 수행해줄 수 있다.

 

 

redux-saga는 테스트를 쉽게 할수 있다.

saga자체는 side effect, 일반적인 객체만 yield하기 때문에 비동기 적인 동작들은 테스트 하고 완전히 분리 할수가 있다.

 

redux-saga는 typescript지원을 위해서는 typesafe-actions 라이브러리를 사용해야 하는것으로 알고 있습니다.

 

 

 

 

 

출처: https://www.youtube.com/watch?v=UxpREAHZ7Ck 

https://redux-saga.js.org/docs/api/#alleffects---parallel-effects

 

API Reference | Redux-Saga

Middleware API

redux-saga.js.org

https://velog.io/@mokyoungg/Redux-Redux-Saga-%EA%B8%B0%EC%B4%88-%EC%97%B0%EC%8A%B5

 

[Redux] Redux-Saga 기초 연습

Redux-Saga를 활용한 네트워크 요청. redux-saga의 흐름을 이해하기 위해 작성. 공식페이지의 튜토리얼을 추천. https://mskims.github.io/redux-saga-in-korean/introduction/BeginnerTutorial.html 1. redux-saga 설치 >$ npm install

velog.io

 

728x90

'Frontend > React' 카테고리의 다른 글

React 글자 4개로 React 컴포넌트 최적화 가능(번역글) 🤔  (1) 2022.04.20
React conatiner/presentational -> hook 📝  (0) 2022.04.20
React Jsx 🌱  (2) 2022.04.18
React 재조정 🌱  (1) 2022.04.15
React hook 🌱  (1) 2022.04.14
반응형

jsx는 React.createElement를 반환하기 위한 Syntatic sugar 입니다.

Syntatic Sugar은 좀 더 편리하게 사용하기 위해 도와주는 것으로 이해할 수 있습니다.

예를 들면 Javascript의 class, React의 jsx가 있습니다.

 

javascript의 Class는 babel로 변환해 보면 prototype으로 구성되어 있음을

React의 jsx는 bable로 변환해 보면 React.createElement를 반환함을 확인할 수 있습니다.

둘다 원래는 직접 prototype, React.createElement로 작성해야 했던것을 Class, jsx를 사용함으로써 편리하게 사용할 수 있는 것이죠

 

class를 babel로 변환한 모습
jsx를 babel로 변환한 모습

jsx는 클래스형 컴포넌트에서 사용합니다. render내부에서 return 되면 됩니다.

 

공식문서에 나와있는 설명을 약간 곁들이겠습니다

JSX는 Javascript를 확장한 문법입니다. 
JSX는 React "엘리먼트(element)"를 생성합니다. 

 

아래의 설명또한 공식 문서에 나와있는 설명입니다.

 

JSX도 표현식입니다.

 

컴파일이 끝나면, JSX 표현식이 정규 Javascript 함수 호출이 되고 Javascript객체로 인식됩니다.

즉 JSX를 if 구문 및 for loop 안에 사용하고, 변수에 할당하고, 인자로서 받아들이고, 함수로부터 반환할 수 있습니다.

function getGreeting(user){
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>
  }
  return <h1>Hello, Stranger</h1>
}

 

공식문서에서는 JSX는 HTML보다는 javascript에 가깝기 때문에 jsx내에서 javascript에서 프로퍼티를 명명하는 규칙인 camelCase를 따른다 합니다. 그래서 className, style내부의 속성들에서도 backgroundColor와 같이 사용하여야 한다고 합니다!!

 

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
)


const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
)

 

 

결론: JSX는 React 엘리먼트를 생성하는 Syntatic Sugar 이다!!!

 

출처: https://ko.reactjs.org/docs/introducing-jsx.html

 

JSX 소개 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

728x90
반응형

React의 재조정(Reconciliation)에 대해 정리해보려 합니다.

여기서 말하는 재조정에서는 diffing 알고리즘을 어떻게 정의했는지에 관한 것입니다.

diffing 알고리즘에에 따라 가상 DOM과 실제 DOM의 차이를 판단합니다.

그리고 변경이 필요한 부분만 대체가 되는 것입니다.

 

두개의 가정을 먼저 인지하여야 합니다.

  1. 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
  2. 개발자가 key prop을 통해, 어떤 자식 element가 변경되지 않아야 하는지를 표시해 줄 수 있다.

 


diffing 알고리즘 

 

엘리먼트의 타입이 다른경우

리액트에서는 두 개의 트리를 비교할때, 두 엘리먼트의 root 엘리먼트부터 비교를 하게 됩니다.

이 root 엘리먼트가 다르다면 root 엘리먼트를 포함한 트리는 변경됩니다.

<div>
  <Counter />
</div>


<p>
  <Counter />
</p>

위의 경우에서 div -> p 로 루트 엘리먼트의 타입이 변경되었습니다.

가차없이 이전의 Counter는 사라집니다. 이전 트리에 연관된 state들도 모두 사라집니다.

 

엘리먼트의 타입은 같은데 속성만 다른 경우

<div className="before" title="stuff" />
<div className="after" title="stuff" />

<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />

두 개의 case모두 엘리먼트의 타입은 같지만 속성(className, color) 만 변경되었습니다. 

이러한 경우에는 동일한 내역은 유지하고 변경된 속성들만 갱신합니다.

 

targeting 중인 DOM node의 갱신이 끝나면 자식들을 재귀적으로 처리합니다.

 


자식 엘리먼트의 key값이 다른 경우

이 글을 읽으신다면 배열 요소를 jsx로 리턴을 할때 key를 넣어주지 않아서 warning이 뜨신 경험이 있으실 겁니다.

key 값을 넣어주지 않는다면 default로 index가 key값으로 들어갑니다.

배열 요소의 끝에 push를 해줄때는 문제가 없습니다. Index가 변경되지 않기 때문입니다.

문제는 unshift 입니다.

기존에 위치하고 있던 친구들의 index가 바뀌어 버려 불필요한 렌더링을 해주어야 되거나 의도한 대로 화면에 표시가 되지 않을 수 있습니다.

key값에 index를 넣지 않아야 하는 이유에 관하여는 이전에 작성한 글이 있습니다.

2022.04.02 - [Frontend/React] - React keys

 

 

React에서는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인합니다.

<ul>
  <li key="2014">2014</li>
  <li key="2015">2015</li>
</ul>


<ul>
  <li key="2013">2013</li>
  <li key="2014">2014</li>
  <li key="2015">2015</li>
</ul>

위의 코드에서는 Key값을 추가해주었고 key값으로 인해 React에서는 기존의 2014, 2015 key값을 가진 엘리먼트 들은 위치만 이동시키면 된다는 것을 알수 있습니다.

 

이 key는 같은 형제 노드에서만 유일하면 됩니다!!

 

 

출처: https://ko.reactjs.org/docs/reconciliation.html

 

재조정 (Reconciliation) – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

728x90

'Frontend > React' 카테고리의 다른 글

React Redux-saga's work flow from component 🙋‍♂️  (0) 2022.04.19
React Jsx 🌱  (2) 2022.04.18
React hook 🌱  (1) 2022.04.14
React 연속된 요청 useEffect 내에서 처리하기 (useEffect, suspense)  (1) 2022.04.10
React suspense  (1) 2022.04.10
반응형

React Hook이란?

React Hook은 자바스크립트 함수 입니다. 

Hook은 class를 작성하지 않고도 state와 React의 기능들을 사용할 수 있게 해줍니다

 

Hook이 등장한 이유는 다음과 같습니다.

  • 컴포넌트 사이에서 상태로직을 재사용하기 어렵습니다. -> Custom hook의 등장배경
    - Render Props와 HOC 같은 패턴은 코드의 파악이 어렵다
    - 상태 관련 로직을 추상화 하기가 어렵다 (이 내용은 클래스형 컴포넌트 공부후에 이해가 더 될듯)
  • Class는 사람과 기계를 혼동시킵니다.
    - this가 동적으로 결정되기 때문에 클래스 내부에서 파악하기가 어렵다
    - class는 코드의 최소화를 힘들게 만들고, 핫 리로딩을 깨지기 쉽고 신뢰할 수 없게 만듭니다.
  • 복잡한 컴포넌트들은 이해하기가 어렵다
    - 생명주기 class 메서드가 관련이 없는 로직들은 모아놓고, 관련이 있는 로직들은 여러개의 메서드에 나누어 놓는 경우가 자주 있었다.
componentDidUpdate() {
  // 데이터 요청
  // 이벤트 리스너 등록(해당 컴포넌트에서만 사용되기 때문에 재사용 되기 어렵다)
}

componentWillUnmount() {
  // 이벤트 리스너 해제(해당 컴포넌트에서만 사용되기 때문에 재사용 되기 어렵다)
}




// effect에서 코드 중복의 예시
componentDidMount() {
  document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
  document.title = `You clicked ${this.state.count} times`;
}

// class안의 두개의 생명주기 메서드에 같은 코드가 중복되어 있다 
// => 마운트 된 후, 업데이트 된 후에 동일한 결과를 보여주어야 하기 때문에 중복된 코드가 있다 
// 이를 useEffect를 사용하면 아래와 같이 사용하여 중복을 줄일 수 있다.

useEffect(() => {
  document.title = `You clicked ${this.state.count} times`;
})

Hook의 종류에는 useState, useEffect, useReducer, useContext, useCallback, useMemo, useRef, useLayoutEffect 등이 있습니다.

 

Hook을 사용할 때는 두가지 규칙을 지켜야 합니다.

  1. 최상위 레벨 에서만 Hook을 호출한다.
    반복문이나 조건문에서 Hook을 호출하면 안됩니다.
  2. 오직 React 함수 내에서 Hook을 호출한다.
    React 함수 컴포넌트 내부에서 Hook을 호출하여야 합니다.
    커스텀 훅에서 Hook을 호출하여야 합니다.

 

최상위 레벨에서 사용해야 하는 이유는 다음과 같습니다.

React에서는 Hook이 호출된 순서에 의존하기 때문입니다.

function Form() {
  const [name, setName] = useState('Mary');
  useEffect(function persistForm(){
    localStorage.setItem('formData', name);
  })
  const [surname, setSurname] = useState('Poppins');
  useEffect(function updateTitle(){
    document.title = name + ' ' + surname; 
  })
}


// 첫 번째 렌더링
useState('Mary');         //  1. Mary라는 name state 변수를 선언합니다.
useEffect(persistForm)    //  2. 폼 데이터를저장하기 위한 effect를 추가합니다.
useState('Poppins');      //  3. Poppins라는 name state 변수를 선언합니다.
useEffect(persistTitle)   //  4. 제목을 업데이트 하기 위한 effect를 추가합니다.

// 두번째 렌더링
useState('Mary');         //  1. name state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(persistForm)    //  2. 폼 데이터를 저장하기 위한 effect가 대체됩니다.
useState('Poppins');      //  3. surname state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(persistTitle)   //  4. 제목을 업데이트 하기 위한 effect가 대체됩니다.

 

만약 아래와 같이 조건문이 추가되어 훅이 호출되는 순서가 변경되었다면 

// 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
}

useState('Mary')           // 1. name state 변수를 읽습니다. (인자는 무시됩니다)
// useEffect(persistForm)  // 🔴 Hook을 건너뛰었습니다!
useState('Poppins')        // 🔴 2 (3이었던) surname state 변수를 읽는 데 실패했습니다.
useEffect(updateTitle)     // 🔴 3 (4였던) 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.

 

리액트 Hook은 호출된 순서에 의존하기 때문에 위와 같이 조건문에 의해 호출 순서가 변경된 경우에는 사용하면 안됩니다.

만약 조건문을 사용하고 싶다면 아래와 같이 사용하면 됩니다.

useEffect(function persistForm() {
    // 👍 더 이상 첫 번째 규칙을 어기지 않습니다
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
});

Custom Hook이란?

커스텀 훅을 사용하면 컴포넌트 로직을 함수로 뽑아내어 재사용할 수 있습니다.

주로 여러 컴포넌트에서 반복되는 로직인 경우 커스텀 훅을 사용하거나 하였습니다.

이렇게 커스텀 훅으로 작성하여 코드의 중복을 줄이고 코드의 파악이 좀더 수월할 수 있도록 하였습니다.

 

하나의 컴포넌트에서 여러 상태를 거느려야 할 경우에는 복잡한 상태 + 코드의 양이 많아져 컴포넌트의 재사용, 파악이 어려워 졌었습니다.

이러한 경우에도 커스텀 훅을 사용하여 코드의 양을 줄이고 파악에 수월할 수 있도록 하였습니다.

 


함수형 컴포넌트란 ??

리액트에서 컴포넌트를 정의하는 방법중 하나로 javascript 함수를 이용하여 React element를 반환하는 컴포넌트를 말합니다.

 

컴포넌트를 정의하는 가장 간단한 방법은 Javascript 함수를 작성하는 것입니다.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>
}

이 함수는 데이터를 가진 하나의 "props" 객체 인자를 받은 후 React 엘리먼트를 반환하므로 유효한 React 컴포넌트입니다. 이러한 컴포넌트는 Javascript 함수이기 때문에 말 그대로 "함수 컴포넌트"라고 호칭합니다.


함수형, 클래스형 컴포넌트, 훅의 등장 순서

 

  • 함수, 클래스형 컴포넌트 -> 훅 등장

 

함수형, 클래스형 컴포넌트가 먼저 존재하였습니다.

하지만 함수형 컴포넌트에서는 상태관리를 할 수 가 없었습니다.

단순히 props만 전달받아 렌더링만 해주는 형식이였죠

Stateless Functional Component라는 키워드로 검색하면 더 많은 정보를 알 수 있을 것입니다.

 

이후에 16.8 버전에서 훅이 등장합니다.

훅은 개인 개발자에 의해 만들어졌고 페이스북에서 훅을 구매하여 페이스북에 입사하신걸로 알고 있습니다.(실력 매우 부럽네요)

훅으로 인해 함수형 컴포넌트에서 상태관리가 가능해졌습니다.

 

훅이 등장한 이유는 위에서 설명했지만 간략히만 다시 알아보면

1. 컴포넌트간에 상태 관리 로직의 상태 불가능

2. 복잡해진 컴포넌트(클래스 상태관리 메서드에서 관련 없는 메서드는 모이고 관련 있는 메서드는 헤어지는 상황 다수 발생)

3. this가 초래한 사람, 기계의 혼잡도(this는 사람이 인식하기 힘들고, 컴파일을 진행할때 class는 컴파일을 지연시키는 상황이 발생했었다 합니다)

 

그래서 훅이 등장했고 훅을 함수형 컴포넌트에서 사용하게끔 되었습니다.

훅은 클래스형 컴포넌트에서는 동작하지 않습니다.

훅의 디테일한 내용과 직접 구현해보는 상황은 추후에 포스팅할 예정입니다.

 

 

출처 : https://ko.reactjs.org/docs/hooks-intro.html

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

https://jooonho.com/react/2021-05-01-hooks/

 

React Hook

Hook에 대해서 알아보자

jooonho.com

https://ko.reactjs.org/docs/components-and-props.html

 

Components와 Props – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

728x90

+ Recent posts