자바스크립트에서 모든 함수는 Function 객체이다.

하지만 함수는 호출할수있다는점이 일반 객체와는 다른점이다.   

 

 

 

함수를 생성하는 방법은 3가지가있다.

함수선언문(function statement)

함수표현식(function expression)

Function() 생성자 함수

 

최근에는 추가되어 화살표함수(Arrow function) 가있다.

 

 

 

우선 함수선언문에대해서 알아보자

 

 

 

자바스크립트 탄생

1995년 90%의 시장 점유율로 시장을 지배하고있던 넷스케이프 커뮤니케이션즈는 웹페이지의

보조적인 기능을 수행하기위해 브라우저에서 동작하는 경량 프로그래밍 언어를 도입하기로 결정한다.

그렇게 탄생한것이 자바스크립트이다.

 

자바스크립트 표준화

1996년 마이크로소프트는 자바스크립트의 파생 버전인 JScript를 인터넷 익스플로러에 탑재했다.

넷스케이프 커뮤니케이션즈와 마이크로소프트는 자사 브라우저의 시장점유율을 높이기위해 자사 

브라우저에서만 동작하는 기능을 경쟁적으로 추가하기 시작했다.

 

이로인해 웹페이지가 브라우저에따라 정상적으로 동작하지 않는 크로스 브라우징 이슈가 발생하기 시작했다.

결과적으로 모든 브라우저에서 정상적으로 동작하는 웹페이지를 개발하기가 무척 어려워졌다.

 

이에따라 모든브라우저에서 정상적으로 동작하는 표준화된 자바스크립트의 필요성이 대두되기 시작했다.

이를위해 넷스케이프 커뮤니케이션즈는 컴퓨터 시스템의 표준을 관리하는 비영리 표준화 기구인 ECMA 인터네이셔널에

자바스크립트 표준화를 요청한다.

 

2015년에 공개된 ECMAScript 6는(ECMAScript 2015, EX6)는 let/const 키워드, 화살표함수, 클래스, 모듈 등과 같이

프로그래밍언어로서 갖춰야할 기능들을 대거 도입하는 큰 변화가 있었다. 

 

자바스크립트 성장과 역사

초창기 자바스크립트는 웹페이지의 보조적인 기능을 수행하기위해 한정적인 용도로 사용되었다.

이시기에는 대부분로직은 주로 웹 서버에서 실행되었고, 브라우저는 서버로부터 전달받은 HTML과 CSS를 단순히 렌더링하는

수준이었다.

 

*렌더링이란 ,  HTML, CSS, 자바스크립트로 작성된 문서를 해석해서 브라우저에 시각적으로 출력하는 것을 말한다.

 

Ajax

1999년, 자바스크립트를 이용해 서버와 브라우저가 비동기 방식으로 데이터를 교환할 수 있는 통신기능인 Ajax가 등장했다.

이전의 웹페이지는 html 태그로 시작해서 html 태그로 끝나는 완전한 HTML 코드를 서버로부터 전송받아 웹페이지

전체를 렌더링하는 방식으로 동작했다. 따라서 화면이 전환되면 서버로부터 새로운 HTML을 전송받아 웹페이지 전체를

처음부터 다시 렌더링했다.

 

이러한 방식은 변경할 필요가 없는 부분까지 포함된 HTML 코드를 서버로부터 다시 전송받기 때문에 불필요한

데이터 통신이 발생하고, 변경할 필요가 없는 부분까지 처음부터 다시 렌더링해야 하기 때문에 성능 면에서도 불리하다.

이로인해 화면이 전환되면 화면이 순간적으로 깜빡이는 현상이 발생하고, 이는 웹페이지의 어쩔수 없는 한계로 받아들여졌다.

 

Ajax의 등작은 이전의 패러다임으 전환했다. 즉 웹페이지에서 변경할 필요가 없는 부분은 다시 렌더링하지 않고,

서버로부터 필요한 데이터만 전송받아 변경해야 하는 부분만 한정적으로 렌더링하는 방식이 가능해진것이다. 

이로서 웹브라우저에서도 데스크톱 애플리케이션과 유사한 빠른 성능과 부드러운 화면전환이 가능해졌다.

 

2005년 구글이 발표한 구글 맵스는 웹 애플리케이션 프로그래밍 언어로서 자바스크립트의 가능성을 확인하는 계기를 마련했다.

웹브라우저에서 자바스크립트와 Ajax를 기반으로 동작하는 구글 맵스가 데스크톱 애플리케이션과 비교했을때 손색이

없을 정도의 성능과 부드러운 화면 전환 효과를 보여준것이다.

 

 

jQuery

