keyframes

  • styled-components 안에 들어있음
  • 웹에서 애니메이션을 구현할 때 transition과 animation이라는 스타일 속성을 많이 사용함
  • transition은 단순한 엘리먼트 상태 변화 시, animation은 다이나믹한 효과를 줄 때 적합
  • keyframes는 animation에서 사용하는 속성 중 하나!
  • css에서 keyframes를 사용하는 방법
@keyframes boxFade {
	0% {
		opacity: 1;
	}
	50% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

 

styled-components에서 keyframes 사용하기

styled-components 패키지 설치

yarn add styled-components

위 아래로 움직이는 애니메이션

const Box = styled.div`
	width: 100px;
	height: 100px;
	border-radius: 50px;
	background: green;
	position: absolute;
	top: 20px;
	left: 20px;
	animation: ${boxFade} 2s 1s infinite linear alternate;
`;
  • styled 안에서 변수를 이용하기 위해 ${변수이름} 형식으로 작성
  • animation duration : 2s, 1s
  • animation-iteration-count : infinite
const boxFade = keyframes`
	0% {
		opacity: 1;
		top: 20px;
	}
	50% {
		opacity: 0;
		top: 400px;
	}
	100% {
		opacity: 1;
		top: 20px;
	}
`;

 

스크롤 움직이기

  • window.scrollTo() 이용
<button onClick={() => { window.scrollTo(0, 0); }}>위로가기</button>
  • 웹페이지 상단으로 스크롤x 이동함
<button onClick={() => { window.scrollTo({top: 0, left: 0, behavior: "smooth"}); }}>위로가기</button>
  • behavior을 추가해서 자연스러운 스크롤 구현
  • 좌표뿐만 아니라 ref를 통해 특정 엘리먼트의 위치로 스크롤 시킬 수도 있다.
  • 리덕스 패키지 설치하기
yarn add redux react-redux

리덕스란?

  • 데이터를 한 곳에 몰아넣고 이곳저곳에서 꺼내볼 수 있게 하는, 상태 관리

State

  • 저장하고 있는 상태값(=데이터)
  • 딕셔너리 형태({[key]: value}) 형태로 보관

Action

  • 상태에 변화가 필요할 때 (가지고 있는 데이터를 변경할 때) 발생하는 것
{type: 'CHANGE_STATE', data: {...}} //type = 이름

ActionCreator

  • 액션 생성 함수, 액션을 만들기 위해 사용함
const changeState = (new_data) => { //액션을 리턴한다 return { type: 'CHANGE_STATE', data: new_data, } }

Reducer

  • 리덕스에 저장된 상태(=데이터)를 변경하는 함수
  • 액션 생성 함수 호출 → 액션을 만들면 → 리듀서가 현재 상태(=데이터)와 액션 객체를 받아서 → 새로운 데이터를 만들고 → 리턴
//기본 상태값을 임의로 지정했음
const initialState = {
	name: 'yewon'
}

function reducer(state = initialState, action) {
	switch(action.type){
		//action의 type마다 케이스문을 작성하면
		//액션에 따라 새로운 값을 리턴할 수 있음
		case CHANGE_STATE:
			return {name: 'garam'};
		default:
			return false;
	}
}

Store

  • 프로젝트에 리덕스를 적용하기 위해 만드는 것
  • 리듀서, 현재 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한 내장 함수 포함

dispatch

  • 액션을 발생시키는, 스토어의 내장 함수

리덕스의 특징 3가지

  1. store은 프로젝트 하나 당 1개만 쓴다.
  2. store의 state는 오직 action으로만 변경할 수 있다.
  3. 어떤 요청이 와도 리듀서는 같은 동작을 해야한다.
  • 리듀서는 순수한 함수여야 한다.
    • 파라미터 외의 값에 의존하지 않는다.
    • 이전 상태는 수정하지(=건드리지) 않는다.
    • 파라미터가 같으면 항상 같은 값을 반환
    • 리듀서는 이전 상태와 액션을 파라미터로 받는다.

리덕스를 통한 리액트 상태 관리

  • 여러 컴포넌트가 동일한 상태를 보고 있을 때 유용하게 사용
  • 데이터를 관리하는 로직을 컴포넌트에서 빼면, 컴포넌트에서는 뷰만 관리할 수 있음 → 유지보수하기 좋다

상태 관리 흐름도

  1. 리덕스 store을 component에 연결
  2. component에서 상태 변화가 필요할 때 action 호출
  3. reducer을 통해 새로운 상태 값을 만듦
  4. 새 상태값을 store에 저장
  5. component는 새로운 상태값을 받아옴 (props를 통해 받아오므로 다시 렌더링됨)


덕스(ducks) 구조

  • 보통 리덕스를 사용할 때, action, actionCreator, reducer를 분리해서 작성한다.
  • 덕스 구조는 모양새로 묶는 대신 기능으로 묶어 작성한다.
  • (ex. 버킷리스트의 action, actionCreator, reducer을 한 파일에 넣는다.)

모듈 만들기

  • src 폴더 - redux 폴더 - modules 폴더 안에 bucket.js 만들기
  • bucket.js
//Action
const LOAD = 'bucket/LOAD';
const CREATE = 'bucket/CREATE';

//initialState
const initialState = {
	list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
};

//Action Creator
export const loadBucket = (bucket) => {
	return {type: LOAD, bucket};
}

export const createBucket = (bucket) => {
	return {type: CREATE, bucket};
}

//Reducer
export default function reducer(state = initialState, action = {}) {
	switch (action.type) {
		case 'bucket/LOAD':
			return state;

		case 'bucket/CREATE':
			const new_bucket_list = [...state.list, action.bucket];
			return {list: new_bucket_list};

		default:
			return state;
	}
}

 

  • src 폴더 - redux 폴더 - configStore.js 만들기
  • configStore.js
import {createStore, combineReducers} from 'redux';
import bucket from './modules/bucket';
import {createBrowserHistory} from 'history';

//브라우저 히스토리
export const history = createBrowserHistory();
//root 리듀서
const rootReducer = combineReducers({bucket});
//스토어
const store = createStore(rootReducer);

export default store;

리덕스와 컴포넌트 연결하기

  • 스토어를 불러오고 버킷리스트에 주입한다
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {BrowserRouter} from 'react-router-dom';

//버킷리스트에 리덕스를 주입해줄 프로바이더
import {Provider} from 'react-redux';
//연결할 스토어
import store from './redux/configStore';

ReactDOM.render(
	<Provider store={store}>
		<BrowserRouter>
			<App />
		</BrowserRouter>
	</Provider>,
	document.getElementById('root')
);

컴포넌트에서 리덕스 데이터 사용하기

1. 클래스형 컴포넌트에서 리덕스 데이터 사용하기

  • 리덕스 모듈과 connect 함수 불러오기
  • 상태값을 가져오는 함수(mapStateToProps)와 액션 생성 함수를 부르는 함수(mapDispatchToProps) 만들기
  • connect로 컴포넌트와 스토어 엮기
  • this.state에 있는 list를 지우고 스토어에 있는 값으로 바꾸기
  • setState를 this.props.create로 바꾸기
  • App.js
import React from 'react';
import {withRouter} from 'react-router';
import {Route, Switch} from 'react-router-dom';

//리덕스 스토어와 연결하기 위해 connect 호출
import {connect} from 'react-redux';
//리덕스 모듈에서 액션 생성 함수 가져오기
import {loadBucket, createBucket} from './redux/modules/bucket';

//스토어가 가진 상태값을 props로 받아오기 위해
const mapStateToProps = (state) => ({
	bucket_list: state.bucket.list,
});

//액션 생성 함수를 props로 받아오기 위한 함수
const mapDispatchToProps = (dispatch) => ({
	load: () => {
		dispatch(loadBucket());
	},
	create: (new_item) => {
		dispatch(createBucket(new_item));
	}
});

class App extends React.Component {
	constructor(props){
		super(props);
		this.state = {
		};
		this.text = React.createRef();
	}

	componentDidMount(){
		console.log(this.props);
	}

	addBucketList = () => {
		const new_item = this.text.current.value;
		this.props.create(new_item);
	};

	render(){
		return(
			<div className="App">
				<Container>
					<Title>내 버킷리스트</Title>
					<Line/>
					<Switch>
						<Route path="/" exact
							render={(props) => (
								<BucketList
									list={this.props.bucket_list}
									history={this.props.history}
								/>
							)}
						/>
						<Route path="/detail" component={Detail}/>
						<Route
							render={(props) => <NotFound history={this.props.history} />}
						/>
					</Switch>
				</Container>
				<Input>
					<input type='text' ref={this.text}/>
					<button onClick={this.addBucketList}>추가하기</button>
				</Input>
			</div>
		);
	}
}

//connect로 묶기
export default connect(mapStateToProps, mapDispatchProps)(withRouter(App));

 

2. 함수형 컴포넌트에서 리덕스 데이터 사용하기

  • 훅을 사용해서 액션 생성 함수를 부르고 스토어에 저장된 값을 가져온다
  • BucketList.js에 useSelector() 적용
import React from 'react';
//reudx hook 불러오기
import {useDispatch, useSelector} from 'react-redux';

const BucketList = (props) => {
	const bucket_list = useSelector(state => state.bucket.list);

	return (
		<ListStyle>
			{bucket_list.map((list, index) => {
				return (
					<ItemStyle
						className='list_item'
						key={index}
						onClick={() => {
							props.history.push('/detail');
						}}
					>
						{list}
					</ItemStyle>
				);
			})}
		</ListStyle>
	);
};

export default BucketList;

 

3. 상세페이지에서 버킷리스트 내용 띄우기

  • Detail.js
import React from 'react';
//redux hook 불러오기
import {useDispatch, useSelector} from 'react-redux';

const Detail = (props) => {
	//스토어에서 상태값 가져오기
	const bucket_list = useSelector((state) => state.bucket.list);
	//url 파라미터에서 인덱스 가져오기
	let bucket_list = parseInt(props.match.params.index);

	return <h1>{bucket_list[bucket_index]}</h1>;
};

export default Detail;

SPA (Single Page Application)

  • 서버에서 주는 html이 1개만 있는 어플리케이션
  • 전통적인 웹사이트 : 페이지를 이동할 때마다 정적자원들(html, css, js)을 내려줌
  • SPA : 딱 한번만 정적자원을 받아옴

라우팅

  • 브라우저 주소에 따라 다른 페이지를 보여주는 것
  • SPA에서 html은 딱 하나만 있지만 브라우저 주소창대로 다른 페이지를 보여줄 수 있음

리액트에서 라우팅 처리하기

  • react-route-dom 패키지 설치하기
yarn add react-router-dom

적용하기

1. index.js에 BrowserRouter 적용하기

import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
	<BrowserRouter>
		<App />
	</BrowserRouter>
	document.getElementById("root")
);
  • BrowserRouter : 웹 브라우저가 가지고 있는 주소 정보를 props로 넘겨줌

 

2. 세부 화면 만들기

  • Home.js
const Home = (props) => {
	return (
		<div>메인 화면</div>
	)
}

export default Home;
  • Cat.js
const Cat = (props) => {
	return (
		<div>고양이</div>
	)
}

export default Cat;
  • Dog.js
const Dog = (props) => {
	return (
		<div>강아지</div>
	)
}

export default Dog;

 

3. App.js에서 Route 적용하기

  • Route 사용 방법 1) 넘겨줄 props가 없을 때
