사용자가 input에 값을 입력한 후 버튼을 누르면 해당 input이 하단의 리스트에 업데이트되는 기능 구현을 생각해보자. 만약 사용자가 input에 아무것도 입력하지 않은 경우 사용자에게 입력을 요구하는 경고 문구를 띄우려고 한다. 이러한 상황에서 우리는 동적 스타일링의 중요성을 알 수 있다.
조건을 state로 정의해두고, 각 state에 해당하는 CSS를 적용시키는 방법에는 여러가지가 있다.
// CourseInput.js
const CourseInput = props => {
// input 값 state
const [enteredValue, setEnteredValue] = useState('');
// 입력된 값 유효한 값인지 확인하는 state
const [isValid, setIsValid] = useState(true);
... 생략
const formSubmitHandler = event => {
event.preventDefault();
// 입력값 유효한지 확인
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
};
... 생략
return (
<form onSubmit={formSubmitHandler}>
<div className="form-control">
// !----isValid state에 따라 다르게 노출할 부분----!
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</div>
<Button type="submit">Add Goal</Button>
</form>
)
}
<label style={{color: !isValid? 'red' : 'black'}}>Course Goal</label>
<input style={{borderColor: !isValid? 'red' : 'black', background: !isValid ? 'salmon' : 'transparent'}} type="text" onChange={goalInputChangeHandler} />
inline으로 작성되었기 때문에 적용된 CSS중 가장 높은 우선순위를 가지게 된다. 따라서 다른 컴포넌트에도 오버라이드 되어서 스타일이 제대로 안 먹히는 문제가 발생한다.
조건에 따라 보여질 CSS가 정의된 다른 css class를 적용하는 방법이다. 잘못된 input을 받았을 때 보여줄 CSS를 미리 작성해두고, 조건에 따라 (isValid의 state값에 따라) 해당 class들이 동적으로 추가될 수 있도록 한다.
1️⃣ 조건에 따라 보여질 CSS class 만들기
/* CourseInput.css */
.form-control.invalid input{
border-color: red;
background: #ffd7d7;
}
.form-control.invalid label{
color: red;
}
2️⃣ 조건에 따라 동적으로 class명 전달
<div className = { \`className ${value}` }> </div>
🧐 잠깐! 이 문법이 뭐더라?
A) template literal
``
``
안에 작성된 값은 모두 문자열로 취급된다. 그렇다면''
과의 차이점이 무엇일까. 그것은 바로${}
안에 동적인 value값, JS 구문을 추가할 수 있다는 것이다. 즉 문자열 안에서 변수에 저장된 값을 불러올 수 있는 JS의 기본적인 syntax이다~
isValid의 state에 따라 .invalid CSS class
를 적용하려면
<div className={ \` form-control ${ !isValid ? 'invalid' : ''} `}>
와 같이 사용하면 조건에 따라 동적으로 미리 만들어 둔 css class를 적용시킬 수 있다.
하나의 컴포넌트에 해당 컴포넌트의 style.css import 하여도 해당 컴포넌트에만 스타일이 적용되는게 아닌, 전체 컴포넌트에 적용되어서 같은 class 명을 가지는 선택자가 있을 경우 스타일이 적용되게 된다. css 파일을 Import해서 쓰는 기존 방식은 모두 이러한 문제가 발생한다고 생각하면 된다. 이게 꼭 문제가 되는 건 아니다. class명을 신경써서 절대 안 겹치게 작성하면 문제될 건 없지만, 변수명도 정하기 어려운데 class명을 안 겹치게 만들 수 있을까...? 사실상 불가능하고 불편한 방법이다.
styled component package를 사용하면 내가 선택한 component에 국한된 style을 적용할 수 있다! 전체 파일에 css가 overriding 되는 것을 전혀 걱정하지 않아도 된다. Styled Component는 전달한 style css값을 가지는 새로운 component를 반환하는 기능을 가진다.
1️⃣ package 설치하기
npm install --save styled-components
2️⃣ component 파일에 (js / jsx파일) import 하기
import styled from 'styled-components';
3️⃣ styled-component 구문 사용하기
const Button = styled.button``;
const Button = styled.button``
styled : styled-components에서 import해온 object
button : styled object의 method
style-component는 모든 html element (h1, div, p, ... )에 대한 메소드를 가지고있다.`` : 메소드 호출
🧐 잠깐! 이 문법이 뭐더라?
A) Tagged Tamplet Literal
기존에 ()로 호출하던 방식과 다르게 ``를 사용해서 함수를 호출한다. type에 상관없이 Function, Number, Array, Object 등을 전달하고 이를 실행할 수 있게 된다. 또한, 줄구분 없이 작성 가능하다.
이렇게 ``에 값을 전달하면, 해당 메소드는 전달한 css style값들을 가지는 새로운 button component를 반환한다.
1️⃣ 일반 html element 선택자의 경우 (ex. button, div ..)
기존 css파일을 생각해보면,선택자 : {height : 10pt}
과 같이 작성했을 것이다. 하지만 styled-component에서는 선택자를 작성해줄 필요가 없다. 그냥height: 10pt;
와같이 작성하면 된다.2️⃣ pseudo 선택자, 중첩 선택자의 경우 &
&는 내가 style에서 불러온 method를 의미한다. method는 일반 html element들을 모두 포함하고 있으므로 결국 & 는 사용하려는 html 요소를 대체한다고 생각하면 된다!- 가상선택자
기존.button:focus
styled-component&:focus
야! 이 버튼에 focus라는 가상선택자가 있으면 적용해줘~ 뭐 대충 이런,,- 중첩선택자 :
기존.form input {}
styled-component& input {}
+) 🧐 중첩 선택자에 pseudo 선택자를 선택하고 싶으면..?
<form>
<input />
</form>
의 구조 속 <input />
에 focus
를 적용하고 싶은경우?
& input:form {}
+) 🧐 특정 class를 가진 태그의 내부 태그에 css를 부여하고 싶으면?
<form class="invalid">
<input />
</form>
의 <input />
에 효과를 주고 싶으면?
&.invalid input {}
즉, 두 번 이상 &를 사용하진 않는다! &는 그냥 선택자를 대신한다고만 생각하면 된다!
리액트가 변환할 때 자동으로 클래스 이름을 지정해주게 되는데 절대 겹칠일 없는 알 수 없는 문자열로 이루어지기 때문이다.. 생각만큼 거장한 방법이 아닌 단순한 방법이라 당황스럽다,,ㅎㅎ
B component가 A component 파일 내부에서만 사용된다는 것이 확실하다면, A component 파일에 A와 B component 총 2개의 component를 작성하는 것이 가능하다.
A component가
<form>
<div>
<label> </label>
<input />
</div>
</form>
을 return하는 component라고 가정해보자. 이때, <div> </div>
에styled-component를 지정하여 사용하려 할 때 위와같이 한 파일에 다수의 component를 정의하는 상황이 발생할 수 있다.
// CourseInput.js
// Component B) styled-component를 적용한 div
// 이 파일 안에서만 사용된다
const FormControl = styled.div`
...css 생략
`;
// Component A) export하려는 진짜 component
const CourseInput = props => {
...생략
return (
<form>
// 기존의 div가 styled-componet로 대체됨!!
<FormControl>
<label> </label>
<input />
</FormControl>
</form>
)
}
export default CourseInput;
styled-component에 의해 반환되는 새로운 component는 기존의 component와 같이 사용할 때 props를 전달할 수 있다. 즉 class이름을 props의 형태로 전달하는 것이다.
<FormControl className={'class이름'} >
이제는 쉽다! inline으로 style 적용할 때를 떠올려서 그대로 Inline에서 삼항연산자를 사용해서 조건에 따라 class를 전달한다.
<FormControl className={!isValid && 'invalid'}>
- isValid값에 따라 className invalid를 전달하든 말든 한다.
- isValid값이 전달되면 정의된 styled component 속 css에 따라 동작한다.
//2. css에 적용
const FormControl = styled.div`
...생략
// invalid가 전달될 경우 이 css를 쓴다!!
&.invalid input{
border-color: red;
background: #ffd7d7;
}
&.invalid label{
color: red;
}
`
// 1. 먼저 className 추가하는 삼항연산자 작성
const CourseInput = props => {
...생략
return (
<form onSubmit={formSubmitHandler}>
// 유사 inline..
<FormControl className={!isValid && 'invalid'}>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</FormControl>
<Button type="submit">Add Goal</Button>
</form>
)
}
inline에 작성해서 조건부로 class를 넘기는게 아닌, 일단 props로 메소드에 넘겨서 백틱 안에서 해당 props들 사용하는 방법이다. 이게 훨씬 쉽게 동적으로 스타일을 바꾸는 방법이다.
예시와 함께 알아보자!
isValid값에 따라 isValid가 false면 (valid 하다는 뜻)input의 border를 #ccc로, isValid가 true면 (valid하지 않다는 뜻)input의 border를 red로 바꾸고자 한다. isValid는 input에 입력된 값이 유효한지에 따라 바뀌는 state값이다!
step 1. 내가 만든 styled-component에 prop으로 사용하려는 class값 전달하기
const CourseInput = props => {
...생략
return (
<form onSubmit={formSubmitHandler}>
// 여기 props로 전달!!
<FormControl invalid={!isValid}>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</FormControl>
<Button type="submit">Add Goal</Button>
</form>
)
}
Step 2. styled-component 구문 안에서 (백틱 안에서) 해당 prop값 사용하기
우리는 백틱 안에서 작업할 것이기 때문에 &{}
를 사용할 수 있음을 기억할 것이다. (Template literal)
기존 input border 색이 정의된 부분으로 가서, &{}
안에 화살표 함수를 사용하여 props를 매개변수로 전달받고, 해당 props를 활용해서 바꾸려는 정확한 css text 값을 입력한다.
이때 이 화살표 함수는 styled-component package에 의해서 자동으로 호출되며 매개변수로 사용하는 props
는 전달된 모든 props값을 가진다. 이 예시에서는 invalid
값 하나만 포함하고 있다.
const FormControl = styled.div`
...생략
& label {
font-weight: bold;
display: block;
margin-bottom: 0.5rem;
/*props 전달받아서 적용!*/
color : ${props => (props.invalid? 'red' : 'black')};
}
& input {
display: block;
width: 100%;
/*props 전달받아서 적용!*/
border: 1px solid ${props => props.invalid ? 'red' : '#ccc'};
background : ${props => props.invalid ? '#ffd7d7' : '#transparent'};
font: inherit;
line-height: 1.5rem;
padding: 0 0.25rem;
}
/* 1번 방법처럼 직접 class명에 적용하는게 아님!
&.invalid input{
border-color: red;
background: #ffd7d7;
}
&.invalid label{
color: red;
*/
`;
🧐 CSS Module이란?
styled component는 js파일 안에 css코드까지 같이 넣는 방식이라면, css module은 기존처럼 css파일과 js파일을 분리하여 가지고 있는 방법이다.
그렇다면 기존의 css파일과 다른점은 무엇일까? CSS Module의 핵심은 css.module파일을 import하는 js파일(component)에만 국한되어 style이 적용된다는 것이다. 즉 overriding되어서 전역에 css파일이 적용되던 기존의 일반 방법과 다르게 css의 범위를 지정할 수 있다는 큰 특징이 있다!
1️⃣ css파일의 이름을
Button.css
=>Button.module.css
처럼 중간에 module을 추가해야한다.이것은 기본적으로 css모듈이 작동하도록 코드를 변환하라고 컴파일 프로세스에게 보내는 신호이다. 컴파일된 후 개발자도구로 class명을 확인해보면
Component이름_지정한class이름_랜덤해시값
으로 변환되어 적용되는 걸 확인해볼 수 있다
2️⃣ js파일에 import할 때
import styles from './Button.css'
와 같은 방식으로 import해야한다. 이때styles
는 객체로 해당 객체에 css파일의 코드들을 담아서 import해오겠다는 의미이므로classes나 다른 명칭
등으로 바꿔도 된다.
3️⃣ className이나 idName을 지정할 때, import해온 객체에서 값을 받아온다.
<button className="button">
에서<button className={styles.button} >
과 같이 바뀌게 된다.
이는 곧 prop처럼 style 속성을 전달 받아서 적용한다는 의미처럼 해석해볼 수 있다.🚨 주의) 만약 class이름이
<button className={styles.form-control} >
와 같이-
가 들어간다면, 허용된 문법이 아니기 때문에 에러가 날 것이다. 이 경우에는 class이름을formControl
과 같이 카멜케이스로 변경하거나<button className={styles.['form-control']} >
과 같이['class-name']
의 문법을 사용하여 에러를 해결할 수 있다.
백틱구문을 활용한다!
여러개의 className의 경우 ${className1} ${className2}
을 사용해서 안에 적용하려는 class 이름을 작성하면 된다.
동적으로 className을 지정하는 경우 알고 있듯이 ${}
안에 삼항연산자를 통해 조건에 맞게 동적으로 className을 적용할 수 있다~
<button className={ ` ${ styles['form-control'] } ${ !isValid && styles.invalid }` }>
좋은 정보 감사합니다