2006년 jQuery의 등작으로 다소 번거롭고 논란이 있던 DOM을 더욱 쉽게 제어할수있게 되었고 크로싱 브라우징

이슈도 어느정도 해결되었다. 이로인해 배우기가 다소 까다로운 자바스크립트보다 배우기 쉽고 직관적인 jQuery를 더 선호하는

개발자가 양산되기도 했다.

 

 

Node.js

2009년 라이언달이 발표한 Node.js는 구글 v8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경이다.

Node.js는 브라우저의 자바스크립트 엔진에서만 동작하던 자바스크립트를 브라우저 이외의 환경에서도 동작할수있도록

자바스크립트 엔진을 브라우저에서 독립시킨 자바스크립트 실행 환경이다. Node.js는 다양한 플랫폼에 적용할 수 있지만

서버사이드 애플리케이션 개발에 주로 사용되며, 이에 필요한 모듈, 파일시스템, HTTP 등 필트인 API를 제공한다.

 

Node.js는 자바스크립트엔진을 기반으로 하므로 Node.js 환경에서 동작하는 애플리케이션은 자바스크립트를 사용해 개발한다.

프런트엔드와 백엔드 영역에서 자바스크립트를 사용할수있는것은 별도의 언어를 학습하기위한 시간을 덜수있다는 장점이있다.

 

Node.js는 비동기 I/O를 지원하며 단일 스레드 이벤트 루프 기반으로 동작함으로써 요청처리 성능이 좋다.

따라서 Node.js는 데이터를 실시간으로 처리하기위해 I/O가 빈번하에 발생하는 SPA에 적합하다. 하지만 CPU 사용률이 높은

애플리케이션에는 권장하지 않는다.

 

이제 자바스크립트는 크로스 플랫폼을 위한 가장 중요한 언어로 주목받고있다. 웹은 물론 모바일 하이브리드 앱, 서버사이드, 데스크톱 

머신러닝, 로보틱스환경을 위한 프로그래밍 언어로서 세계에서 가장 인기있는 프로그래밍 언어이다.

 

SPA 프레임워크

모던 웹 애플리케이션은 데스크톱 애플리케이션과 비교해도 손색없는 성능과 사용자 경험을 제공하는 것이 필수가 되었고,

더불어 개발 규모와 복잡도도 상승했다. 이전의 개발방식으로는 복잡해진 개발과정을 수행하기 어려워졌고,

이러한 필요에 따라 많은 패턴과 라이브러리가 출현했다. 그 덕분에 개발에 많은 도움을 주었지만 변겨에 유연하면서

확장하기 쉬운 애플리케이션아키텍처의 구축을 어렵게 했고, 필연적으로 프레임워트가 등장하게 되었다.

 

이런한요구에 발맞춰 CBD(Component based development) 방법론을 기반으로하는 SPA 가 대중화되면서

Angular, React, Vue.js Svelte 등 다양한 SPA 프레인워크/라이브러리 또한 많은 사용층을 확보하고 있다.

 

 

자바스크립트의 특징

자바스크립트는 웹브라우저에서 동작하는 유일한 프로그래밍언어이다. 

자바스크립트는 개발자가 별도의 컴파일작업을 수행하지 않는 인터프리터 언어이다. 대부분 모던 자바스크립트 엔진

(크롬의 v8, 파이어포스의 spiderMonkey, 사파리의 javascriptcore, 마이크로소프트 엣지의 chakra 등) 

인터프리터와 컴파일러의 장점을 결합해 비교적 처리속도가 느린 인터프리터의 단점을 해결했다.

 

*컴파일러 언어 (코드가 실행되기전 단계인 컴파일 타임에 소스코드 전체를 한번에 머신코드로 변환한후 실행한다.)

*인터프리터 언어(코드가 실행되는 런타임에 문 단위로 한줄씩 중간코드인 바이트코드로 변환한후 실행한다.)

 

자바스크립트는 강력한 객체지향 프로그래밍 틍력을 가지고있다. 간혹 클래스, 상속, 정보 은닉을 위한 키워드가 없어서 객체지향 언어가

아니라고 오해를 하는 경우도 있지만 자바스크립트는 클래스 기반 객체지향 언어보다 효율적이면서 강력한 프로토타입 기반의 객체지향 언어다.

 

 ES6 브라우저 지원 현황

인터넷 익스플로러를 제외한 대부분 모던 브라우저는 ES6를 지원하지만 100%를 지워하고 있지는 않다.

 Node.js는 v4부터 ES6를 지원하기 시작했다. es6 지원 현황은 다음 웹사이트에서 확인할 수 있다.

 