<Route path="주소[ex. /home]" component={[보여줄 컴포넌트]}/>
  • Route 사용 방법 2) 넘겨줄 props가 있을 때
<Route path="주소" render={(props) => (<BucketList list={this.state.list}/>)} />
  • App.js에 적용하기
import {Route} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;
  • "/"가 "/cat"와 "/dog"에 포함되어 있기 때문에 exact 추가

4. URL 파라미터 사용하기

  • 파라미터 : /cat/nabi
  • 쿼리 : /cat?name=nabi
  • App.js
<Route path="/cat/:cat_name" component={Cat}/>
  • Cat.js에서 console.log(props.match); 콘솔로 파라미터 확인 가능

5. 링크 이동시키기

1) <Link/> 사용하기

<Link to="주소">[텍스트]</Link>
  • App.js
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>

				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;

2) history 사용하기

  • App.js
import {withRouter} from 'react-router';
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	componentDidMount(){
		console.log(this.props);
	}

	render() {
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>
	
				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
	
				<button onClick={() => {
					this.props.history.push("/cat");
				}}>
					cat으로 가기
				</button>
				<button onClick={() => {
					this.props.history.goBack();
				}}>뒤로가기</button>
			</div>
		);
	}
}
//내보내는 부분에서 withRouter로 감싸주기
export default withRouter(App);
  • push([이동할 주소]) : 페이지 이동시킴
  • goBack() : 뒤로가기

 

잘못된 주소 처리하기 (Switch 이용)

  • NotFound.js
const NotFound = (props) => {
	return <h1>주소가 올바르지 않습니다</h1>;
);

export default NotFound;
  • App.js
render() {
	return (
		<div className="App">
			...
			<Switch>
				<Route
					path="/"
					exact
					render={(props) => {
						<BucketList
							list={this.state.list}
							history={this.props.history}
						/>
					}}
				/>
				<Route path="/detail" component={Detail}/>
				<Route component={NotFound}/>
			</Switch>
			...
		</div>
	);
}
  • NotFound 컴포넌트를 주소 없이 연결함
  • Switch는 첫번째로 매칭되는 path를 가진 컴포넌트를 렌더링함

