OOP(Object-Oriented Programming)에 대한 이해는 웹 개발 분야에서 매우 중요하다. 특히 프로젝트가 클 수록, 장기간동안 유지보수 해야하는 프로그램일수록 더더욱 그러하다. 그런데 React 같은 경우 더 이상 class를 사용하는 방식이 지양되는데 모든 OOP 설명에서 class로 설명하고 object로 설명한다. 이를 최신 React에는 어떻게 적용하고 활용할 수 있는가? 아래에서 각 주제를 자세히 다뤄보자.
OOP의 기본 원리 및 React에서의 사용 예시:
객체 지향 프로그래밍(OOP)은 소프트웨어 개발 방법론 중 하나로, 코드를 객체(데이터와 메서드로 이루어진 엔티티)의 모음으로 구성한다. OOP의 주요 원리에는 다음과 같은 개념들이 있다.
클래스(Class)
클래스는 객체를 만들기 위한 템플릿이다. 예를 들어, “자동차” 클래스에는 모든 자동차가 가져야 하는 특성과 동작을 정의할 수 있다.
객체(Object)
- 클래스의 인스턴스로, 실제 데이터와 메서드를 포함한다. “자동차” 클래스로부터 “BMW 3 시리즈”나 “현대 엘란트라”와 같은 객체를 생성할 수 있다.
- 위에서 예시를 든 클래스랑 객체의 개념을 React에 적용시키면 아래와 같이 나타낼 수 있다.
const Car = ({ make, model }) => {
return (
<div>
<h2>Car Details</h2>
<p>Make: {make}</p>
<p>Model: {model}</p>
</div>
);
}
const App = () => {
// "BMW 3" object
const bmw = { make: 'BMW', model: '3 series' };
// "Hyundai Elantra" object
const hyundai = { make: 'Hyundai', model: 'Elantra' };
return (
<div>
<h1>My Cars</h1>
<Car make={bmw.make} model={bmw.model} />
<Car make={hyundai.make} model={hyundai.model} />
</div>
);
}
export default App;
이 예시에서는 함수형 컴포넌트 Car
를 정의하고, 다양한 자동차 객체를 생성하여 사용한다. React를 통해 컴포넌트를 만들 때, 이 객체 지향적인 개념을 활용하여 컴포넌트를 재사용하고 유지 관리할 수 있다. 객체와 클래스의 개념은 React 개발에서도 유용하게 적용될 수 있으며, 코드의 가독성과 재사용성을 높일 수 있다.
상속(Inheritance)
- 다른 클래스에서 정의한 특성과 동작을 상속받아 새로운 클래스를 만들 수 있는 기능이다. 이를 통해 코드 재사용성과 확장성을 높일 수 있다.
- 상속은 React의 class component로는 활용할 수 있지만 최신 trend인 function, hooks를 이용한 개발에서는 조금 다르다. 함수형 컴포넌트에서는 상속 대신에 **컴포넌트 합성 (Component Composition)**을 주로 사용한다. 함수형 컴포넌트에서 컴포넌트 합성은 다른 컴포넌트의 기능을 가져와 현재 컴포넌트에 통합하는 방법이다.
함수형 컴포넌트에서 컴포넌트 합성을 사용하는 몇 가지 예시를 살펴보겠다:
- Render Props 패턴: 다른 컴포넌트로부터 함수를 전달받아 해당 함수를 실행하여 JSX를 반환한다. 이 방법을 사용하면 다른 컴포넌트의 기능을 함수형 컴포넌트 내에서 사용할 수 있다.
const DataFetcher = ({ render }) => {
const data = fetchData(); // logic to fetch data
return <div>{render(data)}</div>;
}
const App = () => {
return (
<DataFetcher
render={(data) => (
<div>
<h1>Data: {data}</h1>
</div>
)}
/>
);
}
2. Hooks 사용: React의 Hooks를 사용하여 상태 및 생명주기 관리를 함수형 컴포넌트 내에서 처리할 수 있다. 예를 들어 useState
, useEffect
, useContext
등의 Hooks를 사용하여 다른 컴포넌트에서 사용하는 기능을 구현할 수 있다.
const Timer = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>Count: {count}</div>;
}
const App = () => {
return (
<div>
<h1>Timer App</h1>
<Timer />
</div>
);
}
3. Custom Hooks: 함수형 컴포넌트에서는 커스텀 Hooks를 작성하여 로직을 재사용할 수 있다. 이 커스텀 Hooks를 여러 함수형 컴포넌트에서 재사용할 수 있으며, 이것이 컴포넌트 합성의 한 예시이다.
// useCounter.js
const useCounter = (initialValue) => {
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(count + 1);
};
return { count, increment };
}
// App.js
import useCounter from './useCounter';
const App = () => {
const counter1 = useCounter(0);
const counter2 = useCounter(10);
return (
<div>
<div>
<h2>Counter 1: {counter1.count}</h2>
<button onClick={counter1.increment}>Increment</button>
</div>
<div>
<h2>Counter 2: {counter2.count}</h2>
<button onClick={counter2.increment}>Increment</button>
</div>
</div>
);
}
다형성(Polymorphism)
다양한 객체가 동일한 인터페이스를 사용하고 다르게 반응하는 능력을 나타낸다. 이를 통해 유연하고 일반적인 코드를 작성할 수 있다.
다형성은 리액트에서 다양한 컴포넌트가 동일한 인터페이스를 가지고 다르게 동작하도록 하는데 유용하게 활용된다.
다형성을 사용한 예시 중 하나는 버튼 컴포넌트를 만드는 것이다. 이 버튼 컴포넌트는 다양한 스타일과 동작을 가진 버튼을 생성할 수 있도록 한다.
먼저, 다양한 스타일의 버튼을 생성하는 버튼 컴포넌트를 만들어보겠다.
// Button component
const Button = ({ style, onClick, label }) => {
return (
<button style={style} onClick={onClick}>
{label}
</button>
);
}
// style objects
const primaryButtonStyle = {
backgroundColor: 'blue',
color: 'white',
};
const secondaryButtonStyle = {
backgroundColor: 'gray',
color: 'black',
};
const dangerButtonStyle = {
backgroundColor: 'red',
color: 'white',
};
const App = () => {
return (
<div>
<h1>Polymorphic Buttons</h1>
<Button style={primaryButtonStyle} onClick={() => alert('Primary button clicked')} label="Primary Button" />
<Button style={secondaryButtonStyle} onClick={() => alert('Secondary button clicked')} label="Secondary Button" />
<Button style={dangerButtonStyle} onClick={() => alert('Danger button clicked')} label="Danger Button" />
</div>
);
}
export default App;
이 예제에서 Button
컴포넌트는 style
속성을 통해 다양한 스타일을 가진 버튼을 생성할 수 있다. 이러한 다형성을 통해 동일한 인터페이스(Button
)를 가진 여러 버튼을 만들고 각각 다르게 동작하도록 할 수 있다. 이렇게 함으로써 코드 재사용성과 유지보수성이 향상된다.
버튼의 스타일만 변경하는 것 외에도 버튼의 렌더링 방식이나 동작 등을 다양하게 다룰 수 있으며, 이를 통해 유연하고 일반적인 코드를 작성할 수 있다.
캡슐화(Encapsulation)
데이터와 메서드를 하나의 단위로 묶어 정보 은닉을 허용하는 개념이다. 이로써 코드의 안정성과 보안성이 향상된다.
React에서 캡슐화(Encapsulation)는 컴포넌트의 상태(State)와 메서드(Method)를 하나의 단위로 묶어 정보 은닉을 적용하는 개념으로 된다. 이를 통해 컴포넌트 내부의 데이터와 동작을 외부에서 직접 접근하지 못하도록 보호하고, 코드의 안정성과 보안성을 향상시킬 수 있다.
다음은 React에서 캡슐화가 사용된 간단한 예시이다:
const Counter = () => {
// Conceal information by encapsulating the state
const [count, setCount] = useState(0);
// Change state through method
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
이 예시에서 useState
훅을 사용하여 상태를 초기화하고 변경한다. 함수형 컴포넌트 내에서 count
, increment
, decrement
와 같은 데이터와 함수는 함수 내부에 캡슐화되어 있다. 이들은 컴포넌트 외부에서 직접 접근할 수 없다.
OOP는 코드를 더 간결하고 이해하기 쉽게 작성하고, 재사용 가능한 모듈로 만들어 유지 보수를 용이하게 한다. 객체 지향의 이러한 특징은 웹 개발에서도 중요한 역할을 한다.