재활용(Mixins)
<div class="container">
<div class="item">
Mixin
</div>
</div>
.container {
background-color: orange;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.container .item {
background-color: blue;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
가운데 정렬하는 부분이 반복적으로 사용되고 있다. (각 클래스 하단 3개의 코드줄)
이렇게 반복되는 코드 작성을 ‘mixin’을 통해 줄여준다.
-
mixin 사용하기
@mixin center {
display: flex;
align-items: center;
justify-content: center;
}
.container {
@include center;
.item {
@include center;
}
}
.box {
@include center;
}
- ‘@mixin’에 재활용할 코드를 넣어준다. mixin 키워드 뒤에 설정할 이름또한 정해준다.
- ‘@include’를 통해서 재활용한 코드를 사용한다.
- 재활용이 가능하기 때문에 새로운 클래스인 box에도 사용이 가능하다.
-
세부 값 변경해서 사용하기
container 클래스의 width 값을 변경하고 싶을때, mixin 사용을 버리고 따로 작성해야 할까? No!
@mixin box {
width: 100px;
height: 100px;
background-color: tomato;
}
.container {
@include box;
.item {
@include box;
}
}
.box {
@include box;
}
.container {
width: 100px;
height: 100px;
background-color: tomato;
}
.container .item {
width: 100px;
height: 100px;
background-color: tomato;
}
.box {
width: 100px;
height: 100px;
background-color: tomato;
}
mixin에서 제공하는 인수를 통해 원하는 세부 값을 변경해서 사용할 수 있다.
중첩된 속성
변수
.container {
position: fixed;
top: 100px;
.item {
width: 100px;
height: 100px;
transform: translateX(100px);
}
}
.container {
position: fixed;
top: 100px;
}
.container .item {
width: 100px;
height: 100px;
transform: translateX(100px);
}
- 100px이 동일하게 사용되고 있다.
- 이때, 100px이 모두 동일하게 적용되어야 할 때, 한 쪽에서 100px을 200px로 변경한다면, 다른 부분의 100px들도 하나하나 변경해줘야 한다.
-
이런 번거로움을 줄이기 위해서 변수를 사용한다.
동일한 수치를 사용해야 하는 부분에 변수를 넣어줌으로, 변수를 지정한 부분의 수치만 변경하면 모든 부분의 수치가 한 번에 변경이 된다.
$size: 100px;
.container {
position: fixed;
top: $size;
.item {
width: $size;
height: $size;
transform: translateX($size);
}
}
-
SCSS에서의 변수
자주 사용하는 수치라던가 프로젝트의 메인 색상 등을 변수로 만들어서 반복적으로 사용할 때 유용하게 쓸수 있다.
-
변수 사용 주의사항
변수는 선언된 범위내에서 유효범위를 가진다.
변수를 container 윗 부분에서 선언하게 되면(해당 파일 최상단) 변수가 선언된 파일 어디에서나 사용 가능한 전역변수가 된다.
변수를 container 내부에 postion 위에 작성하게 되면, 해당 변수는 선언된 중괄호 안에서만 사용이 가능한 변수가 된다.
-
중복된 변수
container 내부에는 size는 100px, item 내부의 size는 200px로 변수를 선언했다.
.container {
$size: 100px;
position: fixed;
top: $size;
.item {
$size: 200px;
width: $size;
height: $size;
transform: translateX($size);
}
}
-
css 변환 결과
.container {
position: fixed;
top: 100px;
}
.container .item {
width: 200px;
height: 200px;
transform: translateX(100px);
}
container에는 100px, item에는 200px이 들어간 것을 볼 수 있다.
scss의 변수도 재할당이 가능하다는 것을 알수 있다.
-
재할당 주의 사항
자바스크립트처럼 해당 변수가 재할당 되면, 재할당된 변수 아래의 코드는 재할당 된 값으로 들어간다.
item에서 size를 200px로 재할당 했는데, item 하단에 size를 사용하면 container의 100px이 아닌, 200px로 수치가 입력된다. 해당 left는 container에 포함되는 것임에도 불구하고!
즉, 중괄호 사이의 범위로 적용되는 것이 아니고 단순히 순서(?)로 적용되는 것이기 때문에 주의가 필요하다.
.container {
$size: 100px;
position: fixed;
top: $size;
.item {
$size: 200px;
width: $size;
height: $size;
transform: translateX($size);
}
left: $size;
}
.container {
position: fixed;
top: 100px;
left: 200px
}
.container .item {
width: 200px;
height: 200px;
transform: translateX(100px);
}
산술 연산
div {
width: 20px + 20px;
height: 40px - 10px;
font-size: 10px * 2;
margin: 30px / 2;
padding: 20px & 7;
}
div {
width: 40px;
height: 30px;
font-size: 20px;
margin: 30px/2; /* 연산이 되지 않은 것*/
padding: 6px;
}
-
나누기 기호”/”사용 불가한 이유
font 단축 속성으로 인해 해당 기호를 나누기로 사용이 불가능하다. font 단축 속성은 위쪽의 내용을 단축해서 사용할 수 있게 한다.
슬래쉬 ”/”를 통해서 앞은 size, 뒤는 line-height를 구분해서 나타낸다.
추가로 font 단축속성은 뒤에 font-famaily까지 작성을 해줘야 한다. 그닥 유용하지는 않다.
span {
font-size: 10px;
line-height: 10px;
font: 10px / 10px serif;
}
해당 font 단축속성은 css의 기능으로, scss에서도 슬래쉬 기호는 font 단축 속성에서 나누는 용도로 사용하기 때문에! 연산 나누기로 사용이 불가능 하게 된다.
- 나누기 기호 사용 방법
-
같은 단위끼리만 연산 가능
아래와 같이 서로 다른 단위끼리는 연산이 불가능하다.
div {
width: 100% - 200px;
}
-
calc() 함수를 통해 다른 단위 연산하기
단위가 다른 값을 연산할때 calc 함수를 사용하면 연산이 가능하다.
div {
width: calc(100% - 200px);
}
물론, css로 변환했을 때에도 어떤 정리된 값으로 출력이 되는 것은 아니다.
해당 내용 자체가 화면에 출력이 가능하게 되는 것이다.
div {
width: calc(100% - 200px);
}
해당 코드가 화면에 구현이 된다면, 꽉찬 너비에 200px이 빠진 형태가 된다.
주석
.container {
h1 {
color: red;
/* background-color: blue; */
// font-size: 60px;
}
}
-
/* */
해당 주석은 scss에서 css로 변환(컴파일)했을 때, 주석처리가 남아있다.
⇒ 코드가 남아 있다.
-
//
해당 주석은 scss에서 css로 변환했을 때, 코드가 나오지 않는 (존재하지 않는)다.
⇒ 코드가 남아 있지 않다.
중첩 with SassMeister
<div class="container">
<ul>
<li>
<div class="name">Soha</div>
<div class="age">39</div>
</li>
</ul>
</div>
- css에서는 하나 하나에 모두 부모요소를 작성해 주었다. 상위 선택자를 반복적으로 작성하여 코드가 길어지고 복잡해진다.
-
scss에서는 중괄호를 통해 상위 요소안에서 계속해서 중첩하여 작성해주는 방식을 사용한다.
.container {
ul {
li {
font-size:60px;
.name {
color:royalblue;
}
.age {
color:orange;
}
}
}
}
-
scss로 작성한 코드를 컴파일하여 css로 변환했을 때
.container ul li {
font-size: 60px;
}
.container ul li .name {
color: royalblue;
}
.container ul li .age {
color: orange;
}
-
자식 선택자로 css 변환하기
중첩을 이용해서 작성하면 기본적으로 후손 선택자(띄어쓰기 O)로 적용이 된다.cc
그렇다면, 자식 선택자(띄어쓰기 X)로 하려면 어떻게 해야 할까?
자식 선택자로 하고 싶은 태그 앞에 꺾쇠 괄호를 닫아주면 자식 선택자로 변환이 된다.
.container {
> ul {
li {
font-size:60px;
.name {
color:royalblue;
}
.age {
color:orange;
}
}
}
}
.container > ul li {
font-size: 60px;
}
.container > ul li .name {
color: royalblue;
}
.container > ul li .age {
color: orange;
}
Sass Meister
codepen에서 작성할 경우 컴파일 버튼을 눌러야 css로 변환된 형태를 확인 할 수 있다. 이렇게 하면 약간의 번거로움이 발생한다.
Sass Meister 웹 사이트를 통해서 왼쪽은 scss 내용, 오른쪽에는 css 내용을 한 눈에 확인 할 수 있다.
상위(부모) 선택자 참조
상위 선택자 참조는 ‘&’기호를 사용한다.
‘&’ 기호가 붙어있는 것은 해당 기호가 있는 중첩된 선택자 영역에 (여기서는 btn) 해당하는 선택자가 들어와서 치환되었다.
해당 코드에서 ‘&’ 기호가 있는 클래스는 active, 해당 클래스를 감싸는 영역은 btn 클래스이다. 정리하면, active 클래스가 일치 선택자를 통해서 btn 클래스와 붙어져 있다.
반대로 말하면, 해당 기호가 상위 선택자를 참조하고 있다고 말할 수 있다.
그냥 단순하게 쉽게 말하면 ‘&’기호가 붙어져 있는 태그와 바로 상위 태그는 css로 변환했을 때 붙어져서 변환된다!!
.btn {
position: absolute;
&.active {
color: red;
}
}
.list {
li {
&:last-child {
margin-right: 0;
}
}
}
.btn {
position: absolute;
}
.btn.active {
color: red;
}
.list li:last-child {
margin-right: 0;
}
-
활용하기
.fs {
&-small {font-size: 12px;}
&-medium {font-size: 18px;}
&-large {font-size: 25px;}
}
.fs-small {
font-size: 12px;
}
.fs-medium {
font-size: 18px;
}
.fs-large {
font-size: 25px;
}
개요
sass의 문법은 두개가 있다고 보면 된다. scss와 sass!
sass가 기존의 css와 호환이 잘 안되는 문제로 인해 scss가 생기게 되었다.
scss는 기존의 css와 호환이 잘 되기 때문에 sass보다 더 유용하게 사용 할 수 있다.
-
sass
// sass
$font-stack: Helvetica, sans-serif
$primary-color: #333
body
font: 100% $font-stack
color: $primary-color
중괄호도 없고 세미콜론도 존재하지 않는다.
들여쓰기 형태로 구분해서 해석한다.
따라서 작성된 코드를 보면 scss보다 기호가 적어서 깔끔해 보인다.
하지만, 내용이 복잡해지면 어디에서 시작하고 끝나는지 판단이 되지 않을 수 있다.
-
scss
// scss
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
기존의 css 문법과 동일하다.
css의 문제를 개선해서 나온 것이기 때문에 sass보다 사용하기 더 유용하기에 sass보다 scss를 사용하기를 권장한다.
-
scss, sass로 스타일을 작성하는 이유
sass를 이용해서 문법을 작성하면 실제 웹에서 동작하기 위해서는 css로 변환이 되어야 한다.
그런데, 여기서 드는 의문점. 어차피 css로 변환을 해야하는데 왜 sass로 작성을 해야 하는 것일까?
기존 css로 작성했을 때, 우리는 하나의 요소에 스타일을 주기 위해서 모든 조상요소들을 다 소환해야 했다.
header .main-menu .item .item_contents .contents_menu > ul
이런식으로 매번 모든 조상요소들을 길~게 작성을 해줘야하는 번거로움이 발생했다.
하지만, sass의 중첩기능을 사용하면 손쉽고 간편하게 작성해줄 수 있다.
그리고 기존 css에서 색상을 작성할 때 색상 코드번호를 이용해서 작성한다.(#2C2A29 등) 이렇게 되면 다른 페이지에 사용할 때 혹시 오타라도 나게 되면 색상이 완전 달라지는 문제가 발생한다.
하지만, sass의 변수기능을 사용하여 해당 색상에 변수를 설정해주면 다른 페이지에서 사용할 때에도 오타 없이 안전하게 해당 색상을 사용할 수 있다.
-
css 전처리 도구
우리의 컴퓨터 → sass를 사용하여 작성
사용자 브라우저 → 기존의 css만 동작 가능
우리는 sass 문법을 css 문법으로 변환을 해줘야 한다.
이 변환되는 과정을 컴파일이라고 한다.
이 컴파일을 해주는 것이 css 전처기 도구(css 전처리기)이다.
표준의 css의 전처리기는 sass, scss, less, stylus 등이 있다.
프로젝트 생성
# 프로젝트 폴더로 이동
# npm 설치 (package.json 폴더 생성)
npm init -y
# parcel-bundler 설치
npm i -D parcel-bundler
package.json
"scripts": {
"dev": "parcel index.html",
"build": "parcel build index.html"
}
- index.html 파일에 link 태그를 통해 main.scss 파일 연결
-
간단하게 코드 작성
<body>
<div class="container">
<h1>Hello SCSS</h1>
</div>
</body>
-
scss 중첩 기능
.container {
h1 {
color: red;
}
}
container 클래스 안에 h1 태그를 넣는다 → 해당 기능이 중첩이다.
다음과 같이 작성하면 글자 색상에 빨강으로 변환 것을 볼 수 있다.
-
scss 변수 기능
$color: yellow;
.container {
h1 {
color: $color;
}
}
‘$’을 통해 변수를 생성한다. 생성한 변수를 h1 태그의 색상으로 넣어준다.
글자 색상이 노랑색으로 변환 것을 볼 수 있다.
-
기본적인 원리
index.html 파일에 link 태그를 통해서 main.scss 파일을 연결했다.
우리가 설치한 parcel-bundler 패키지를 통해서 분석돼서 실제 브라우저에서는 css로 변환되어 동작한다.
확인할 방법은 dist 폴더를 보면 main.~~.css파일을 볼 수 있다.
package.json 파일을 보면 sass 패키지가 설치된 것을 볼 수 있다. 이것 또한 parcel-bundler 패키지가 자동으로 main.scss 파일이 연결 된 것을 확인해서 필요한 모듈을 설치한 것이다.
결과적으로 sass 패키지를 설치해서 scss 파일을 css 파일로 변환해서 브라우저에서 동작시키게 된 것이다.
Generics Class
class Person<T> {
private _name: T;
constructor(name: T) {
this._name = name;
}
}
new Person("mark"); // string
// new Person<string>(29); // error
- T의 유효범위는 constructor나 private에서 발생하는 것이 아니라, 클래스 전체에서 타입의 유효범위가 발생한다.
- 따라서 generic으로 T를 선언해야 한다면 class 이름 뒤에 T를 지정한다.
- Person의 T가 string으로 들어간다.
- 만약 generic을 미리 세팅한다면(string으로 지정), 인자 부분에는 string만 들어와야 한다. number가 들어오면 error
class Person<T, K> {
private _name: T;
private _age: K;
constructor(name: T, age: K) {
this._name = name;
this._age = age;
}
}
new Person("mark", 29); // string
new Person<string, number>("Ban","Kan"); // error
- generic을 하나 더 추가하기 위해서 그대로 그냥 추가해 주면 된다..!
- 인자를 두개 받아야 하기에 하나만 받으면 error가 발생한다.
- generic을 통해 타입을 지정해주었을 때, 지정한 타입이 아닌 경우 error가 발생한다.
Generics with extends
우리가 흔히 생각하는 상속의 개념과는 약간 다른 generic extends
해당 개념을 사용하는 이유는 다른 사람이 나의 코드를 사용할 때, 예상치 못한 error을 막기 위함이다.
따라서, 유용하게 사용하면 좋은 키워드이다.
class PersonExtends<T extends string | number> {
private _name: T;
constructor(name: T) {
this._name = name;
}
}
new PersonExtends("Mark");
new PersonExtends(20);
new PersonExtends(true); // error
- class 이름 뒤에 T에 extends 키워드를 이용해서 타입을 추가로 넣어줄 수 있다.
- extends 키워드 뒤에 union 타입을 추가해주게 된다.
이를 해석하면, “T는 string과 number만 타입으로 갖는 것이 가능하다.” 는 말이다.
keyof & type lookup system
(컴파일 타임에서) 타입을 적절히 찾아내고 활용하는 시스템 만들기
interface IPerson {
name: string;
age: number;
}
const IPer: IPerson = {
name: "Mark",
age: 19,
}
function getProp(obj: IPerson, key: "name" | "age"): string | number {
return obj[key]
}
function setProp(obj: IPerson, key: "name" | "age", value: string | number): void {
obj[key] = value;
}
- name일때, IPerson에 name을 넣은게 나와야 하고, age일때 IPerson에 age를 넣은게 나와야 하는데 union 타입이 되어서 작성하기 어렵고 타입에 문제가 발생한다.
keyof
타입 앞에 붙여서 새로운 타입을 만들어 낸다.
interface IPerson {
name: string;
age: number;
}
const IPer: IPerson = {
name: "Mark",
age: 19,
}
// type Keys = keyof IPerson;
// const keys: Keys = "name";
// const keys: Keys = "age";
// IPerson[keyof IPerson]
// => IPerson[”name” | “age”]
// => IPerson[”name”] | IPerson[”age”]
// => string | number
function getProp(obj: IPerson, key: keyof IPerson): IPerson[keyof IPerson] {
return obj[key]
}
function setProp(obj: IPerson, key: keyof IPerson, value: string | number): void {
obj[key] = value;
}
- keys는 age, name을 가지고 있는 형태이다.
- 어떤 개체에 keyof를 붙이면 결과물이 type으로 나오는데, key의 이름으로 된 문자열 (name, age)의 union 타입으로 만들어진다.
- 따라서
string | number
을 적어주었던 인수부분에 keyof IPerson
을 적을 수 있다.
- return되는 결과물에는
IPerson[keyof IPerson]
으로 작성해 줄 수 있다.
- 단, 해당 결과물은 다음과 같이 나온다.
IPerson[keyof IPerson] ⇒ IPerson[”name” | “age”] ⇒ IPerson[”name”] | IPerson[”age”]
결과적으로 string과 number의 union타입이 된다.
- getProp을 넣었을 때, name을 넣으면 string이 결과물로 나오게끔 하지 못했다.
Generic
IPerson과 keyof IPerson을 이용해서 관계를 특정한 타입으로 지정을 해줘야 한다. 이를 위해 generic을 사용한다.
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
// getProp의 결과물
getProp(per ,'name'); // string
getProp(per, 'age') // numner
function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
setProp(per, "name", 39) // name과 맞지 않는 타입이기에 error
setProp(per, "name", "Anna") // 맞는 타입이기에 값 변경 완료. value는 string이 됨.
- IPerson자리에 T를 대신 사용한다.
- K를 만들어서 extends를 사용하여 제한을 걸어준다.
제한은 name과 age만 넣을 수 있도록 한다.
<T, K extends keyof T>
K는 keyof T에 의해서 제한된 형태(name 혹은 age)가 된다.
- 따라서 keyof T 자리에 K를 대신 넣어 사용한다.
K는 들어오는 것에 따라 name 혹은 age로 딱! 지정된다.
- 해당 방법으로 인해 런타임을 하기 전에 문자를 잘 못 넣게 되면 error로 알려준다.
- setProp은 value자리에 name혹은 age와 맞지 않은 타입이 들어가게 되면 error가 발생하고 적절한 타입을 넣어주면 값이 변경된다.
그리고 value의 타입이 해당 name 혹은 age의 타입으로 변경이 된다.