SCSS

  • CSS를 편하게 쓸 수 있도록 도와줌
  • node-sass 설치하기
yarn add node-sass@4.14.1 open-color sass-loader classnames
  • 중첩된 속성
div {
	p {
		color: #888888;
		font: {
			family: sans-serif;
			size: 14px;
		}
	}

	img {
		width: 400px;
	}
}
  • 상위 요소 이어쓰기 (& 사용)
div {
	background-color: green
	&:hover { background-color: blue }
}

.div {
	background-color: green
	&_blue { background-color: blue }
}
  • 변수 사용
$defaultSize: 20px;
$className: blue;

p {
	font-size: #{$defaultSize};
	&.#{$className} {color: #{$className}}
}

styled-components

  • 컴포넌트에 스타일을 직접 입히는 방식
  • styled-components 패키지 설치하기
yarn add styled-components
  • class 이름 지을 필요 없어짐, 간단하고 직관적임
import React from 'react';
import styled from 'styled-components';

function App() {
	return (
		<div className="App">
			<MyStyled bgColor={"red"}>hello react!</MyStyled>
		</div>
	);
}

const MyStyled = styled.div`
	width: 50vw;
	min-height: 150px;
	padding: 10px;
	border-radius: 15px;
	color: #fff;
	&:hover {
		background-color: #ddd;
	}
	background-color: ${(props) => (props.bgColor ? "red" : "purple")};
`;

export default App;

 


가상돔

DOM (Document Object Model)

  • HTML의 단위 하나하나를 객체로 생각하는 모델
  • 트리 구조를 가짐
  • 수정이 일어날 때마다 모든 DOM을 조회해서 수정할 것을 찾고 전부 수정하면 연산이 많이 일어남 → 가상돔의 필요성

가상돔 (Virtual DOM)

  • 메모리 상에서 돌아가는 가짜 DOM
  • 기존 DOM과 새로 그린 DOM을 비교해서 바뀐 부분만 수정

라이프 사이클

  • 컴포넌트가 렌더링을 준비하는 순간 ~ 페이지에서 사라질 때까지

  • 컴포넌트 생성수정(업데이트)제거
  • 생성 : 처음으로 컴포넌트를 불러오는 단계
  • 수정(업데이트) : 사용자의 행동으로 데이터가 바뀌거나 부모 컴포넌트가 렌더링할 때 발생
  • ex. props가 바뀔 때, state가 바뀔 때, 부모 컴포넌트가 업데이트 되었을 때 (리렌더링), 강제로 업데이트했을 때 (forceUpdate())
  • 제거 : 페이지를 이동하거나 사용자의 행동으로 인해 컴포넌트가 화면에서 사라지는 단계

라이프 사이클 함수

  • constuctor() : 생성자 함수, 컴포넌트가 생성되면 가장 처음 호출되는 함수
  • render() : 컴포넌트의 모양을 정의하는 함수
  • componentDidMount() : 첫번째 렌더링을 마친 후에 실행, 리렌더링시에는 호출되지 않음 (컴포넌트가 화면에 나타나는 것 → 마운트(Mount) 한다고 표현함)
  • componentDidUpdate(prevProps, prevState, snapshot) : 리렌더링을 완료한 후 실행되는 함수, prevProps/prevState는 업데이트 전 props/state
  • componentWillUnmount() : 컴포넌트가 DOM에서 제거될 때 실행하는 함수, 컴포넌트에 이벤트 리스너를 등록했다면 반드시 해제해야 함
import React from 'react';

class LifecycleEx extends React.Component {
	//생성자 함수
	constructor(props) {
		super(props);
		this.state = {
			cat_name: '나비',
		};
		console.log('in constructor!');
	}

	componentDidMount() {
		console.log('in componentDidMount!');
	}

	componentDidUpdate(prevProps, prevState) {
		console.log(prevProps, prevState);
		console.log('in componentDidUpdate!');
	}

	componentWillUnmount() {
		console.log('in componentWillUnmount!');
	}

	render() {
		return (
			<div>
				<h1>고양이 이름은 {this.state.cat_name}</h1>
				<button onClick={this.changeCatName}>고양이 이름 바꾸기</button>
			</div>
		);
	}
}

export default LifecycleEx;
  • 라이프 사이클 함수는 클래스형 컴포넌트에서만 사용 가능함

Ref

  • 리액트에서 DOM 요소를 가져올 때 사용
class App extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			list: ['영화관 가기', '매일 책읽기', '수영 배우기']
		};
		this.text = React.createRef(); //ref 선언하기
	}

	componentDidMount() {
		console.log(this.text);
		console.log(this.text.current);
	}

	render() {
		return (
			<div className="App">
				...
				<input type="text" ref={this.text}/>
			</div>
		);
	}
}

 


클래스형 컴포넌트 state 관리

  • setState() : 클래스형 컴포넌트의 state를 업데이트할 때 사용하는 함수
  • 네모 추가/삭제 예제
더보기
import React from 'react';

class App extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 3,
		};
	}

	componentDidMount() {}

	addNemo = () => {
		this.setState({count: this.state.count + 1});
	};

	removeNemo = () => {
		if (this.state.count > 0) {
			this.setState({count: this.state.count - 1});
		} else {
			window.alert('네모가 없습니다');
		}
	};

	render() {
		const nemo_count = Array.from({length: this.state.count}, (v, i) => i);
		console.log(nemo_count);
		return (
			<div className="App">
				{nemo_count.map((num, idx) => {
					return (
						<div
							key={idx}
							style={{
								width: '150px',
								height: '150px',
								backgroundColor: '#ddd',
								margin: '10px',
							})
						></div>
					);
		}}}

			<div>
				<button onClick={this.addNemo}>하나 추가</button>
				<button onClick={this.removeNemo}하나 빼기</button>
			</div>
		);
	}
}

export default App;
  • addNemo, removeNemo에서 count 상태를 수정함

 

함수형 컴포넌트에서 state 관리

  • 함수형 컴포넌트는 state가 없음
  • 그러나 react hooks를 사용하면 state를 가질 수 있음
  • useState()를 이용하여 state 관리
  • 네모 추가/삭제 예제
더보기
import React from "react";

