aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
445 lines (374 loc) • 10.1 kB
Markdown
# Frontend 페르소나 Best Practices
## 개요
Frontend 페르소나는 사용자 경험(UX)과 인터페이스 디자인을 최우선으로 고려합니다. 성능, 접근성, 반응형 디자인에 중점을 둡니다.
## 핵심 원칙
### 1. 사용자 중심 디자인
- 직관적인 인터페이스 설계
- 일관된 디자인 시스템 구축
- 피드백과 상태 표시 명확히
### 2. 성능 최적화
```javascript
// Lazy Loading 구현
const LazyImage = ({ src, alt }) => {
const [imageSrc, setImageSrc] = useState(null);
const imageRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
setImageSrc(src);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imageRef.current) {
observer.observe(imageRef.current);
}
return () => observer.disconnect();
}, [src]);
return (
<img
ref={imageRef}
src={imageSrc || '/placeholder.svg'}
alt={alt}
loading="lazy"
/>
);
};
```
### 3. 접근성 (Accessibility)
```jsx
// ARIA 속성 활용
<button
aria-label="메뉴 열기"
aria-expanded={isMenuOpen}
aria-controls="navigation-menu"
onClick={toggleMenu}
>
<MenuIcon />
</button>
// 키보드 네비게이션 지원
const handleKeyDown = (e) => {
switch(e.key) {
case 'Enter':
case ' ':
e.preventDefault();
handleClick();
break;
case 'Escape':
handleClose();
break;
}
};
```
## 컴포넌트 설계
### 재사용 가능한 컴포넌트
```jsx
// Compound Component Pattern
const Accordion = ({ children }) => {
const [openIndex, setOpenIndex] = useState(null);
return (
<AccordionContext.Provider value={{ openIndex, setOpenIndex }}>
<div className="accordion">{children}</div>
</AccordionContext.Provider>
);
};
Accordion.Item = ({ index, children }) => {
const { openIndex, setOpenIndex } = useAccordion();
const isOpen = openIndex === index;
return (
<div className="accordion-item">
{React.Children.map(children, child =>
React.cloneElement(child, { isOpen, toggle: () => setOpenIndex(index) })
)}
</div>
);
};
Accordion.Header = ({ children, toggle }) => (
<button className="accordion-header" onClick={toggle}>
{children}
</button>
);
Accordion.Content = ({ children, isOpen }) => (
<div className={`accordion-content ${isOpen ? 'open' : ''}`}>
{children}
</div>
);
```
### 상태 관리 패턴
```javascript
// Custom Hook for Complex State
function useFormState(initialValues, validationRules) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 실시간 검증
if (touched[name]) {
const error = validationRules[name]?.(value);
setErrors(prev => ({ ...prev, [name]: error }));
}
}, [touched, validationRules]);
const handleBlur = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
const error = validationRules[name]?.(values[name]);
setErrors(prev => ({ ...prev, [name]: error }));
}, [values, validationRules]);
const validateAll = useCallback(() => {
const newErrors = {};
Object.keys(validationRules).forEach(name => {
const error = validationRules[name](values[name]);
if (error) newErrors[name] = error;
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}, [values, validationRules]);
return {
values,
errors,
touched,
handleChange,
handleBlur,
validateAll
};
}
```
## 스타일링 Best Practices
### CSS-in-JS vs CSS Modules
```javascript
// Styled Components (CSS-in-JS)
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
// CSS Modules
// Button.module.css
.button {
composes: base from './base.module.css';
background: var(--primary-color);
color: white;
padding: 0.5rem 1rem;
}
.button:hover {
opacity: 0.9;
}
// Component
import styles from './Button.module.css';
const Button = ({ children, ...props }) => (
<button className={styles.button} {...props}>
{children}
</button>
);
```
### 반응형 디자인
```css
/* Mobile First Approach */
.container {
width: 100%;
padding: 1rem;
}
/* Tablet */
@media (min-width: 768px) {
.container {
max-width: 750px;
margin: 0 auto;
}
}
/* Desktop */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 2rem;
}
}
/* Utility Classes */
.hide-mobile { display: none; }
.show-tablet { display: none; }
@media (min-width: 768px) {
.hide-mobile { display: block; }
.show-tablet { display: block; }
}
```
## 성능 최적화
### 번들 크기 최적화
```javascript
// Dynamic Imports
const HeavyComponent = lazy(() =>
import(/* webpackChunkName: "heavy" */ './HeavyComponent')
);
// Tree Shaking을 위한 Named Exports
// utils.js
export const debounce = (fn, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
};
export const throttle = (fn, limit) => {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
// Import only what you need
import { debounce } from './utils';
```
### 렌더링 최적화
```jsx
// React.memo로 불필요한 리렌더링 방지
const ExpensiveComponent = React.memo(({ data }) => {
return <ComplexVisualization data={data} />;
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.data.id === nextProps.data.id;
});
// useMemo for expensive calculations
const ProcessedData = ({ rawData }) => {
const processed = useMemo(() => {
return rawData
.filter(item => item.active)
.map(item => ({
...item,
computed: expensiveComputation(item)
}))
.sort((a, b) => b.computed - a.computed);
}, [rawData]);
return <DataTable data={processed} />;
};
```
## 테스팅 전략
### 컴포넌트 테스트
```javascript
// React Testing Library
describe('Button Component', () => {
it('should render with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('should handle click events', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should be keyboard accessible', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
const button = screen.getByRole('button');
button.focus();
await userEvent.keyboard('{Enter}');
expect(handleClick).toHaveBeenCalled();
});
});
```
### E2E 테스트
```javascript
// Cypress
describe('User Flow', () => {
it('should complete checkout process', () => {
cy.visit('/products');
// 제품 선택
cy.findByText('Add to Cart').click();
// 장바구니 확인
cy.findByLabelText('Cart').click();
cy.findByText('Proceed to Checkout').click();
// 결제 정보 입력
cy.findByLabelText('Email').type('user@example.com');
cy.findByLabelText('Card Number').type('4242424242424242');
// 주문 완료
cy.findByText('Place Order').click();
cy.findByText('Order Confirmed').should('be.visible');
});
});
```
## 보안 고려사항
### XSS 방지
```jsx
// 위험한 HTML 렌더링 피하기
// 나쁜 예
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// 좋은 예
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userContent)
}} />
// 또는 텍스트로만 렌더링
<div>{userContent}</div>
```
### 민감한 정보 보호
```javascript
// 환경 변수 사용
const API_KEY = process.env.REACT_APP_API_KEY;
// 민감한 정보 로깅 방지
const sanitizeUser = (user) => {
const { password, ssn, ...safeUser } = user;
return safeUser;
};
console.log('User data:', sanitizeUser(userData));
```
## 개발 도구
### 필수 도구
- **번들러**: Vite, Webpack
- **상태관리**: Redux Toolkit, Zustand, Jotai
- **스타일링**: Tailwind CSS, Emotion, Styled Components
- **테스팅**: Jest, React Testing Library, Cypress
- **린팅**: ESLint, Prettier
### 성능 모니터링
```javascript
// Web Vitals 측정
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
// Google Analytics로 전송
gtag('event', metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
```
## 체크리스트
### 개발 전
- [ ] 디자인 시스템 확인
- [ ] 반응형 브레이크포인트 정의
- [ ] 접근성 요구사항 검토
- [ ] 성능 목표 설정
### 개발 중
- [ ] 컴포넌트 재사용성 검토
- [ ] 접근성 테스트 (스크린 리더, 키보드)
- [ ] 크로스 브라우저 테스트
- [ ] 성능 프로파일링
### 배포 전
- [ ] 번들 크기 최적화
- [ ] 이미지 최적화
- [ ] SEO 메타 태그
- [ ] 에러 경계 설정
- [ ] 분석 도구 통합