React의 복합 구성 요소 패턴

개발하는 동안 우리는 React에서 몇 가지 디자인 패턴에 직면합니다. 복합 컴포넌트는 React에서 가장 중요하고 자주 사용되는 디자인 패턴 중 하나입니다. React를 사용하여 Expandable Accordion 구성 요소를 만들어 보겠습니다.

복합 구성 요소는 부모 없이는 사용할 수 없는 두 개 이상의 구성 요소로 구성된 구성 요소입니다.

선택 상자가 그 예입니다.



먼저 Expandable 구성요소를 설정합니다. 다음은 이와 함께 제공되는 코드입니다.

import React, {createContext} from React;
const ExpandableContext = createContext();
const {Provider} = ExpandableContext;

const Expandable = ({children}) => {
    return <Provider>{children}</Provider>
}

export default Expandable;


여기에서 다음과 같은 일이 발생합니다
  • ExpdandableContext가 생성됩니다.
  • 공급자가 ExpandableContext에서 해체됨
  • 결국 확장 가능한 구성 요소를 생성하고 확장 가능한 구성 요소에 전달된 하위 항목을 표시하는 공급자와 함께 JSX를 반환합니다
  • .

    이제 우리는 확장된 아코디언에 대한 상태를 도입하고 이에 대한 토글 기능까지 만들어야 합니다.

    const Expandable = ({children}) => {
    
        /**
         * State to update the expanded behaviour
         */
        const [expanded, setExpanded] = useState(false);
    
        /**
         * Method for toggling the expanded state
         */
        const toggle = setExpanded(prevExpanded => !prevExpanded);
    
        return <Provider>{children}</Provider>
    }
    


    이제 토글 콜백 함수는 확장 가능한 헤더에 의해 호출되며 매번 변경되거나 다시 렌더링되지 않아야 합니다. 따라서 다음과 같이 콜백을 메모할 수 있습니다.

    그런 다음 - 토글 기능을 전달하고 공급자로 확장해야 합니다. 따라서 다음 줄을 작성합니다.

    const value = { expanded, toggle }
    


    매번 값이 다시 렌더링되는 것을 방지하기 위해 우리는 렌더링할 때마다 개체를 보존하기 위해 useMemo를 사용합니다.

    const value = useMemo(()=> {expanded, toggle}, [expnded, toggle]);
    


    확장 후 사용자 지정 기능을 제공할 수 있도록 외부 사용자에게 유연성 제공

    경우에 따라 아코디언이 확장된 후 사용자에게 사용자 지정 기능을 제공해야 할 수도 있습니다. 이 경우 아래 패턴을 따를 수 있습니다.

    클래스 구성 요소의 경우 콜백을 사용하여 이 작업을 수행할 수 있지만 기능 구성 요소의 경우 useeffect를 사용하여 이 작업을 수행해야 하며 기능 구성 요소가 이미 마운트된 경우에만 이를 실행해야 합니다(구성 요소가 매번 마운트될 때 실행되지 않아야 함).

         * Check for mounting
         */
        const componentJustMounted = useRef(true);
    
        /**
         * Function to call when the expanded state is altered tp true, 
         * that is when the expansion happens. 
         */
        useEffect(()=> {
            if(!componentJustMounted.current){
                onExpand(expanded);
            }
            componentJustMounted.current = false
        }, [expanded]) 
    


    렌더링 주기 동안 보존될 참조를 반환하므로 useRef를 사용하고 있습니다. 처음에는 true로 설정되어 있습니다. 확장된 prop이 전달된 상태에서 콜백이 실행될 때만 false로 만듭니다.

    따라서 전체 구성 요소 Expandable.js는 다음과 같습니다.

    import React, {createContext, useState, useCallback, useRef, useEffect} from 'react';
    const ExpandableContext = createContext();
    const {Provider} = ExpandableContext;
    
    const Expandable = ({children}) => {
    
        /**
         * State to update the expanded behaviour
         */
        const [expanded, setExpanded] = useState(false);
    
        /**
         * Check for mounting
         */
        const componentJustMounted = useRef(true);
    
        /**
         * Function to call when the expanded state is altered tp true, 
         * that is when the expansion happens. 
         */
        useEffect(()=> {
    
            if(!componentJustMounted.current){
                onExpand(expanded);
            }
            componentJustMounted.current = false
        }, [expanded, onExpand])
    
        /**
         * Method for toggling the expanded state
         */
        const toggle = useCallback(() => 
            setExpanded(prevExpanded => !prevExpanded), []
        );
    
        const value = useMemo(()=> {expanded, toggle}, [expanded, toggle])
    
        return <Provider value={value}>{children}</Provider>
    }
    
    export default Expandable;
    
    


    하위 구성 요소 빌드

    본문, 헤더 및 아이콘의 세 가지 구성 요소는 다음과 같습니다.

    Header.js

    import React, { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    
    const Header = ({children}) => {
      const { toggle } = useContext(ExpandableContext)
      return <div onClick={toggle}>{children}</div>
    }
    export default Header; 
    


    여기서는 토글에 액세스하고 클릭하면 div를 클릭하면 본문이 토글됩니다. 이것은 아코디언의 기본 기능입니다.

    바디용,

    Body.js

    import { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    
    const Body = ({ children }) => {
      const { expanded } = useContext(ExpandableContext)
      return expanded ? children : null
    }
    export default Body
    


    본문에서 확장 속성이 true인지 여부를 확인합니다. 이것이 사실이면 우리는 본문을 props.children으로 설정하고, 그렇지 않으면 null을 반환합니다(본문이 확장되지 않았기 때문에).

    아이콘의 경우 다음과 같은 Icon.js를 사용할 수 있습니다.

    Icon.js

    // Icon.js
    import { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    
    const Icon = () => {
      const { expanded } = useContext(ExpandableContext)
      return expanded ? '-' : '+'
    }
    export default Icon
    


    확장된 몸체는 - 기호로, 수축된 몸체는 +로 표시됩니다.

    이러한 논리를 추가한 후 이러한 각 요소에 스타일만 추가하면 마지막으로 구성 요소가 다음과 같이 표시됩니다.

    Expandable.js

    import React, {
      createContext,
      useState,
      useCallback,
      useRef,
      useEffect,
      useMemo,
    } from "react";
    export const ExpandableContext = createContext();
    const { Provider } = ExpandableContext;
    
    const Expandable = ({ onExpand, children, className = "", ...otherProps }) => {
      const combinedClasses = ["Expandable", className].filter(Boolean).join("");
    
      /**
       * State to update the expanded behaviour
       */
      const [expanded, setExpanded] = useState(false);
    
      /**
       * Check for mounting
       */
      const componentJustMounted = useRef(true);
    
      /**
       * Method for toggling the expanded state
       */
      const toggle = useCallback(
        () => setExpanded((prevExpanded) => !prevExpanded),
        []
      );
    
      /**
       * Function to call when the expanded state is altered tp true,
       * that is when the expansion happens.
       */
      useEffect(() => {
        if (!componentJustMounted.current) {
          onExpand(expanded);
        }
        componentJustMounted.current = false;
      }, [expanded, onExpand]);
    
      const value = useMemo(() => ({ expanded, toggle }), [expanded, toggle]);
    
      return (
        <Provider value={value}>
          <div className={combinedClasses} {...otherProps}>{children}</div>
        </Provider>
      );
    };
    export default Expandable;
    


    Body.js

    // Body.js
    import './Body.css'
    import { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    
    const Body = ({ children , className='',... otherProps}) => {
      const { expanded } = useContext(ExpandableContext);
      const combinedClassName = ['Expandable-panel', className].filter(Boolean).join('');
      return expanded ? 
      <div className ={combinedClassName} {...otherProps} >{children}</div> : null
    }
    export default Body
    


    Header.js

    import React, { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    import './Header.css';
    const Header = ({className='', children, ...otherProps}) => {
    
      const combinedClassName = ['Expandable-trigger',className].filter(Boolean).join('');
    
      const { toggle } = useContext(ExpandableContext)
      return <button className={combinedClassName} {...otherProps}
      onClick={toggle}>{children}</button>
    }
    export default Header;
    


    Icon.js

    import { useContext } from 'react'
    import { ExpandableContext } from './Expandable'
    
    const Icon = ({ className='', ...otherProps}) => {
      const { expanded } = useContext(ExpandableContext);
      const combinedClassName = ['Expandable-icon', className].join('');
      return <span className={combinedClassName} {...otherProps}>{expanded ? '-' : '+'}</span>
    }
    export default Icon
    


    https://officialbidisha.github.io/exapandable-app/에서 동작을 볼 수 있습니다.

    github 코드는 https://github.com/officialbidisha/exapandable-app에서 사용할 수 있습니다.

    이것이 복합 구성 요소가 작동하는 방식입니다. 헤더, 아이콘, 본문 없이 확장 가능한 구성 요소를 사용할 수 없으며 그 반대도 마찬가지입니다. 우리는 이제 성공적으로 디자인 패턴을 배웠습니다.

    행복한 학습!

    좋은 웹페이지 즐겨찾기