const Nemo = (props) => {
	//setCount로 count라는 state 값을 수정함
  const [count, setCount] = React.useState(3); //useState로 초깃값 설정

  const addNemo = () => {
    setCount(count + 1); //setCount로 count 1 증가
  };

  const removeNemo = () => {
    setCount(count > 0 ? count - 1 : 0); //setCount로 count 1 감소
  };

  const nemo_count = Array.from({ length: count }, (v, i) => i);
  return (
    <div className="App">
      {nemo_count.map((num, idx) => {
        return (
          <div
            key={idx}
            style={{
              width: "150px",
              height: "150px",
              backgroundColor: "#ddd",
              margin: "10px",
            }}
          >
            nemo
          </div>
        );
      })}

      <div>
        <button onClick={addNemo}>하나 추가</button>
        <button onClick={removeNemo}>하나 빼기</button>
      </div>
    </div>
  );
};

export default Nemo;

 


이벤트 리스너 (Event Listener)

  • 사용자가 어떤 행동(이벤트)을 하는지 지켜보고 알려줌
  • 마우스 클릭, 터치, 마우스 오버, 키보드 누름 등의 이벤트
  • onClick처럼 엘리먼트에 직접 넣어주는 방법 or addEventListener을 통해 추가하는 방법이 있음

클래스형 컴포넌트에서 Event Listener 구독하기

  • DOM 요소가 있어야 이벤트 발생하는지를 지켜볼 수 있음 → componentDidMount()에서 작성
  • addEventListener() : 이벤트 등록 함수
  • removeEventListener() : 이벤트 제거 함수
constructor(props) {
	super(props);
	this.state = {
		count: 3,
	};
	this.div = React.createRef();
}

hoverEvent = (e) => {
	console.log(e.target);
	if (e.target.className === "App") {
		e.target.style.background = "#eee";
	}
}

componentDidMount() {
	console.log(this.div);
	//addEventListener로 이벤트 등록
	this.div.current.addEventListener("mouseover", this.hoverEvent);
}

componentWillUnmount() {
	//컴포넌트가 제거될 때 이벤트 지우기
	this.div.current.removeEventListener("mouseover", this.hoverEvent);
}

return (
	<div className="App" ref={this.div}>
		<Nemo/>
	</div>
);

웹의 기초 개념

웹의 동작 개념

HTML을 받는 경우

  • 브라우저 : 서버 API에 요청을 보내서 받은 HTML 파일을 그려줌

데이터만 받는 경우

  • JSON 형식의 데이터를 받아와서 페이지에 끼우게 됨

클라이언트와 서버

  • 클라이언트 : 우리가 웹사이트를 보는 도구
  • 서버 : 우리가 보는 웹사이트에 뿌려줄 것을 만들어 클라이언트에 전달해주는 역할

 

서버리스(serverless)

  • 서버의 사양, 네트워크 설정 등이 되어있는 서버를 빌려서 사용함
  • 직접 서버를 구축하기 위한 인프라 작업을 하지 않아도 됨

 

DOM (Document Object Model)

  • HTML 단위 하나하나를 객체로 생각하는 모델
  • 트리구조

 


 

Javascript 기초

Class

  • 객체를 정의하기 위한 상태, 함수로 구성
class Cat {
  constructor(name) { //생성자
		this.name = name; 
	}

	showName(){ //함수
		return this.name;
	}
}

class MyCat extends Cat {
  constructor(name, age) {
		super(name); //부모 클래스의 생성자
		this.age = age; 
	}
	
	showName(){ //오버라이딩
		return '내 고양이 이름은 '+super.showName()+'입니다.';
	}
	
	showAge(){
		console.log('내 고양이는 '+this.age+'살 입니다!');
	}
}

let my_cat = new MyCat('perl', 4);
my_cat.showName();
my_cat.showAge();

 

변수 선언

  • var : 함수 단위 변수
  • let : block 단위 변수 (재할당 가능)
  • const : block 단위 변수 (재할당 불가)
function scope(){
	const a = 0;
	let b = 0;
	var c = 0;
	
	if(a === 0){ //block 기준 : 중괄호 {}
		const a = 1;
		let b = 1;
		var c = 1;
		console.log(a, b, c);
	}

	console.log(a, b, c);
}
  • 실행 결과 c만 1로 변하고 a, b는 바뀌지 않음

 

=, ==, ===

  • = : 변수에 값 할당
  • == : 동등 연산자, 유형을 비교하지 않는 등차
  • === : 일치 연산자, 유형도 비교하는 등차
💡
0 == "0" → true 반환 0 === "0" → false 반환

 

Spread 연산자

  • 객체 안에 있는 요소들을 객체 바깥으로 꺼내주는 역할
  • 점 3개 (...) → Spread 문법
let array = [1,2,3,4,5];
let new_array = [...array];

 

Array 내장 함수

1. map

  • 배열에 속한 항목을 변환할 때 사용
  • 변환한 값을 새로운 배열로 만들어줌 (원본 배열은 변화 X)
const array_num = [0,1,2,3,4,5];
const new_array = array_num.map((array_item) => {
	return array_item + 1;
});

2. filter

  • 특정 조건을 만족하는 항목만 골라서 새로운 배열로 만들어줌
const array_num = [0,1,2,3,4,5];
const new_array = array_num.filter((array_item) => {
	return array_item > 3;
});

3. concat

  • 배열과 배열을 합치거나, 배열에 특정한 값을 추가해주는 함수
const array_num01 = [0,1,2,3];
const array_num02 = [3,4,5];
const merge = array_num01.concat(array_num02);
  • concat은 중복 항목을 제거해주지 않음 → Set을 이용하면 가능
const array_num01 = [0,1,2,3];
const array_num02 = [3,4,5];
const merge = [...new Set([...array_num01, ...array_num02])];

 

  • Quiz_Array 내장 함수
    1. 아래 for문을 map으로 바꾸기
    const animals = ["강아지", "고양이", "햄스터", "강아지", "고양이", "고양이", "토끼"];
    
    let count = 0;
    for (let i = 0; i < animals.length; i++) {
    	let animal = animals[i];
    	if (animal === "고양이") {
    		count += 1;
    	}
    }
    console.log(count);
    • 정답
      const animals = ["강아지", "고양이", "햄스터", "강아지", "고양이", "고양이", "토끼"];
      
      let count = 0;
      animals.map((animal) => {
      	if (animal === "고양이") {
      		count += 1;
      	}
      });
      console.log(count);

     

    1. 아래 코드를 filter 이용하도록 바꾸기
    const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];
    
    let cats = [];
    for (let i = 0; i < animals.length; i++) {
    	let animal = animals[i];
    	if (animal.indexOf("고양이") !== -1) {
    		cats.push(animal);
    	}
    }
    console.log(cats);
    • 정답
      const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];
      
      let cats = [];
      const cats = animals.filter((animal) => {
      	return animal.indexOf("고양이") !== -1;
      });
      console.log(cats);

       

     

    1. 아래 두 배열을 하나로 합치기
    const dogs = ["검은 강아지", "노란 강아지", "흰 강아지"];
    const cats = ["검은 고양이", "복슬 고양이", "노란 고양이"];
    
    const animals = dogs.concat(cats);
    console.log(animals);

     

    1. 딸기는 몇 개일까? (map 이용)
    let fruit_list = ['사과','감','감','배','포도','포도','딸기',
    '포도','감','수박','딸기']
    
    let count = 0;
    for (let i = 0; i < fruit_list.length; i++) {
    	let fruit = fruit_list[i];
    	if (fruit == '딸기') {
    		count += 1;
    	}
    }
    console.log(count);
    • 정답
      let fruit_list = ['사과','감','감','배','포도','포도','딸기',
      '포도','감','수박','딸기']
      
      let count = 0;
      fruit_list.map((fruit) => {
      	if (fruit == "딸기") count += 1;
      });
      console.log(count);

       

     

 


 