인터넷 익스플로러를 제외한 모던 브라우저의 ES6 지원 비율을 거의 100%에 육박하지만 인터넷 익스플로러나

구형브라우저는 ES6를 대부분 지원하지 않는다.

따라서 브라우저에서 아직지원하지 않는 최신 기능을 사용하거나 인터넷 익스플로러나 구형 브라우저를 고려해야 하는 상황이라면

바벨과 같은 트랜스파일러를 사용해 ES6 이상의 사양으로 구현한 소스코드를 ES5 이하의 사양으로 다운그레이드할 필요가 있다.

 

웹어플리케션을 만들다보면 비동기작접으로 처리해야할때가 필요하다

한가지일만하는것이아닌 여러 작업을 실행하는, 파일 다운로드 기능이라 보면된다

 

이렇게 서버 API를 호출할때 외에도 작업을 비동기적으로 처리할때가있는데

바로 setTimeout 함수를 사용하여 특정작업을 예약할때이다.

 

function printMe() {
	console.log("hello world!");
}
setTimeout(printMe, 3000);
console.log("대기중 ...");

비동기작업을 할때 가장 흔히사용하는 방법은 콜백함수를 사용하는 것이다.

 

 

1. 콜백함수

 

function increase(number, callback) {
  setTimeout(() => {
    const result = number + 10;
    if (callback) {
      callback(result);
    }
  }, 1000);
}

increase(0, (result) => {
  console.log(result);
});

 

2. Promise

promise는 콜백 지옥같은 코드가 생기지않게 도입된기능이다.

여러 작업을 연달아 처리한다고 해서 함수를 여러번감싸는게아니라 then을

사용해서 그다음작업을 설정한다.

import { Component } from 'react';

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0,
  };
  render() {
    const { number, fixedNumber } = this.state; // state를 조회할때는 this.state로 조회한다
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지않는값 {fixedNumber}</h2>
        <button
          //onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
          onClick={() => {
            this.setState((prevState) => {
              return { number: prevState.number + 1 };
            }); //변하는값만 설정
            this.setState((prevState) => ({ number: prevState.number + 1 }));
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

function increase(number) {
  const promise = new Promise((resolve, reject) => {
    //resoleve는 성공,  reject는 실패
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        const e = new Error('numbertobig');
        return reject(e);
      }
      resolve(result);
    }, 1000);
  });
  return promise;
}

increase(0)
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .catch((e) => {
    //도중에 에러가 발생한다면
    console.log(e);
  });

 

 

3. async / await

async/await는 Promise를 더욱 쉽게 사용할수있도록 해주는  ES8문법이다.

이문법을 사용하려면 함수의 앞부분에 async 키워드를 추가하고 함수 내부에서 Promise의 앞부분에 await 키워드를 사용한다.

 

이렇게하면 Promise가 끝날때까지 기다리고, 결과값을 특정 변수에 담을수 있다.

function increase(number) {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        const e = new Error('numberToobig');
        return reject(e);
      }
      resolve(result);
    }, 1000);
  });
  return promise;
}

async function runTasks() {
  try {
    let result = await increase(0);
    console.log(result);
    result = await increase(0);
    console.log(result);
    result = await increase(result);
    console.log(result);
    result = await increase(result);
    console.log(result);
    result = await increase(result);
    console.log(result);
    result = await increase(result);
    console.log(result);
  } catch (e) {
    console.log(e);
  }
}

 

4.  axios로 API호출해서 데이터 받아오기

axios는 http요청을 promise 기반으로 처리한다

import { useState } from 'react';
import axios from 'axios';

