reset.scss, common.scss의 위치는 index.js에서 한번만
초기화 하는 세팅은 reset.scss에!
모든 컴포넌트에 적용되어야 하는 css는 common.scss에!
// common.scss
- {
box-sizing: border-box;
margin: 0;
padding: 0;
}
변수, mixin등의 경우에는 variables.scss, mixin.scss 별도 파일로 분리해서 필요한 .scss 파일에서 import 해서 사용하시면 됩니다.
관심사에 따라 모듈화하여 관리하면, 추후에 수정사항이 생길 시 한 가지의 파일만 수정하면 되기 때문에 유지보수가 용이해 집니다.
// style/variables.scss
$theme-background: #f6f4f1;
$theme-light-gray-background: #faf9f8;
$theme-blue: #007aff;
$theme-gray-strong: #888888;
$theme-gray-normal: #cccccc;
$theme-gray-light: #eeeeee;
$theme-white: #fff;
$theme-black: #1c1c1e;
$theme-red: #ff0000;
// 변수를 사용하는 scss 파일
@import "../../styles/variables.scss";
.main {
.title {
color: $theme-red;
}
}
귀찮다고 common.scss 에 프로젝트 전체적으로 적용되는 스타일 과 변수 / 믹스인을 함께 선언하면 안됩니다. 만약 그렇게 하게 되면 변수를 사용하기 위해 각 scss 파일에서 common.scss 를 import 할텐데, 이때 전체적으로 적용되는 스타일이 중복해서 적용되게 됩니다
// Bad common.scss 🙅♂️ 🙅♂️ 🙅♂️
$theme-background: #f6f4f1;
$theme-light-gray-background: #faf9f8;
$theme-blue: #007aff;
$theme-gray-strong: #888888;
$theme-gray-normal: #cccccc;
$theme-gray-light: #eeeeee;
$theme-white: #fff;
$theme-black: #1c1c1e;
$theme-red: #ff0000;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
css 그대로 두신 분들! nesting 꼭 적용해주세요. 결국 자바스크립트 모듈 시스템을 통해 모든 css 파일들이 합쳐지기 때문에, 하나의 css 내가 작성한 코드가 의도치 않게 다른 파일에 있는 요소에 영향을 줄 수 있습니다.
컴포넌트 각 html 요소들의 종속 관계와 동일하게 css 속성들도 nesting 을 합니다.
function Login() {
return (
<main className="login">
<div className="loginWrapper">
<h1 className="loginTitle">Westagram</h1>
<form className="loginForm" action="">
<input className="loginFormInput" placeholder="전화번호, 사용자 이름 또는 이메일" />
<input className="loginFormInput" placeholder="비밀번호" />
<button className="loginButton">로그인</button>
</form>
<a href="#">비밀번호를 잊으셨나요?</a>
</div>
</main>
);
}
// Bad 🙅♂️🙅♂️🙅♂️
.login {
display: flex;
justify-content: center;
align-items: center;
}
.loginWrapper {
padding: 50px;
border: 1px solid grey;
text-align: center;
}
.loginWrapper .loginTitle {
font-size: 50px;
}
.loginForm {
display: flex;
flex-direction: column;
margin: 80px 0 50px 0;
font-size: 15px;
}
.loginForm .loginFormInput {
width: 350px;
margin-bottom: 10px;
padding: 10px 0 10px 10px;
border: 1px solid gray;
border-radius: 5px;
background-color: #fafafa;
outline: none;
}
.loginForm .loginButton {
padding: 15px 0;
background-color: skyblue;
border: none;
border-radius: 5px;
color: white;
outline: none;
opacity: 0.5;
cursor: default;
}
// Good 🙆♂️🙆♂️🙆♂️
.login {
display: flex;
justify-content: center;
align-items: center;
.loginWrapper {
padding: 50px;
border: 1px solid grey;
text-align: center;
.loginTitle {
font-size: 50px;
}
.loginForm {
display: flex;
flex-direction: column;
margin: 80px 0 50px 0;
font-size: 15px;
.loginFormInput {
width: 350px;
margin-bottom: 10px;
padding: 10px 0 10px 10px;
border: 1px solid gray;
border-radius: 5px;
background-color: #fafafa;
outline: none;
}
.loginButton {
padding: 15px 0;
background-color: skyblue;
border: none;
border-radius: 5px;
color: white;
outline: none;
opacity: 0.5;
cursor: default;
}
}
}
}
가장 상위 요소의 className 은 컴포넌트 이름과 동일하게 작성해 줍니다. 해당 className 아래로 css를 네스팅 하는 것을 통해, 각 컴포넌트의 스타일링을 다른 컴포넌트 파일과 확실하게 구분지어 줄 수 있습니다.(위 컨벤션은 회사마다 / 개발팀 마다 다를 수 있습니다.)
style에 변화를 줄 때 inline으로 바로 줄 수도 있지만 우리는 css 파일을 사용하고 있기 때문에 className을 적극 활용합시다!
인라인 스타일링은 꼭 필요한 경우가 아니라면 지양해 주세요. 스타일링이 HTML 파일에서 부여되고 가장 높은 우선 순위를 가지기 때문에, 후에 CSS 유지 보수를 어렵게 할 수 있습니다.
// inline style 사용 🙅♂️
<button
onClick={handleCommentInputButton}
style={{ color: isCommentButtonActive ? "#0095f6" : "#b5e1ff" }}
>
로그인
</button>
// className 사용 🙆♂️
<button
onClick={handleCommentInputButton}
className={isCommentButtonActive ? "activated" : "deactivated"}
>
로그인
</button>
.activated {
color: #0095f6;
}
.deactivated {
color: #b5e1ff;
}
비구조화 할당(구조분해 할당)은 객체, 배열에 적용할 수 있는 ES6 문법입니다. 이 기능을 통해 긴 코드를 간결하게 쓸 수 있습니다.
// 비구조화 할당 미적용
const handlePasswordInput = event => {
setPassword(event.target.value);
...
}
// 비구조화 할당 적용
const handlePasswordInput = event => {
const { value } = event.target;
setPassword(value);
...
}
핸들러 함수에서 뿐만 아니라 함수 컴포넌트 바디 안에서도 props 객체에 비구조화 할당을 적용해 코드를 간결하게 할 수 있습니다.
// 비구조화 할당 미적용
function PasswordInput (props) {
return (
<input value={props.passwordValue} onClick={props.handlePasswordInput}/>
)
}
// 비구조화 할당 적용
function PasswordInput (props) {
const { passwordValue, handlePasswordInput } = props;
return (
<input value={passwordValue} onClick={handlePasswordInput}/>
)
}
// props 자리에서 바로 비구조화 할당을 할 수도 있습니다.
function PasswordInput ({ passwordValue, handlePasswordInput }) {
return (
<input value={passwordValue} onClick={handlePasswordInput}/>
)
}
// Before 🙅♂️
if (event.target.value.includes("@") && event.target.value.length >= 5) {
setIsIdValid(true);
} else {
setIsIdValid(false);
}
// After 🙆♂️
const { value } = event.target;
const isIdInputValid = value.includes("@") && value.length >= 5;
setIsIdValid(isIdInputValid);
// footerData.js
// named export (vs. default export)
export const INFO_LIST = [
{id: 1, content: "도움말"},
{id: 2, content: "홍보 센터"},
{id: 3, content: "API"},
{id: 4, content: "채용정보"},
{id: 5, content: "개인정보처리방침"},
{id: 6, content: "약관"},
{id: 7, content: "위치"},
{id: 8, content: "인기 계정"},
]
// Footer.js (Footer 컴포넌트)
import { INFO_LIST } from './footerData.js'
return (
...
{INFO_LIST.map(info => {
return (
<li key={info.id}>
<a href="">{info.content}</a>
</li>
)
});
map을 사용할 때는 return 되는 JSX 요소마다 유니크한 key 값이 존재해야 합니다.
key 속성은 제일 바깥에 있는 태그에 부여합니다.
Array.map(el => {
return (
<li key={el.id}>
<span>{el.content}</span>
</li>
)
})
위 상황에서 INFO_LIST 변수를 함수 컴포넌트 바디에서 선언할 경우 컴포넌트가 render 될 때마다 새로운 변수가 계속 선언되기 때문에, 값이 변하지 않는 변수는 컴포넌트 밖에서 선언합니다.
input 태그에는 name 이라는 속성이 있습니다. 말 그대로 input 태그에 이름을 지어 주는 건데요. 이 name 속성 덕분에 여러 개의 input handler를 하나로 합칠 수 있습니다.
// 비슷하게 생긴 코드가 보이면 줄이는 방법에 대해 고민해보시기 바랍니다!
// Before
const [inputValues, setInputValues] = useState({
email: "",
password: "",
});
const handleEmail = event => {
const { value } = event.target;
setInputValues({
...inputValues,
email: value,
})
}
const handlePassword = event => {
const { value } = event.target;
setInputValues({
...inputValues,
password: value,
})
}
return (
...
<input
className="emailInput"
type="text"
onChange={handleEmail}
/>
<input
className="passwordInput"
type="password"
onChange={handlePw}
/>
...
);
}
// input에 name 추가, 함수 하나로
// After
const [inputValues, setInputValues] = useState({
email: "",
password: "",
});
const handleInput = event => {
const { name, value } = event.target;
setInputValues({
...inputValues,
[name]: value,
})
}
return (
...
<input
className="emailInput"
name="email"
type="text"
onChange={handleInput}
/>
<input
className="passwordInput"
name="password"
type="password"
onChange={handleInput}
/>
...
);
}
name 속성의 값은 value처럼 event.target으로 가져올 수 있습니다. (event.target.name)
name 속성은 오직 input 태그에서만 사용할 수 있습니다. 다른 태그에서 사용하려고 시도하지 마세요!
a tag는 사용하면 새로고침 하는 것처럼 html을 새로 다 받아 오는 반면, Link 를 사용하면 컴포넌트만 바꿔줍니다. 렌더링 최적화를 위해서 Link 사용해주세요!
1.라이브러리
-React 관련 패키지
-외부 라이브러리
2.컴포넌트
-공통 컴포넌트 → 먼 컴포넌트 → 가까운 컴포넌트
3.함수, 변수 및 설정 파일
4.사진 등 미디어 파일(.png)
5.css 파일 (.scss)
state의 업데이트는 렌더링을 발생시킵니다. 만약 state로 관리하지 않아도 되는 값을 state로 관리할 경우, 일어나지 않아도 되는 렌더링이 발생하게 됩니다. 따라서 불필요한 state 값들은 제거해 주시기 바랍니다 👐
그렇다면 state로 관리하기에 부적합한 값은 무엇이 있을까요? 그 기준은 다음과 같습니다.
1.부모로부터 전달받는 props
2.시간이 지나도 변하지 않는 값 (UI 변화에 관여하지 않는 값)
3.컴포넌트 안의 다른 state 나 props 로 계산 가능한 값
하나씩 예제로 살펴보도록 하겠습니다.
1. 부모로부터 전달받는 props
function Parent() {
const [age, setAge] = useState(0);
const [secondAge, setSecondAge] = useState(0);
// 🙅♂️ totalAge는 age 와 secondAge를 통해 계산할 수 있으므로 state로 관리할 필요가 없습니다.
const [totalAge, setTotalAge] = useState(0);
useEffect(() => {
setTotalAge(age + secondAge);
}, [age, secondAge]);
const handleAgeInput = (event) => {
const { value } = event.target;
setAge(Number(value));
};
const handleSecondAgeInput = (event) => {
const { value } = event.target;
setSecondAge(Number(value));
};
return (
<div>
<input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
<Child age={age} />
</div>
);
}
function Child({ age }) {
// 🙅♂️ props로 전달받은 값을 추가적으로 state에 저장할 필요가 없습니다.
const [childAge, setChildAge] = useState(props.age);
return (
<div>
입력된 나이는: <span>{childAge}</span>
</div>
);
}
function Child({ age }) {
return (
<div>
**입력된 나이는: <span>{age}</span>**
</div>
);
}
2. 시간이 지나도 변하지 않는 값 (UI 변화에 관여하지 않는 값)
function Footer() {
// 🙅♂️ 변하지 않는 값은 state로 관리하지 않습니다.
const [footerInfo, setFooterInfo] = useState([
{ id: 1, content: "도움말" },
{ id: 2, content: "홍보 센터" },
{ id: 3, content: "API" },
{ id: 4, content: "채용정보" },
{ id: 5, content: "개인정보처리방침" },
{ id: 6, content: "약관" },
{ id: 7, content: "위치" },
{ id: 8, content: "인기 계정" },
]);
return (
<div>
{footerInfo.map((info) => {
return (
<li key={info.id}>
<a href="">{info.content}</a>
</li>
);
})}
</div>
);
}
const FOOTER_INFO = [
{ id: 1, content: "도움말" },
{ id: 2, content: "홍보 센터" },
{ id: 3, content: "API" },
{ id: 4, content: "채용정보" },
{ id: 5, content: "개인정보처리방침" },
{ id: 6, content: "약관" },
{ id: 7, content: "위치" },
{ id: 8, content: "인기 계정" },
];
function Footer() {
return (
<div>
{FOOTER_INFO.map((info) => {
return (
<li key={info.id}>
<a href="">{info.content}</a>
</li>
);
})}
</div>
);
}
3. 컴포넌트 안의 다른 state 나 props 로 계산 가능한 값
export default function TotalAge() {
const [age, setAge] = useState(0);
const [secondAge, setSecondAge] = useState(0);
// 🙅♂️ totalAge는 age 와 secondAge를 통해 계산할 수 있으므로 state로 관리할 필요가 없습니다.
const [totalAge, setTotalAge] = useState(0);
useEffect(() => {
setTotalAge(age + secondAge);
}, [age, secondAge]);
const handleAgeInput = (event) => {
const { value } = event.target;
setAge(Number(value));
};
const handleSecondAgeInput = (event) => {
const { value } = event.target;
setSecondAge(Number(value));
};
return (
<div>
<input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
<input onChange={handleSecondAgeInput} placeholder="두번째 나이를 입력해 주세요!" />
<div>
**나이의 합: <span>{totalAge}</span>**
</div>
</div>
);
}
export default function TotalAge() {
const [age, setAge] = useState(0);
const [secondAge, setSecondAge] = useState(0);
const handleAgeInput = (event) => {
const { value } = event.target;
setAge(Number(value));
};
const handleSecondAgeInput = (event) => {
const { value } = event.target;
setSecondAge(Number(value));
};
const totalAge = age + secondAge;
return (
<div>
<input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
<input onChange={handleSecondAgeInput} placeholder="두번째 나이를 입력해 주세요!" />
<div>
나이의 합: <span>{totalAge}</span>
</div>
</div>
);
}
https://stackoverflow.com/questions/43087007/react-link-vs-a-tag-and-arrow-function