React 프로젝트 만들기

NVM(Node Version Manager)

  • Node.js의 버전 관리자
  • 다양한 버전을 마음대로 골라서 설치할 수 있게 해주는 툴
  • 설치된 NVM 버전 확인
nvm --version

 

Node.js 설치하기

  • 설치하기
nvm install [설치할 버전]

 

  • 정상적으로 설치되었는지 확인
nvm ls //nvm으로 설치한 노드 버전 리스트 확인
node -v //노드 버전 확인

 

  • 사용 중인 노드 버전 바꾸기
nvm use [사용할 노드 버전]

 

Yarn 설치하기

  • NPM(Node Package Manager) : 무수히 많은 third-party 패키지를 활용할 수 있게 해줌
  • NPM과 유사한 도구 : Yarn→ 둘 다 프론트엔드 의존성을 관리하기 위한 패키지 매니저
  • → 이미 완성된 패키지를 가져다 쓰기 편리하도록 도와줌
  • NPM으로 Yarn 설치하기
//npm install [옵션] [설치할 패키지 이름]
npm install -g yarn
//-g : 컴퓨터 전체에서 쓸 수 있게 하는 옵션
yarn -v //yarn 버전 확인

 

  • Yarn으로 패키지 설치하기
yarn add [옵션] [설치할 패키지 이름]

 

CRA(create-react-app)으로 시작하는 리액트

  • Yarn으로 CRA 설치
yarn add global create-react-app

 

  • 리액트 프로젝트 만들기
//yarn create react-app [프로젝트 이름]
yarn create react-app week-1

 

  • 리액트 앱(프로젝트) 실행하기
//프로젝트 폴더로 이동한 후 실행
cd week-1
yarn start

 


 

JSX

JSX란?

  • 리액트에서 뷰를 그리는 방법→ JSX 문법을 이용해서 React 엘리먼트 생성, DOM으로 렌더링시켜서 그리기
  • 자바스크립트 안에서 HTML 태그 같은 마크업을 넣어 뷰 작업을 할 수 있음
const start_half = <div>
	<h1>안녕하세요</h1>
</div>;

 

JSX 규칙

  1. 태그를 반드시 닫기
  1. 반드시 1개의 엘리먼트를 반환하기
return (
	<div className="App">
		<p>안녕하세요</p>
		<input type="text"/>
	</div>
);
  1. JSX에서 자바스크립트 값을 가져오기 위해서는 중괄호를 사용
const cat_name = 'perl';
return (
	<div>
		hello {cat_name}!
	</div>
);
  1. 태그 내에서 클래스 명을 정할 때 class 대신 className을 사용
<div className="App">
  1. 인라인으로 style 넣기
//중괄호를 두 번 쓰는 이유 -> 딕셔너리도 자바스크립트이기 때문
<p style={{color:'orange', fontSize:'20px'}}>orange</p>

//또는 스타일 딕셔너리를 변수로 선언해서 사용
function App() {
	const style = {
		color: 'orange',
		fontSize: 20px'
	};

	return (
		<div className="App">
			<p style={styles}>orange</p>
		</div>
	);
}

 


 

Component

State

  • Component가 가지고 있는 데이터
  • 한 컴포넌트에서만 사용하는 정보를 주로 넣어놓고 생성, 수정하는 데이터

 

Props

  • Component가 부모 Component로부터 받아온 데이터
  • Props로 받은 데이터는 수정할 수 없다

 

함수형 Component

BucketList.js

import React from 'react';

const BucketList = (props) => {
	return (
		<div>
			버킷 리스트
		</div>
	);
}

/*
//또는 이렇게 작성할 수 있다.
function BucketList(props) {
	return (
		<div>버킷 리스트</div>
	);
}
*/

export default BucketList;
  • return을 통해 Component가 뿌려줄 UI 엘리먼트를 반환
  • 함수형 Component를 export하여 다른 컴포넌트에서 이 Component를 사용할 수 있도록 함

 

App.js

import React from 'react';
import BucketList from './BucketList';

function App() {
	return (
		<div className="App">
			<h1>내 버킷리스트</h1>
			<BucketList/>
		</div>
	);
}

export default App;

 

클래스형 Component

💡
class 클래스명 extends React.Component

App.js

import React from 'react';
import BucketList from './BucketList';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state = { //state 정의
			list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
		};
	}

	render() { //render 함수 안에 리액트 엘리먼트 넣기
		return (
			<div className="App">
				<h1>내 버킷리스트</h1>
				<BucketList/>
			</div>
		);
	}
}

export default App;

 

Component간의 데이터 전달

  • 함수형 Component에는 state가 없음 (React hook을 이용하면 state 사용 가능)
💡
<컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/>

App.js

render() {
	return (
		<div className="App">
			<h1>내 버킷리스트</h1>
			<BucketList list={this.state.list}/>
		</div>
	);
}

 

앱개발 종합반 수강을 마치며...

앱개발 종합반을 수강하며 제작한 나만의 꿀팁 앱

수강 계기 및 목적

나는 현재 컴공 4학년에 재학중이고, 앱 또는 웹 개발 프론트엔드 개발자가 되어야겠다고 진로 방향을 생각하고 있었다.

마침 1학기 종강도 했겠다, 방학 때 새롭게 공부를 시작해볼까 했던 터라 "앱개발 종합반"을 신청했다.

 

현재 한이음 프로젝트는 안드로이드 스튜디오를 이용해서 개발을 진행하고 있는데

앱개발 종합반의 강의 계획표를 보니 리액트 네이티브를 다룬다고 나와있었다.

 

리액트 네이티브에 대해 찾아보니 안드로이드/iOS를 모두 지원하는 크로스 플랫폼 앱을 제작할 수 있는 프레임워크라고 한다.

만약 나중에 앱 개발자가 된다면 두 개의 플랫폼을 모두 지원하는

