const ActiveUsersList = () => {
const [users, setUsers] = useState([])
useEffect(() => {
const loadUsers = async () => {
const response = await fetch('/some-api')
const data = await response.json()
setUsers(data)
}
loadUsers()
},[])
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return (
<ul>
{users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => <UserItem key={user.id} user={user} />)}
</ul>
)
}
// Users 데이터를 패칭하는 기능을 커스텀 훅으로 분리
const useUsers = () => {
const [users, setUsers] = useState([])
useEffect(() => {
const loadUsers = async () => {
const response = await fetch('/some-api')
const data = await response.json()
setUsers(data)
}
loadUsers()
},[])
return { users }
}
// 필터링하는 부분을 유틸 함수로 분리
const getOnlyActive = (users) = {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo)
}
// 렌더링하는 부분만 컴포넌트에 남기기
const ActiveUserList = () => {
const { users } = useUsers();
return (
<ul>
{getOnlyActive(users).map(user => <UserItem key={user.id} user={user}>)}
</ul>
)
}
// 사용자의 데이터를 가져오고 필터링하는 과정도 커스텀 훅으로 만들기
const useActiveUsers = () => {
const { users } = useUsers()
const activeUsers = useMemo(() => {
return getOnlyActive(users)
}, [users])
return {activeUsers}
}
// 컴포넌트에서는 반환된 데이터로 UI만 그리기
const ActiveUserList = () => {
const { activeUsers } = useActiveUsers();
return (
<ul>
{activeUsers.map(user => <UserItem key={user.id} user={user}>)}
</ul>
)
}
const Header = () => {
const { pathname } = useRouter();
return (
<header>
<Logo />
<Actions>
{pathname === '/dashboard' && (
<Link to="/events/new">Create event</Link>
)}
{pathname === '/' && <Link to="/dashboard">Go to dashboard</Link>}
<Actions>
<header>
)
}
const Header = ({children}) => (
<header>
<Logo />
<Actions>{children}</Actions>
<header>
)
const HomePage = () => (
<>
<Header>
<Link to="/dashboard">Go to dashboard</Link>
</Header>
<OtherHomeStuff />
</>
)
const DashboardPage = () => (
<>
<Header>
<Link to="/events/new">Create event</Link>
</Header>
<OtherDashboardStuff />
</>
)
const CardItem = ({ imageUrl, tagNumber, name }: Props) => {
...
return (
<div>
<img src={imageUrl} />
<div>
<span>{tagNumber}</span>
<span>{name}</span>
</div>
</div>
)
}
const CardThumbnail = ({ url, size, rounded, className }: Props) => ...
const CardBody = ({ className, align, children }: Props) => ...
const CardText = ({className, lineCnt, children}: Props) => ...
const RoundCardItem = (...) => {
return (
<div>
<CardThumbnail url={imageUrl} rounded />
<CardBody align="center">
<CardText>{tagNumber}</CardText>
</CardBody>
<div>
)
}

interface Video {
title: string;
duration: number;
coverUrl: string;
}
interface Props {
video: Video;
}
const Thumbnail = ({ video }: Props) => {
return <img src={video.coverUrl} />;
}
type LiveStream = {
name: string;
previewUrl: string;
}
type Props = {
items: Array<Video | LiveStream>
}
const VideoList = ({ items }) => {
return (
<ul>
{items.map(item => {
if('coverUrl' in item) {
return <Thumbnail video={item}>
} else {
// video 객체를 전달함으로 LiveStream 썸네일을 사용할 수 있다.
}
})}
</ul>
)
}
type Props = {
coverUrl: string
}
const Thumbnail = ({coverUrl}: Props) => {
return <img src={coverUrl} />
}
type Props = {
items: Array<Video | LiveStream>
}
const VideoList = ({ items }) => {
return (
<ul>
{items.map(item => {
if('coverUrl' in item) {
return <Thumbnail video={item.coverUrl}>
} else {
return <Thumbnail video={item.previewUrl}>
}
})}
</ul>
)
}

const ActiveUsersList = () => {
const [users, setUsers] = useState([])
useEffect(() => {
const loadUsers = async () => {
const response = await fetch('/some-api')
const data = await response.json()
setUsers(data)
}
loadUsers()
},[])
...
}
const useUsers = () => {
const [users, setUsers] = useState([])
useEffect(() => {
const loadUsers = async () => {
const response = await fetch('/some-api')
const data = await response.json()
setUsers(data)
}
loadUsers()
},[])
return { users }
}
SOLID 원칙을 기반으로 리액트 컴포넌트를 설계하는 방법에 대해서 다뤄봤는데요. 굉장히 실용적인 내용이었습니다. 저는 SOLID 원칙의 정확한 의미를 모르고 있었지만 제가 기존에 작성하던 코드가 해당 원칙을 고려하여 작성한 코드와 유사한 것이 신기하네요. 의문이 드는 부분이 하나 있다면 Header 컴포넌트를 설계하는 부분에서 저렇게 Header 컴포넌트를 설계를 한다면 페이지마다 Header 컴포넌트를 선언해줘야 하는 데 맞는 방식일까요?