const App = () => {
  const [data, setData] = useState(null);
  const onClick = () => {
    axios
      .get('https://jsonplaceholder.typicode.com/posts/1')
      .then((response) => {
        setData(response.data);
      });
  };
  return (
    <div>
      <div>
        <button onClick={onClick}>불러오기</button>
      </div>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};
export default App;

그렇다면 만약 async를 적용하면 어떨까??

import { useState } from 'react';
import axios from 'axios';

const App = () => {
  const [data, setData] = useState(null);
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/posts/1',
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };
  return (
    <div>
      <div>
        <button onClick={onClick}>불러오기</button>
      </div>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};
export default App;

이렇게 async () => { } 와 같은 형식으로 적용한다.

 

5. newsAPI 만들기

그렇다면 큰틀로 newsAPI를 만들어보자 

newsapi에서 API키를 발급받아야한다.

 

https://newsapi.org/account 

 

Login - News API

 

newsapi.org

에서 회원가입후 api키를 받는다.

 

 

 

https://newsapi.org/s/south-korea-news-api

 

South Korea News API - Live top headlines from South Korea

Get live top and breaking news headlines from South Korea with our JSON API. Live example This example demonstrates the HTTP request to make, and the JSON response you will receive, when you use the News API to get live headlines from South Korea. Top head

newsapi.org

에 들어가서 두가지  API 주소를 가져올것이다.

1. 전체뉴스불러오기

https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은 api 키

 

 

https://newsapi.org/v2/top-headlines?country=kr&category=sports&apiKey=발그받은 api 키

 

 

 

그후 주소만 기존소스에서 바꿔준다.

import { useState } from 'react';
import axios from 'axios';

const App = () => {
  const [data, setData] = useState(null);
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은 api키를 입력해라',
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };
  return (
    <div>
      <div>
        <button onClick={onClick}>불러오기</button>
      </div>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};
export default App;

 

1. 뉴스 뷰어UI 만들기

 

 yarn add styled-components  

 

명령어로  styled component를 설치한다.

 

우선 NewsItem 파일로 

 

json 데이타의 키값인

title, description, url, urlToImage

를 보여준다. 

 

import styled from 'styled-components';

const NewsItemBlock = styled.div`
  display: flex;
  .thumbnail {
    margin-right: 1rem;
    img {
      display: block;
      width: 160px;
      height: 100px;
      object-fit: cover;
    }
  }
  .contents {
    h2 {
      margin: 0;
      a {
        color: black;
      }
    }
    p {
      margin: 0;
      line-height: 1.5;
      margin-top: 0.5rem;
      white-space: normal;
    }
  }
  & + & {
    margin-top: 3rem;
  }
`;

const NewsItem = ({ article }) => {
  const { title, description, url, urlToImage } = article;
  return (
    <NewsItemBlock>
      {urlToImage && (
        <div className="thumbnail">
          <a href={url} target="_blank" rel="noopener noreferrer">
            <img src={urlToImage} alt="thumbnail" />
          </a>
        </div>
      )}
      <div className="contents">
        <h2>
          <a href={url} target="_blank" rel="noopener noreferrer">
            {title}
          </a>
        </h2>
        <p>{description}</p>
      </div>
    </NewsItemBlock>
  );
};

export default NewsItem;

 

 

NewsList 파일로 보여준다

json 데이타를 자식컴포넌트에 보여줄값들을 임의로 만들어준다. (임시)

import React from 'react';
import NewItem from './NewsItem';
import styled from 'styled-components';

const NewsListBock = styled.div`
  box-sizing: border-box;
  padding-bottom: 3rem;
  width: 768px;
  margin: 0 auto;
  margin-top: 2rem;
  @media screen and (max-width: 768px) {
    width: 100%;
    padding-left: 1rem;
    padding-right: 1rem;
  }
`;

const sampleArticle = {
  title: '제목',
  description: '내용',
  url: 'https://google.com',
  urlToImage: 'Https://via.placeholder.com/160',
};

const NewsList = () => {
  return (
    <NewsListBock>
      <NewItem article={sampleArticle}></NewItem>
      <NewItem article={sampleArticle}></NewItem>
      <NewItem article={sampleArticle}></NewItem>
      <NewItem article={sampleArticle}></NewItem>
      <NewItem article={sampleArticle}></NewItem>
    </NewsListBock>
  );
};

export default NewsList;

 

 

그후 App 폴더에서 NewsList 컴포넌트를 렌더링해준다.

import { useState } from 'react';
import axios from 'axios';
import NewsList from './Components/NewsList';

const App = () => {
  return (
    <div>
      <NewsList></NewsList>
    </div>
  );
};
export default App;

 

 

그러면 인위적인  List가 보일것이다.

이인위적인 데이타를 이제 API를 사용해서 데이터를 연동해보겠다

 

NewsList.js 소스에 axios 를 더해준다

 

import React, { useEffect, useState } from 'react';
import NewItem from './NewsItem';
import styled from 'styled-components';
import axios from 'axios';

const NewsListBock = styled.div`
  box-sizing: border-box;
  padding-bottom: 3rem;
  width: 768px;
  margin: 0 auto;
  margin-top: 2rem;
  @media screen and (max-width: 768px) {
    width: 100%;
    padding-left: 1rem;
    padding-right: 1rem;a
  }
`;

const sampleArticle = {
  title: '제목',
  description: '내용',
  url: 'https://google.com',
  urlToImage: 'Https://via.placeholder.com/160',
};

const NewsList = () => {
  const [articles, setArticles] = useState(null);
  const [loading, setLoding] = useState(false);
  useEffect(() => {
    //async를 사용하는 함수 따로 선언
    const fetchData = async () => {
      try {
        const response = await axios.get(
          'https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은 api키',
        );
        setArticles(response.data.articles);
      } catch (e) {
        console.log(e);
      }
      setLoding(false);
    };
    fetchData();
  }, []);

  if (loading) {
    return <NewsListBock>대기중 ...</NewsListBock>;
  }
  if (!articles) {
    return null;
  }

  return (
    <NewsListBock>
      {articles.map((article) => (
        <NewItem key={article.url} article={article} />
      ))}
    </NewsListBock>
  );
};

export default NewsList;

여기서 중요한점은 map 함수를 조회하기전에  !articles를 조회해서 해당값이 null인지 

검사해야한다. 이작업을 않한다면 데이터가없을때 null에 map 함수가 없기때문에 렌더링 과정에서 오류가 발생한다.

 

다음엔 카테고리 기능구현을...진행

SASS는 css 전처리기로 복잡한 작업을 쉽게 할수잇도록 해주고 스타일 코드의 재활용성을 높여준다

실상에서는 SCSS가 많이 사용되고, 회사에서도 scss를 사용하고있으로 

SCSS로 볼려고한다.

 

우선 

 

yarn add sass

설치를 해준다.

 

 

1. scss 로 컴포넌트 스타일링하기

우선 SassComponent.js  컴포넌트파일을 만든다.

import React from 'react';
import './SassComponent.scss';

const SassComponent = () => {
  return (
    <div className="SassComponent">
      <div className="box red"></div>
      <div className="box orange"></div>
      <div className="box yellow"></div>
      <div className="box green"></div>
      <div className="box blue"></div>
      <div className="box indigo"></div>
      <div className="box violet"></div>
    </div>
  );
};

export default SassComponent;

 

 

그후 SassComponent.scss  파일을 작성해준다.

$red: #fa5252;
$orange: #fd6e14;
$yellow: #fcc419;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
//믹스인 만들기( 재사용되는 스타일 블록을 함수처럼 사용할수있음 )
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

.SassComponent {
  display: flex;
  .box {
    background: red;
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red {
      //.red 클래스가 .box와 함께 사용되었을때
      background: $red;
      @include square(1);
    }
    &.orange {
      background: $orange;
      @include square(2);
    }
    &.yellow {
      background: $yellow;
      @include square(4);
    }
    &.blue {
      background: $blue;
      @include square(5);
    }
    &.indigo {
      background: $indigo;
      @include square(6);
    }
    &.violet {
      background: $violet;
      @include square(7);
    }
  }
}

 

2. utils 함수 분리하기

여러 파일에서 사용될수있는 Sass 변수및 믹스인은 다른 파일로 분리하여 작성한뒤

필요한곳에서 쉽게 불러와 사용할수있다.

 

src디렉터리에  styles라를 디렉터리를 생성후, 그안에 utils.scss파일을 만들자

 

$red: #fa5252;
$orange: #fd6e14;
$yellow: #141412;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
//믹스인 만들기( 재사용되는 스타일 블록을 함수처럼 사용할수있음 )
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

전에 작성했던 mixin을 불러온다음

 

전파일인  SassComponent.scss 에서

@import 구문을 써서 임포트해준다

 

@import './styles/utils.scss';

.SassComponent {
  display: flex;
  .box {
    background: red;
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red {
      //.red 클래스가 .box와 함께 사용되었을때
      background: $red;
      @include square(1);
    }
    &.orange {
      background: $orange;
      @include square(10);
    }
    &.yellow {
      background: $yellow;
      @include square(3);
    }
    &.blue {
      background: $blue;
      @include square(5);
    }
    &.indigo {
      background: $indigo;
      @include square(6);
    }
    &.violet {
      background: $violet;
      @include square(7);
    }
  }
}

 

 

3. sass-loader 설정 커스터마이징하기

만약 프로젝트에 디렉터리를 많이 만들어서 구조가 깊어졌다면

@import "../../../styles/utils";

 

이문제점은 웹팩에서 Sass 처리하는 sass-loader 설정을 커스터마이징해서 해결할수있다.

이를 커스터마이징하려면 yarn eject 명령어로 세부설정을 밖으로 꺼내주어야한다.

 

 

yarn eject를 실행후

 

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction
        ? shouldUseSourceMap
        : isEnvDevelopment,
      modules: {
        mode: 'icss',
      },
    },
    'sass-loader'
  ),
  // Don't consider CSS imports dead code even if the
  // containing package claims to have no side effects.
  // Remove this when webpack adds a warning or an error for this.
  // See https://github.com/webpack/webpack/issues/6571
  sideEffects: true,
},