크로스 플랫폼 앱 개발을 하는 것이 유리하고 생산적일거라는 생각이 들었다.

 

또한, 프론트엔드 개발자가 되기 위해서는 자바스크립트에 익숙해지고 잘 다룰 줄 알아야한다고 들었다.

앱개발 종합반 1주차 강의에서 자바스크립트의 기초를 잡고

2주차부터 리액트 네이티브 수업이 진행된다고 하기에 (리액트 네이티브가 자바스크립트를 기반으로 하기 때문)

자바스크립트 찐찐 기초밖에 모르는 상태에서 해당 강의를 수강함으로써

자바스크립트와 친숙해지고 그것을 기반으로 하는 프레임워크를 배우고자 하였다.

 

 

배운 점

올해 캡스톤디자인 프로젝트를 하면서 자바스크립트를 조금 다루긴 했으나,

잘 모르는 상태에서 이곳저곳에서 코드를 긁어오니 너무 어렵게만 느껴졌고

자바스크립트에 대한 약간의 거부감(?)이 드는 상태였다.

 

앞서 말한 것처럼 앱개발 종합반 1주차에는 자바스크립트의 기초를 배웠다.

리스트와 딕셔너리 자료구조를 배우고, 이것의 복합 구조가 JSON 데이터의 구조라는 것을 알게 되었다.

JSON이 무엇인지 모르는 상태에서 얼핏 봤을 땐 구조가 복잡해보여서 거부감이 들었는데

강의에서 쉽게 접근할 수 있도록 설명을 해주셔서 그런지 "별거 아니네!!" 라며 자신감을 갖게 되었다ㅋㅋㅋ

그리고 강의명이 앱개발 종합반인만큼, 앱개발 시 자주 사용되는 자바스크립트 문법을 따로 알려주셔서 좋았다.

 

2주차에는 본격적으로 리액트 네이티브 프로젝트를 생성하고 Expo를 사용해보았다.

안드로이드 스튜디오에서 개발을 할 때는 수정사항이 있을 때마다

매번 새롭게 빌드하고 실행해야하기 때문에 꽤나 번거로운 작업이 필요한데

Expo 클라이언트 앱을 이용해서 수정사항이 즉시 화면에 반영되는걸 보고 굉장히 편리하다는 생각이 들었다.

그리고 Expo 자체에서 제공해주는 기능들이 많아서 유용하게 활용할 수 있을 것 같았다.

리액트의 태그는 안드로이드 스튜디오의 레이아웃 xml이, 스타일은 CSS가 생각이 나서 어렵지 않게 받아들일 수 있었다.

 

3주차가 앱개발 5주간의 수업 중 가장 어렵게 느껴졌던 강의이다.

리액트의 기본 개념인 컴포넌트, 속성, 상태 중 상태!!가 처음에는 되게 추상적으로 느껴져서 어려웠는데

앱개발 종합반 수업에서 계속 다루는 나만의 꿀팁 앱 예제를 통해 다행히 이해할 수 있었다.

개인적으로 스파르타코딩클럽 강의에서 제공하는 코드스니펫 기능! 너무 좋은거 같다.

코드스니펫이 없었다면 강의에서 다룬 코드를 일일이 타자치고 있었을텐데

전공 강의를 4년간 들으면서 느낀건데, 무념무상으로 이게 뭔지도 모른채 하염없이 타자치고 있는 것보단

차라리 복붙을 해놓고 해당 코드에 대한 이해를 하는 것이 더 낫기 때문!

 

4주차에는 날씨 서버 외부 API를 불러와서 적용하는 방법을 배웠다.

내가 구현하고자 하는 기능을 구글링해보면 해당 기능 관련 API를 쉽게 찾을 수 있으므로

앞으로 개발할 때 다양한 API와 라이브러리를 활용하면 좋겠다는 생각이 들었다.

그리고 Google Firebase를 앱에 연결하고 스토리지, 데이터베이스를 이용해서 서버리스 아키텍처를 구현해보았다.

 

대망의 5주차! 지금까지 배운 내용을 바탕으로 앱을 개발하고나서

최종적으로 배포하기 위해 구글 광고를 앱 화면에 추가하고 구글 플레이스토어에서 배포하는 방법을 배웠다.

관련 자료를 하나하나 찾아가면서 구현하기에는 꽤나 번거로웠을텐데, 강의를 통해 처음부터 끝까지 차근차근 방법을 알 수 있어서 좋았다.

리액트 네이티브를 더 깊이 학습해서 이번에 배운 내용을 바탕으로 추후에 꼭 앱을 출시해보고 싶다~ 

 

향후 목표

수업 시간에 사용되었던 예제인 '나만의 꿀팁' 앱에 다양한 기능을 추가하여 완성도를 높일 것이고

이것과 별도로 앱개발 종합반에서 배운 내용들을 바탕으로 개인 프로젝트 '스케줄 앱'을 완성해볼 것이다!

 

소감

앱개발 종합반에서 자바스크립트와 친숙해지고 리액트를 기반으로 한 리액트 네이티브를 다뤄보았다.

깔끔한 강의 영상과 강의 자료(노션, pdf)를 보며 학습할 수 있어서 만족스러웠고

슬랙 채널 또는 주말에 있는 즉문즉답 시간을 활용하여 Q&A가 이루어지기 때문에

강의 내용 중 이해가 안되는 부분이나 그 외에 각자 공부하다가 막히는 부분을 물어볼 곳이 있다는 점이

스파르타코딩클럽 강의의 가장 큰 장점이지 않을까 싶다.

 

 

이상 앱개발 종합반 완주 후기 끝~

애드몹(AdMob) 설정

  • 앱 내에 구글 배너광고를 쉽게 붙일 수 있도록 도와줌
  • 수익 현황 알려줌
  • 일정 수익 이상이 되면 환전 가능

애드몹 광고 종류

  1. 배너 : 앱 레이아웃 일부를 차지하는 광고
  2. 전면 : 전체 페이지 광고 형식
  3. 리워드 : 광고를 본 사용자에게 리워드 제공
  4. 네이티브 고급 광고 : 맞춤설정 광고 (커스터마이징)

애드몹 설치하기

  • Expo에서 구글 애드몹을 지원해준다.
expo install expo-ads-admob
  • app.json 파일에 다음과 같이 추가한다.
"ios": {
      "supportsTablet": true,
      "buildNumber": "1.0.0",
      "bundleIdentifier": "com.myhoneytip.yewon",
      "config": {
        "googleMobileAdsAppId": ""
      }
  },
"android": {
      "package": "com.myhoneytip.yewon",
      "versionCode": 1,
      "config": {
        "googleMobileAdsAppId": ""
      }
  }
  • googleMobileAdsAppId에 대해서는 자신이 애드몹에 추가한 앱 ID를 입력한다.
  • 안드로이드, iOS 모두 앱을 추가해야함

