React Compound Component Pattern

React Compound Component Pattern에 대해 공부해보자.

리액트 컴파운드 컴포넌트 패턴 소개

w/o 컴파운드 컴포넌트 패턴 리액트 코드

function App() {
    const options = [
        { value: 'apple', label: 'Apple' },
        { value: 'banana', label: 'Banana' },
        { value: 'orange', label: 'Orange' },
    ];
    return (
        <div className="App">
            <Select options={options} />
        </div>
    );
}

const Select = ({ options = [] }) => {
    // 상태를 이용해 메뉴가 열렸는지 닫혔는지 관리
    const [isOpen, setIsOpen] = useState(false);
    // 현재 어떤 옵션이 선택되었는지 저장
    const [selectedValue, setSelectedValue] = useState(null);

    // 메뉴 열림/닫힘을 제어
    const toggle = () => setIsOpen(!isOpen);
    // 옵션 선택 시 상태를 갱신하고 메뉴를 닫음
    const selectOption = (value) => {
        setSelectedValue(value);
        setIsOpen(false);
    };

    return (
        <div
            className="select-container"
        >
            <Trigger
                isOpen={isOpen}
                selectedValue={selectedValue}
                onToggle={toggle}
            />
            <Menu isOpen={isOpen}>
                {options.map((option) => (
                    <Option
                        key={option.value}
                        value={option.value}
                        onSelect={selectOption}
                    >
                        {option.label}
                    </Option>
                ))}
            </Menu>
        </div>
    );
};

export default Select;
const Trigger = ({ isOpen, selectedValue, onToggle }) => {
    return (
        <button
            onClick={onToggle}
        >
            {selectedValue ?? 'Select an option'} {isOpen ? '' : ''}
        </button>
    );
};
const Menu = ({ isOpen, children }) => {
    if (!isOpen) return null;
    return (
        <ul>
            {children}
        </ul>
    );
};

const Option = ({ value, onSelect, children }) => {
    return (
        <li
            onClick={() => onSelect(value)}
        >
            {children}
        </li>
    );
};

w/o 컴파운드 컴포넌트 패턴 문제점

w/ 컴파운드 컴포넌트 패턴 리액트 코드

function App() {
    return (
        <div className="App">
            <Select>
                <Select.Trigger />
                <Select.Menu>
                    <Select.Option value="apple">Apple</Select.Option>
                    <Select.Option value="banana">Banana</Select.Option>
                    <Select.Option value="orange">Orange</Select.Option>
                </Select.Menu>
            </Select>
        </div>
    );
}

const SelectContext = createContext();

export function useSelectContext() {
    const context = useContext(SelectContext);
    if (!context) {
        throw new Error(
            'Select compound components must be used within a Select'
        );
    }
    return context;
}

const Select = ({ children }) => {
    const [isOpen, setIsOpen] = useState(false); // 메뉴 열림/닫힘 상태
    const [selectedValue, setSelectedValue] = useState(null); // 선택된 옵션 값

    const toggle = () => setIsOpen((prev) => !prev); // 메뉴 열림/닫힘 토글 함수
    const selectOption = (value) => {
        setSelectedValue(value); // 새로운 값 선택
        setIsOpen(false); // 선택 후 메뉴 닫기
    };

    const value = { isOpen, selectedValue, toggle, selectOption };

    return (
        <SelectContext.Provider value={value}>
            <div
            >
                {children}
            </div>
        </SelectContext.Provider>
    );
};

export default Select;

Select.Trigger = Trigger;
Select.Menu = Menu;
Select.Option = Option;
const Trigger = () => {
    const { isOpen, toggle, selectedValue } = useSelectContext();
    return (
        <button
            onClick={toggle}
        >
            {selectedValue ?? 'Select an option'}{' '}
            <span>{isOpen ? '' : ''}</span>{' '}
        </button>
    );
};

const Menu = ({ children }) => {
    const { isOpen } = useSelectContext();
    return isOpen ? (
        <ul>
            {children}
        </ul>
    ) : null; // isOpen이 true일 때만 목록 표시
};

const Option = ({ value, children }) => {
    const { selectOption } = useSelectContext();
    return (
        <li
            onClick={() => selectOption(value)}
        >
            {children}
        </li>
    );
};

w/ 컴파운드 컴포넌트 패턴의 장점

정리

끝!