sassRegex 라는 키워드를 찾고난후

 

{
  test: cssModuleRegex,
  use: getStyleLoaders({
    importLoaders: 1,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
    modules: {
      mode: 'local',
      getLocalIdent: getCSSModuleLocalIdent,
    },
  }),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders({
    importLoaders: 3,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
    modules: {
      mode: 'icss',
    },
  }).concat({
    loader: require.resolve('sass-loader'),
    options: {
      sassOptions: {
        includePaths: [paths.appSrc + '/styles'],
      },
    },
  }),
  // Don't consider CSS imports dead code even if the
  // containing package claims to have no side effects.
  // Remove this when webpack adds a warning or an error for this.
  // See https://github.com/webpack/webpack/issues/6571
  sideEffects: true,
},

sass-loader 부분을 지우고 concat을 통해 커스터마이징된 sass-loader 설정을 넣는다.

 

 

그리고 SassComponent.scss 파일에서 import구문을 수정해본다

@import 'utils.scss';

이렇게 선언만해줘도 바로 가져올수있게된다

 
 
만약 자동으로 scss 파일을 import 해주고싶다면
{
    test: sassRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders({
    importLoaders: 3,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
    modules: {
      mode: 'icss',
    },
    }).concat({
    loader: require.resolve('sass-loader'),
    options: {
      sassOptions: {
        includePaths: [paths.appSrc + '/styles'],
      },
      addtionalData: "@import 'utils';",	//구문추가
    },
}),

addtionalData  부분을 추가해주면 import 구문을 지워도 자동으로 utils.scss 파일을 불러올수있다.

 

 

4. CSS Module

CSS Module 은 클래스이름을 파일이름_클래스이름_해시값 형태로 만들어

컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해준다

.module.css 확장자로 저장하면 CSS Module 이 적용된다.

 

 

 

여기까지 컴포넌트 스타일링에대해서 알아보았다

 

만약에 반복되는 코드를 작성해야한다면???

 

import React from 'react';
import PropTypes from 'prop-types';

const IterationSample = () => {
  return (
    <div>
      <ul>
        <li>눈사람</li>
        <li>얼음</li>
        <li>눈</li>
        <li>바람</li>
      </ul>
    </div>
  );
};

export default IterationSample;

 

 

이렇게 <li> ... </li>  반복되는 코드가있을수있다

그렇다면 파일용량도 증가되므로 낭비된다

 

그러면 어떻게 이런반복적인 내용을 효율적으로 보여줄까?

 

 

 

 

 

1. 배열의 map()함수

자바스크립트 배열 객체의 내장 함수인  map 함수를 사용해 반복되는 컴포넌트를 랜더링해줄수있다.

 

문법

const numbers = [1, 2, 3, 4, 5];
const result = numbers.map((num) => num * num);
console.log(result);	//결과 : 1,4,9,16,25

 

이렇게 사용된다

map 함수를 사용해서 컴포넌트를 랜더링해보자

 

 

 

 

 

map 함수를 사용해서 컴포넌트 만들기

import React from 'react';
import PropTypes from 'prop-types';

const IterationSample = () => {
  const names = ['눈사람', '얼음', '눈', '바람'];
  const nameList = names.map((name) => <li>{name}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

IterationSample 컴포넌트를 만들고, map을 사용해서 nameList를 뽑아주었다.

하지만 콘솔창을 켜보니 오류가많았다

 

Each child in a list should have a unique "key" prop.

매 리스트는 key값을 가져야한다는 메시지이다.

 

 

 

 

 

 

2. key

 

 

리액트에서 key는 컴포넌트 배열을 렌더링햇을때 어떤 원소에 변동이있엇는지 알아낸다.

그래서 key가있을때 이값을 사용해서 리스트를 순차적으로 비교할필요없이 더욱  빠르게알아낼수있다.

원래있던 소스에서 key 설정을 해주면

 

import React from 'react';
import PropTypes from 'prop-types';

const IterationSample = () => {
  const names = ['눈사람', '얼음', '눈', '바람'];
  const nameList = names.map((name, index) => <li key={index}>{name}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

 

 

이러면 더이상의 오류가 생기지않는다.

하지만 index를 key 로 사용하면 배열이 변경될때 효율적으로 리렌더링 하지 못한다.

이것을 활용해서 동적인 배열을 렌더링해보는것을 구현해보자

 

 

 

 

 

3.  초기상태 설정하기

import React from 'react';
import { useState } from 'react';

const IterationSample = () => {
  const [names, setNames] = useState([
    { id: 1, text: '눈사람' },
    { id: 2, text: '눈' },
    { id: 3, text: '얼음' },
  ]);

  const nameList = names.map((name) => <li key={names.id}>{name.text}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

 

 

 


1. input 박스만들어서 값상태변경

 

input 박스를 만들어서

onChange이벤트를 발생

useState로 새로운값을 렌더링해주었다 (바뀌는값관리) ->  hook

 

import React from 'react';
import { useState } from 'react';

const IterationSample = () => {
  const [names, setNames] = useState([
    { id: 1, text: '눈사람' },
    { id: 2, text: '눈' },
    { id: 3, text: '얼음' },
  ]);

  const [inputText, setInputText] = useState('');
  const onChange = (e) => {
    setInputText(e.target.value);
    console.log(inputText);
  };

  const nameList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return (
    <>
      <input value={inputText} onChange={onChange}></input>
      <ul>{nameList}</ul>
    </>
  );
};

export default IterationSample;

 

2. 데이터 추가 기능 구현하기

 

import React from 'react';
import { useState } from 'react';

const IterationSample = () => {
  const [names, setNames] = useState([
    { id: 1, text: '눈사람' },
    { id: 2, text: '눈' },
    { id: 3, text: '얼음' },
    { id: 4, text: '바람' },
  ]);
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); //새로운항목을 추가할때 사용할 id

  const onClick = () => {
    const nextNames = names.concat({
      id: nextId,
      text: inputText,
    });
    setNextId(nextId + 1); //nextId값에 1을 더해준다
    setNames(nextNames); // names값을 업데이트한다
    setInputText(''); // inputText값을 비운다.
  };

  const onChange = (e) => {
    setInputText(e.target.value);
    console.log(inputText);
  };

  const nameList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return (
    <>
      <input value={inputText} onChange={onChange}></input>
      <button onClick={onClick}>추가</button>
      <ul>{nameList}</ul>
    </>
  );
};

export default IterationSample;

 

 

 

1. 버튼클릭시

2. onClick 이벤트 발생

3.useState 의 현재값을 concat 으로 배열복사

4.key값증가->  고유값설정, 값을 useState 업데이트 함수로 전달

5. input 값 초기화

 

 

 

 

 

3. 데이터 제거 기능 구현하기

각 항목을 더블클릭햇을때 리스트를사라지는 기능을 구현해보겠다

배열의 특정항목을 지울때는 배열의 filter 를 사용한다

filter함수를 사용하면 배열에서 특정조건을 만족하는 원소들만 분류할수있다

 

import React from 'react';
import { useState } from 'react';

const IterationSample = () => {
  const [names, setNames] = useState([
    { id: 1, text: '눈사람' },
    { id: 2, text: '눈' },
    { id: 3, text: '얼음' },
    { id: 4, text: '바람' },
  ]);
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); //새로운항목을 추가할때 사용할 id

  const onClick = () => {
    const nextNames = names.concat({
      id: nextId,
      text: inputText,
    });
    setNextId(nextId + 1); //nextId값에 1을 더해준다
    setNames(nextNames); // names값을 업데이트한다
    setInputText(''); // inputText값을 비운다.
  };

  const onChange = (e) => {
    setInputText(e.target.value);
    console.log(inputText);
  };

  const onRemove = (id) => {
    const nextNames = names.filter((name) => name.id !== id);
    setNames(nextNames);
  };

  const nameList = names.map((name) => (
    <li key={name.id} onDoubleClick={() => onRemove(name.id)}>
      {name.text}
    </li>
  ));
  return (
    <>
      <input value={inputText} onChange={onChange}></input>
      <button onClick={onClick}>추가</button>
      <ul>{nameList}</ul>
    </>
  );
};

export default IterationSample;

1. onDoubleClick 클릭시 onRemove함수로 id  파라미터를 준다

2. id값으로 삭제하려는 특정id값으로

3. filter함수로 삭제하려는 항목만 삭제시켜준다

 

 

 

 

 

 

정리

반복되는 데이터를 map  함수로 렌더링 하는 방법을 배우고  key 값을 통해서 특정값을  

삭제 , 추가 해주는 방법을 알앗다

여기서 중요한점은  key값은 무조건 유일해야한단는 것이다.

또한 배열을 변형할때는 concat,filter의 배열 내장 함수를 사용하여 새로운 배열을 만든후

새로운 상태로 설정해주어야한다는 것을 명심해야한다.

 

 

다음장에서는 컴포넌트의 스타일링에대해서 알아보자

 

ref는 언제사용할까?

Dom에 직접적으로 접근해야하는 상황이라고한다.

 

 

 

 

 

 

1. state로 상태변화 rendering하기

 

우선 값 변화에따라 state를 사용해서 만든 소스를 구현해보자

import { Component } from 'react';
import './ValidationSample.css';
class ValidationSample extends Component {
  state = {
    password: '',
    clicked: false,
    validated: false,
  };

  handleChange = (e) => {
    this.setState({
      password: e.target.value,
    });
  };

  handleButtonClick = (e) => {
    this.setState({ clicked: true, validated: this.state.password === '0000' });
  };

  render() {
    return (
      <div>
        <input
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={
            this.state.clicked
              ? this.state.validated
                ? 'success'
                : 'failure'
              : ''
          }
        ></input>
        <button onClick={this.handleButtonClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationSample;

 

 

 

 

이렇게 값이 변하고, 변한값을 검증하는과정을 state로 구현할수있지만

state로 구현할수없는 상황도 있다고한다

 

 

 

 

1. 특정 input에 포커스주기

2. 스크롤 박스 조작하기

3. Canvas요소에 그림 그리기 등

 

 

 

 

 

2. 콜백 함수를 통한 ref  설정

 

 

 

ref를 만드는 가장 기본적인 방법은 콜백함수를 사용하는 것이다. 

<input
  ref={(ref) => {
    this.input = ref;
  }}
></input>;

이렇게 ref를 달고하자는 요소에 ref라는 콜백함수를 props로 전달해주면된다.

이콜백함수는 ref를 파라미터로 전달받고, 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해준다.

 

 

 

 

3. createRef를 통한 ref 설정

ref를만드는 또 다른 방법은 리액트에 내장되이는 createRef라는 함수를 사용한다.

우선 콜백함수로 사용하는 방식을 좀더보고자 넘어간다.

 

 

 

 

 

4. 컴포넌트에 ref 달기

리액트에서는 컴포넌트에도 ref를 달수있다.

스크롤 박스가있는 컴포넌트를 만들고, 스크롤바를 아래로 내리는 작업을 부모 컴포넌트에서 실행해보겟다.

 

import { Component } from 'react';

class ScrollBox extends Component {
  render() {
    const style = {
      border: '1px solid black',
      height: '300px',
      width: '300px',
      overflow: 'auto',
      position: 'relative',
    };

    const innerStyle = {
      width: '100%',
      height: '650px',
      background: 'linear-gradient(white, black)',
    };

    return (
      <div
        style={style}
        ref={(ref) => {
          this.box = ref;
        }}
      >
        <div style={innerStyle}></div>
      </div>
    );
  }
}

export default ScrollBox;

 

 

 

그후 상위 컴포넌트에 onClick 이벤트와,  ref 를 선언해준다

 

import { Component } from 'react';
import ScrollBox from './ScrollBox';

class App extends Component {
  render() {
    return (
      <div>
        <ScrollBox ref={(ref) => (this.scrollBox = ref)}></ScrollBox>
        <button onClick={() => this.scrollBox.scrollToBottom()}>
          맨 밑으로
        </button>
      </div>
    );
  }
}
export default App;

 

import { Component } from 'react';

class ScrollBox extends Component {
  scrollToBottom = () => {
    const { scrollHeight, clientHeight } = this.box;
    // 비구조화할당 const scrollHeigt = this.box.scrollHeight 같다

    this.box.scrollTop = scrollHeight - clientHeight;
  };
  render() {
    const style = {
      border: '1px solid black',
      height: '300px',
      width: '300px',
      overflow: 'auto',
      position: 'relative',
    };

    const innerStyle = {
      width: '100%',
      height: '650px',
      background: 'linear-gradient(white, black)',
    };

    return (
      <div
        style={style}
        ref={(ref) => {
          this.box = ref;
        }}
      >
        <div style={innerStyle}></div>
      </div>
    );
  }
}

export default ScrollBox;

 

 

그후 버튼클릭시 scrollToBottom() 메서드를 선언해주었다.

맨빝으로 버튼을 누르면 맨아래로 이동하는것을 알수있다.

 

 

 

 

 

 

 

정리

컴포넌트에서 DOM에 직접 접근해야할때 ref를 사용한다. 

만약 서로다른 컴포넌트끼리 데이터를 교류한다면? ref를 사용할순있지만 리액트사상에 어긋난 설계이다.

그건 나중에 리덕스와 Context API를 사용해서 효율적으로 교류하게될겄이다.

아직 함후컴포넌트에서 ref를 사용하지않았다. 함수컴포넌트에서는 useRef라는 Hook 함수를 사용한다 나중에 보고

 

다음장에서는  컴포넌트의 반복에대해서 알아보자

 

 

 

 

+ Recent posts