가로 배너 생성

  • 광고 단위 만들기 - 배너 선택

  • 광고 단위 이름 입력

  • MainPage.js에 다음 코드를 추가한다.
import {Platform} from 'react-native';
import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
} from 'expo-ads-admob';
<View>
{Platform.OS === 'ios' ? (
	<AdMobBanner
		bannerSize="fullBanner"
		servePersonalizedAds={true}
		adUnitID="iOS 광고 단위 아이디"
		style={styles.banner}
	/>
) : (
	<AdMobBanner
		bannerSize="fullBanner"
		servePersonalizedAds={true}
		adUnitID="Android 광고 단위 아이디"
		style={styles.banner}
	/>
)}
</View>
  • bannerSize : 배너 크기 조정
  • servePersonalizedAds : 사용자 맞춤 광고 제공 여부
  • adUnitID : 광고 단위 아이디
  • style을 이용하여 스타일 적용 가능

전면 배너 생성

  • Card.js
import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
} from 'expo-ads-admob';
export default function Card({content,navigation}){

    useEffect(()=>{
        Platform.OS === 'ios' ? AdMobInterstitial.setAdUnitID("광고 단위 ID") : AdMobInterstitial.setAdUnitID("광고 단위 ID")

        AdMobInterstitial.addEventListener("interstitialDidLoad", () =>
            console.log("interstitialDidLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidFailToLoad", () =>
            console.log("interstitialDidFailToLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidOpen", () =>
            console.log("interstitialDidOpen")
        );
        AdMobInterstitial.addEventListener("interstitialDidClose", () => {
              //광고가 끝나면 다음 코드 줄이 실행!
            console.log("interstitialDidClose")
            navigation.navigate('DetailPage',{idx:content.idx})
        });
    },[])
    const goDetail = async () =>{

      await AdMobInterstitial.requestAdAsync({ servePersonalizedAds: true});
      await AdMobInterstitial.showAdAsync();
    }

    return(
        <TouchableOpacity style={styles.card} onPress={()=>{goDetail()}}>
            <Image style={styles.cardImage} source={{uri:content.image}}/>
            <View style={styles.cardText}>
                <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                <Text style={styles.cardDate}>{content.date}</Text>
            </View>
        </TouchableOpacity>
    )
}

앱을 배포하기 위해 필요한 것들

  1. 앱 로고
  2. 스플래시 스크린
  3. 앱 마켓에 올릴 설명 이미지
  4. 앱 버전 관리, 안드로이드, iOS 인증서 관리 등

스플래시 스크린

  • assets 폴더 안에 있는 splash.png 이미지 수정

로고 준비

  • assets 폴더 안에 있는 icon.png 이미지 수정

최종 앱 파일 생성

app.json

  • version / ios - buildNumber / android - versionCode → 매번 다르게
expo build:android

permission 코드

"permissions": ["ACCESS_FINE_LOCATION", "ACCESS_COARSE_LOCATION"]

 


내가 제작한 앱에 광고를 넣는 방법과 최종적으로 구글 플레이스토어에 배포하는 방법을 배웠다.

 

광고는 구글 애드몹(Admob) 서비스를 이용하여 앱 메인 화면 하단에 배너 광고를 넣고,

각 꿀팁을 클릭해서 상세 페이지로 넘어가기 전에 전면 광고가 나오도록 했다.

 

애드몹 신청 과정이 꽤나 길긴 했으나, 한번 승인받은 이후에

광고 단위를 새롭게 만드는 방법이나 그것을 앱에 적용하는 방법은 어렵지 않았다.

 

구글 플레이스토어에 배포하기 위해 apk 파일을 만들고 등록하는 과정도 직접 따라해봤기 때문에

추후에 나만의 앱을 제작한다면 이번 수업으로 배운 구글 광고 추가와 배포하는 방법을 적용하면 될 것 같다!

 

 

앱 안에서 나오는 배너 광고와 전면 광고의 모습이다

날씨 서버 외부 API 사용하기

  • API 유형
    1. 서버가 제공하는 도메인 사용
    1. 서버가 만들어놓은 함수 사용
  • 이번에 사용할 날씨 서버 API → 외부 API를 도메인 형태로 요청하는 방식
  • 다음 순서로 API를 통해 날씨를 가져올 것
    1. 현재 위치(좌표) 데이터를 가져오기
    1. 위치 데이터를 이용해서 현재 위치 날씨 데이터 가져오기

 

위치 데이터 가져오기

💡
expo-location 도구를 활용할 것!

expo-location 설치하기

expo install expo-location

 

코드 작성

import * as Location from "expo-location";
  • expo-location은 Location 이라는 이름으로 사용할 것이다.

 

useEffect(()=>{
    setTimeout(()=>{
				...
        getLocation()
    },1000)
  },[])
  • 앱이 실행됐을 때 getLocation() 함수를 이용해 현재 위치 데이터를 가져온다.

 

const getLocation = async () => {
    try {
      //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
      await Location.requestPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      console.log(locationData)

    } catch (error) {
      //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
    }
  }
  • 외부 API 요청은 try/catch 이용

 

  • requestPermissionAsync() : 권한을 물어보는 팝업
  • 허용 선택 → 아래 코드 진행
  • getCurrentPositionAsync() : 현재 위치 좌표를 가져오는 함수

 

💡
함수 실행 순서를 정해주는 async / await
  • 무거운 기능(네트워크, 파일 시스템 접근 등)을 다룰 때 코드를 작성한 순서대로 실행하지 않을 수 있다.
  • 자바스크립트는 비동기 특성을 가지고 있어서 먼저 끝나는 함수부터 결과값을 가져온다.
  • 순차적으로 함수를 실행시키기 위해 async, await을 사용한다.
  • 함수 앞에 async를 붙이고 그 내부에서 사용될 함수 앞에 await를 붙인다.

 

날씨 데이터 가져오기

💡
openweathermap api 를 사용할 것!

 

axios 도구 설치

  • 서버가 제공하는 도메인 형식의 API를 사용하려면 axios 도구가 필요하다.
yarn add axios

 

코드 작성

import axios from "axios"
  • 설치한 axios 도구 import

 

const getLocation = async () => {
    try {
      await Location.requestPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      const latitude = locationData['coords']['latitude']
      const longitude = locationData['coords']['longitude']
      const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
      const result = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );

      const temp = result.data.main.temp; 
      const condition = result.data.weather[0].main

    } catch (error) {
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
    }
  }
  • expo-location을 이용하여 경도, 위도 가져오기
  • axios.get(도메인 주소)을 이용해서 날씨 데이터를 가져온다.
  • 도메인 주소에는 경도, 위도, 발급받은 키가 들어간다.

 

const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
})
  • 날씨 정보를 상태(state)로 관리

 

