일부 반응.

24814 단어 reactwebdev
다음은 프로젝트를 만들면서 배운 몇 가지 개념과 실습입니다.
  • Batching
  • Reconciliation
  • Composition
  • React Router v6
  • CSS Modules
  • useRef Hook

  • 반응에서의 배치.

    When I started using useState , I had a misconception that the component is rendered as soon as the setState statement is encountered. I realized this later that React does not render right after the setState statement. Any state update operations are batched together and queued to be computed when the useState is invoked during the next render. The component renders only when the event handler function has executed all the code that it had. Then, during the next render, those state updates that were batched are computed and the value is returned to the state variable. Thus, when the component renders and the hooks are invoked, they return the updated value to the state. Here is an example,

    export default function App() {
      const [a, setA] = useState(1);
    
      const handleBtnClick = () => {
        setA(a + 1);
        setA(a + 1);
        setA(a + 1);
        setA(a + 1);
      };
    
      console.log("rendered", a);
    
      return (
        <div className="App">
          <button onClick={handleBtnClick}>do it</button>
        </div>
      );
    }
    

    If you run this, you will see that the console.log will run only once and give the value 2 . If you have used React.StrictMode you might get the same output twice. Now, this example brings me to the next point which is the updater function.

    In this example, since we are providing an operation inside setA , the setA is converted to setA(1+1) which is converted to setA(2) and then these updates are queued for the next render. Then, during the next render, useState has four state updates and all of them are setA(2) . Why 2 for all of them? That is because every render has it's own state value and this state value does not change for that particular render. You might have noticed that in the line where useState(1) is invoked and we are de-structuring the array into the state value and state update function, we have used const . This means that we cannot change the state value during the same render. The previous render had the value of 1 for a . Therefore, all the setA statements were read as setA(2) . The final value that is returned to the component is 2 .

    In case of a scenario where we want to serially update the state unlike the scenario mentioned above where the state was only replaced by the same constant value of 2 on every update, we would use an updater function.

    An updater function is a callback function that is supplied to the setA . It's argument is the latest state value prior to this calculation. Here is an example,

    const handleBtnClick = () => {
        setA((a) => a + 1); // this returns 2
        setA((a) => a + 1); // this returns 3
        setA((a) => a + 1); // this returns 4
        setA((a) => a + 1); // this returns 5
      };
    

    By giving a callback function, we are telling React to calculate the state value during the next render.

    화해

    React uses this algorithm to make sure the DOM rendering is as efficient as possible. React has the diffing algorithm through which it narrows down which elements are different so that only those elements are updated in the browser DOM. This process starts with ReactDOM.render() method. This render method creates a virtual DOM. During diffing, the newly created virtual DOM is compared with the virtual DOM before the state update. But first, a bit about virtual DOM.

    Virtual DOM is a JSON object that represents the browser DOM. It is extremely fast compared to the browser DOM. It is created from scratch on every state update.

    How does React compare the corresponding elements from two different virtual DOMs? For that, let me show you this example,

    console.log(
    createElement(
    <p className="App">some random text</p>
    ));
    

    This code gives the following output,



    React는 이와 같은 모든 노드를 보고 각각의 속성을 비교합니다. 다르게 호출되는 요소의 경우 이러한 속성 중 하나가 이전 가상 DOM의 동일한 요소 속성과 달라야 합니다.

    모든 자식 노드는 children 개체에 언급되어 있습니다. React는 하위 노드에 대해 고유한 키가 있다는 경고를 표시합니다. React는 다른 요소를 볼 때마다 해당 요소뿐만 아니라 모든 자식도 다시 만듭니다. 따라서 고유한 키를 갖는 것은 React가 특정 자식 노드가 새로운지, 업데이트되었는지 또는 자식 목록에서 제거되었는지를 결정하는 데 도움이 됩니다.

    키가 없는 경우 목록 위에 자식을 추가하면 전체 목록이 파괴되고 다시 생성된다는 의미입니다. 그러나 키가 있으면 전체 목록을 파괴하는 대신 목록에 새 요소를 추가하도록 React에 지시할 수 있습니다.

    하나 더. React Elements의 관점에서 전체 구성 요소 트리를 생각하십시오. 루트 구성 요소에는 하위 구성 요소가 나열되는 위치children가 있고 이들 중 일부에는 children도 있습니다. 이와 같이 계속하면 루트에서 시작하여 리프 노드가 HTML 요소인 트리가 형성되고 있다고 상상할 수 있습니다. 이것은 차이점을 발견하기 위해 diff하는 동안 React가 순회하는 구성 요소 트리입니다. 이 트리를 탐색하기 위해 React는 너비 우선 접근 방식을 사용합니다. 깊이 우선 순회에 대한 사례를 만들기 위해 diff하는 동안 React가 리프 노드가 다른 것을 확인한다고 가정해 봅시다. 따라서 이 노드를 파괴하고 다시 생성합니다. 이제 부모 노드로 이동하여 이 노드도 다른지 확인합니다. 이 부모 노드와 하위 트리를 파괴하고 전체 하위 트리를 다시 만듭니다. 너비 우선 순회가 있었다면 단일 작업에서 동일한 작업을 수행할 수 있습니다. React는 자식 노드로 직접 이동하는 대신 먼저 부모 노드를 확인합니다.

    diffing 프로세스가 완료되면 React는 브라우저 DOM에서 수행해야 하는 최소 업데이트 목록을 준비합니다.

    구성

    React uses the idea of function composition from JS. Components can be composed in a similar manner. Higher order component is one such function that takes the child component as an argument and returns this component wrapped in the parent component. What component is passed as an argument will change depending on the use case. Here is an example,

    const FeedPageWrapper = PostsSection => {
    
        const FeedPage = () => {
    
            return (
                <div
                    className={`bg-off-secondary ${styles.feedPageGrid}`}>
                    <div className={`${styles.navDiv} flx flx-maj-end`}>
                        <NavBar />
                    </div>
                    <PostsSection /> {/*child component used here*/} 
                    <div className={styles.extraDiv}>
                        {/* third div empty for now.*/}
                    </div>
                </div>
            )
        }
        return FeedPage
    }
    
    export default FeedPageWrapper
    

    In the above example, I have a higher order component that takes a component called PostsSection . This component that is passed as an argument will differ based on the page that the user is on. If the user is on bookmarks page, PostsSection will have a list of bookmarked posts. If the user is on user-feed page, PostsSection will have a list of posts personalized for the user.

    Apart from the PostsSection , everything else on the page will be the same. Hence, I decided to use the higher order component here. Here is how this component will be used,

    const BookmarksFeedPage = FeedPageWrapper(BookmarksSection)
    
    export default BookmarksFeedPage
    

    I have passed BookmarksSection as the argument and BookmarksFeedPage is returned that is exported in the next line. Similarly for the user-feed page,

    const UserFeedPage = FeedPageWrapper(UserFeedSection)
    
    export default UserFeedPage
    

    반응 라우터를 사용하는 개인 경로

    By private routes I mean the routes that are personalized for the users and should only be shown if a user is logged in. For example, in an e-commerce app, wish-list and cart pages are the private routes because they will have different data for different users, unlike the products page.

    Here is the algorithm,

    1. Check if the current route is private or not.
    2. If it is, then check whether the user is logged in or not.
    3. If user is logged in, let the user continue with this route.
    4. If user is not logged in, re-direct the user to the login page.
    5. If the current route is not private, then let the user continue with this route.  
    <Route path={ROUTE_CART} element={
              <RequireAuth>
                <Cart />
              </RequireAuth>
    } />
    

    In the above code, I have wrapped <Cart/> inside <RequireAuth/> which checks whether the user is logged in or not.
     

    const RequireAuth = ({ children }) => {
            const location = useLocation()
            return isUserLoggedIn ? children : <Navigate to='/login' state={{ from: location }} replace />
        }
    

    The above code shows that user's log in status is maintained in the state isUserLoggedIn . <Navigate/>
    is a component in react-router@6 which takes a parameter to to navigate to a particular location.
    The user's current location is also saved inside the state of Navigate so that after log in, user can be redirected to this location. Here is the code for that,
     

    const from = location.state?.from?.pathname
    navigate(from, { replace: true })
    

    Setting replace to true means that the login page would be removed from the history stack of the browser. This is helpful when the user presses the back button, the app skips the login page and goes to page that was opened previous to the login page.

    CSS 모듈

    Initially I had used normal CSS stylesheets in my projects. This was raising specificity conflicts because every stylesheet had a global scope. CSS modules resolved this issue because it limits the scope of stylesheet to the file that it is imported in.

    useRef

    I have used useRef in one of my apps to make DOM manipulations. The requirement was that whenever a user clicks on any option from the given options, the app should change the background color of that option to red or green depending upon whether the answer was correct or wrong. Here is the first part of logic,
     

    optionBtnRefs.current = currentQues?.options?.map((option, i) => optionBtnRefs[i] ?? createRef())
    

    This code is creating an array of refs for each option of the question. Then,
     

    <button key={index} ref={optionBtnRefs.current[index]} onClick={() => handleOptionSelect(optionBtnRefs.current[index])} value={option}>{option}</button>
    

    When mapping over the options, I have assigned a ref to each option and the same ref is passed to onClick event handler. Then,
     

    if (ref.current.value === currentQues.answer) {
                ref.current.style.background = 'var(--clr-success)'
                setTimeout(() => { ref.current.style.background = 'var(--clr-primary)'; setScore(s => s + 1) }, 1000)
    
            } else {
                ref.current.style.background = 'var(--clr-error)'
                setTimeout(() => ref.current.style.background = 'var(--clr-primary)', 1000)
            }
    

    Depending upon whether the chosen option is the correct answer or not, I have updated the background of that particular ref . The background is restored to normal after 1 second.

    These were some of the things I wanted to share. Hope it helps. Thanks for reading.

    좋은 웹페이지 즐겨찾기