기존의 UI컴포넌트인 Tags 컴포넌트를 리팩토링하는 Task를 진행하였다.
아래 기존코드를 보면 renderTag prop이 Tag컴포넌트의 children을 render하도록 작성이 되어있는데,
renderTag라 함은 tag 자체를 render하는 것이 더 합리적이라고 생각하여 해당 구조를 변경하게 되었다.
import { isObject } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import Link from 'components/Link';
import type { LinkProps } from 'components/Link';
import Stack from 'components/Stack';
import type { StackProps } from 'components/Stack';
import Tag from 'components/Tag';
import type { TagProps } from 'components/Tag';
interface ITag extends Pick<LinkProps, 'openInNew'>, Pick<TagProps, 'disabled'> {
to?: LinkProps['to'];
label?: string;
[x: string]: any;
}
interface TagsProps extends Pick<TagProps,
'size'
| 'solid'
| 'rounded'
> {
tags: Array<ITag>;
className?: string;
disabled?: boolean;
getId?: (tag: ITag) => string | number;
onClick?: (tag: ITag) => void;
onRemove?: (tag: ITag) => void;
renderTag?: (tag: ITag) => React.ReactNode;
renderTagContainer?: (params: {
tag: ITag,
} & Pick<TagsProps,
'onClick'
| 'onRemove'
| 'disabled'
| 'renderTag'
>) => React.ReactNode;
spacing?: StackProps['spacing'];
}
const defaultProps: Partial<TagsProps> = {
className: undefined,
disabled: false,
getId: undefined,
onClick: undefined,
onRemove: undefined,
renderTag: (obj) => obj.label,
renderTagContainer: undefined,
spacing: 's',
};
// ====
const Root = styled.div``;
const Tags = ({
tags,
size,
solid,
spacing,
rounded,
disabled,
onClick,
onRemove,
renderTag,
renderTagContainer,
getId,
className,
}: TagsProps) => (
<Root className={className}>
<Stack spacing={spacing}>
{
tags.map((tag, i) => {
const { to = '', openInNew = false } = isObject(tag) ? tag : {};
const id = getId ? getId(tag) : String(i);
return (
<Stack.Item key={id}>
{
renderTagContainer
? renderTagContainer({
tag,
onClick,
onRemove,
disabled,
renderTag,
})
: (
to
? (
<Link to={to} openInNew={openInNew}>
<Tag
size={size}
solid={solid}
rounded={rounded}
disabled={tag.disabled || disabled}
onClick={onClick && (() => onClick(tag))}
onRemove={onRemove && (() => onRemove(tag))}
>
{renderTag(tag)}
</Tag>
</Link>
)
: (
<Tag
size={size}
solid={solid}
rounded={rounded}
disabled={tag.disabled || disabled}
onClick={onClick && (() => onClick(tag))}
onRemove={onRemove && (() => onRemove(tag))}
>
{renderTag(tag)}
</Tag>
)
)
}
</Stack.Item>
);
})
}
</Stack>
</Root>
);
Tags.defaultProps = defaultProps;
export default Tags;
export type { TagsProps };
변경된 구조
import React from 'react';
import Stack from 'components/Stack';
import type { StackProps } from 'components/Stack';
import Tag from 'components/Tag';
import type { TagProps } from 'components/Tag';
interface TagsProps
extends Pick<TagProps,
'size'
| 'solid'
| 'rounded'
| 'disabled'> {
tags: Array<TagProps>,
className?: string,
getId?: (tag: TagProps) => string,
onTagClick?: () => void,
onTagRemove?: () => void,
renderTag?: (
tag: TagProps,
renderTagContent: (tag: TagProps) => React.ReactNode,
tagProps: TagProps,
) => React.ReactNode,
renderTagContent?: (tag: TagProps) => React.ReactNode,
spacing?: StackProps['spacing'];
}
const defaultProps: Partial<TagsProps> = {
tags: [],
className: '',
disabled: undefined,
getId: undefined,
onTagClick: undefined,
onTagRemove: undefined,
renderTag: (
tag,
renderTagContent,
{
id,
disabled,
onClick,
onRemove,
size,
solid,
rounded,
to,
...linkProps
},
) => (
<Tag
{...linkProps}
key={id}
disabled={disabled}
onClick={onClick}
onRemove={onRemove}
size={size}
solid={solid}
rounded={rounded}
to={to}
>
{renderTagContent(tag)}
</Tag>
),
renderTagContent: (tag: TagProps) => tag.label,
spacing: 's',
};
// ====
const Tags = ({
tags,
className,
disabled,
getId,
onTagClick,
onTagRemove,
renderTag,
renderTagContent,
rounded,
size,
solid,
spacing,
}: TagsProps) => (
<Stack
className={className}
spacing={spacing}
>
{
tags.map((tag, i) => {
const id = getId ? getId(tag) : String(i);
return (
renderTag(
tag,
renderTagContent,
{
...tag,
id,
rounded,
size,
solid,
disabled: disabled || tag.disabled,
onClick: onTagClick,
onRemove: onTagRemove,
},
)
);
})
}
</Stack>
);
Tags.defaultProps = defaultProps;
export default Tags;
export type { TagsProps };
이렇게 구현하게되면 무엇보다 prop의 name과 그 역할이 더 명확하고 잘 구분이 되게 된다.
renderTag는 Tag 그 자체를 return하며, renderTagContent는 Tag 컴포넌트 하에서 children을 render하게 된다.
위 코드와 아래 코드는 같은 목적을 위해 사용될 컴포넌트이지만, 명확하게 Prop의 역할을 구분짓고 협업시 컴포넌트의 코드에 대한 가독성을 높여줄 것으로 기대한다.