const getLocation = async () => {
	try{
		...
		setWeather({
			temp,condition
		 })
	}
	...
}
  • getLocation 함수 내에서 날씨 정보를 가져온 뒤 상태 반영

 

파이어베이스 (Firebase) 사용하기

서버리스 (serverless)

  • 직접 서버를 구현, 구성할 필요 없이 서버 기능을 제공하는 곳에서 서비스를 사용하면 된다.

 

파이어베이스 (Firebase) 란?

  • 구글에서 만든 서버리스 서비스
  • 실시간 데이터베이스, 인증, 스토리지, 호스팅 등 많은 기능 제공
  • 이용 절차
    1. 파이어베이스 가입하기
    1. 파이어베이스 프로젝트 생성
    1. 사용할 파이어베이스 서비스 활성화

 

파이어베이스를 앱에 연결하기

  • 자바스크립트(웹 개발 언어)로 리액트 네이티브 프로젝트 진행 중 → 웹 앱 선택
  • 스크립트를 복사하여 Firebase SDK 추가하기

 

expo 도구 설치

expo install firebase

 

firebaseConfig.js 생성

import firebase from 'firebase/app';

// 사용할 파이어베이스 서비스 주석을 해제해서 사용
//import "firebase/auth";
import "firebase/database";
//import "firebase/firestore";
//import "firebase/functions";
import "firebase/storage";

// Initialize Firebase
const firebaseConfig = {
  apiKey: "AIzaSyBMXK-Vpv5wW6TYEKxMdzLGsKn4UY-vNX0",
  authDomain: "sparta-myhoneytip-yewon.firebaseapp.com",
  projectId: "sparta-myhoneytip-yewon",
  storageBucket: "sparta-myhoneytip-yewon.appspot.com",
  messagingSenderId: "44731016366",
  appId: "1:44731016366:web:3e1b8f1f29b3440ddbb93f",
  measurementId: "G-E4WCLCL8P6"
};

if (!firebase.apps.length) {
    firebase.initializeApp(firebaseConfig);
}

export const firebase_db = firebase.database()

 

파일 스토리지 (storage)

  • 파이어베이스 사이트에서 Storage 생성
  • 폴더 생성, 파일 업로드 가능

 

리얼타임 데이터베이스

  • JSON 형태로 데이터를 저장/관리한다.
  • 리얼타임 데이터베이스 생성 후 data.json 파일 가져오기

 

전체 데이터 읽기

  • 리얼타임 데이터베이스 고유 주소를 이용해서 데이터를 가져온다.
  • 전체 데이터 읽는 코드 :
firebase_db.ref('/tip').once('value').then((snapshot) => {
   let tip = snapshot.val();
})
  • 실제 적용 :
import {firebase_db} from "../firebaseConfig"

useEffect(()=>{
    setTimeout(()=>{
				...
        firebase_db.ref('/tip').once('value').then((snapshot) => {
          console.log("파이어베이스에서 데이터 가져왔습니다!!")
          let tip = snapshot.val();
          setState(tip)
          setCateState(tip)
          getLocation()
          setReady(false)
    },1000)

    
  },[])
  • useEffect 함수 안에서 firebase 전체 데이터 읽어오기

 

특정 데이터 읽기

  • Card.js 수정
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',{idx:content.idx})}}>
  • 기존에는 navigate에 content를 넘겨주었음
  • content의 idx만 넘기도록 수정

 

  • DetailPage.js
import {firebase_db} from "../firebaseConfig"
...
useEffect(()=>{
		...
    const { idx } = route.params;
    firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
        let tip = snapshot.val();
        setTip(tip)
    });
},[])
  • route.params를 이용해 idx 받아서 firebase 데이터베이스에서 읽어오기

 

expo-contants

  • 사용자마다 고유한 ID값을 생성해준다.
expo install expo-constants
import Constants from 'expo-constants';

console.log(Constants.installationId)

 

데이터 쓰기

import {firebase_db} from "../firebaseConfig"
import Constants from 'expo-constants';

const like = () => {
        const user_id = Constants.installationId;
        firebase_db.ref('/like/'+user_idx+'/'+ tip.idx).set(tip,function(error){
            console.log(error)
            Alert.alert("찜 완료!")
        });
    }
  • 팁 찜하기 버튼을 클릭하면 firebase 데이터베이스에 유저의 아이디와 팁 인덱스를 이용하여 데이터를 추가한다.

 


API를 이용해서 서버에서 날씨 정보 가져오기!

expo-location을 이용해서 현재 위치를 받아오고

openweathermap api를 이용해서 현재 내 위치의 날씨 정보를 가져온다.

두번째 사진 오른쪽 상단에 실제 날씨 정보가 출력된 것을 확인할 수 있었다.

 

리액트와 expo, 그외 기타 사이트에서 다양한 API를 제공해줘서 앱 개발하는데 있어서 매우 편리한 것 같다.

API를 활용하니 생각보다 내 위치 및 날씨 데이터를 가져오는 절차가 간단했다!

앞으로 앱을 개발할 때 날씨 정보 뿐만 아니라 다양한 API를 찾아보고 활용하도록 해야겠다.

 

구글 파이어베이스 DB를 이용하여 팁 목록과 나의 찜 목록을 다룰 수 있었다.

사진은 4주차 숙제로 구현한 팀 찜하기 기능과 찜 목록이다.

 

파이어베이스는 한이음 프로젝트 하면서 이미 접해본 적이 있기 때문에 낯설지 않았다.

안드로이드 스튜디오에서는 파이어베이스와 연동하기 위한 절차가 꽤 길었는데 (파이어베이스 sdk 파일 추가, gradle 파일에 dependecy 추가 등등..)

리액트 네이티브는 웹앱 (자바스크립트를 기반으로 함) 이라 그런지 간단히 스크립트만 복붙하여 연동을 마칠 수 있었다.

 

대신 DB에서 데이터를 삭제할 때 함수를 잘못 작성했다가 console.log가 무한 반복된다거나 하는 오류들을 겪어서

다시 그런 실수를 반복하지 않도록 삭제할 때 작성할 코드를 조심히 작성해야겠다는 생각이 들었다.

remove 함수를 이용해서 DB 데이터를 삭제하는 방법은 공식 문서를 참고하여 이해했다.

( https://firebase.google.com/docs/reference/js/firebase.database.Reference?authuser=2#remove )

 

이제 리액트 네이티브에서 파이어베이스를 다루는 방법도 알게 되었으니

내 개인 프로젝트로 만들고 있는 일정 관리 앱에도 유용하게 사용할 수 있을 것 같다!

+ Recent posts