ㅇㅇㅈ Blog

프론트엔드 수행중

0%

React-BootStrap Nav Link

BootStrap Nav 링크에 React-Router-Dom NavLink 적용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { Navbar, Container } from 'react-bootstrap';

const NavBarElement = () => {
return (
<Navbar collapseOnSelect expand='lg' bg='dark' variant='dark'>
<Container>
<Navbar.Brand href='/'>React-Bootstrap</Navbar.Brand>
</Container>
</Navbar>
);
};

export default NavBarElement;

이런식으로 부트스랩에서 제공하는 컴포넌트로 nav를 작성하면 href 가 기본적으로 되어 있는데
이걸 to='/' 이런식으로 바꾸면 제대로 작동하지가 않는다
그렇다고 href에 주소를 그냥 작성하면 새로고침이 일어나게 된다

방법 1

그냥 react-router-dom의 navlink를 사용하고 클래스로 bootstrap을 적용하는 것
그럼 페이지를 이동해도 새로고침이 일어나지 않는다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { NavLink } from 'react-router-dom';
import { Navbar, Container } from 'react-bootstrap';

const NavBarElement = () => {
return (
<Navbar collapseOnSelect expand='lg' bg='dark' variant='dark'>
<Container>
<NavLink className='navbar-brand' to='/'>
React-Bootstrap
</NavLink>
</Container>
</Navbar>
);
};

export default NavBarElement;

방법 2

as 를 통해 NavLink를 상속 받는것

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { NavLink } from 'react-router-dom';
import { Navbar, Container } from 'react-bootstrap';

const NavBarElement = () => {
return (
<Navbar collapseOnSelect expand='lg' bg='dark' variant='dark'>
<Container>
<Navbar.Brand as={NavLink} to='/'>
React-Bootstrap
</Navbar.Brand>
</Container>
</Navbar>
);
};

가상 키보드

1
2
3
$ npm i -D webpack webpack-cli webpack-dev-server

$ npm i -D terser-webpack-plugin //js 압축 플러그인

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const path = require('path'); // 절대 경로
const TerserWebpackPlugin = require('terser-webpack-plugin');

module.exports = {
entry: './src/js/index.js', // 진입점
output: {
filename: 'bundle.js', // 번들파일 이름
path: path.resolve(__dirname, './dist'), // 번들될 파일 생성 경로
clean: true, // 번들하고 나서 clean
},
devtool: 'source-map', // 빌드한 파일과 원본파일을 서로 연결시켜주는 기능
mode: 'development', // js,css,html 난독화 해주냐는 차이
optimization: {
minimizer: [new TerserWebpackPlugin()],
},
};

html 관련 설정 모듈

1
$ npm i -D html-webpack-plugin

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const path = require('path');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
clean: true,
},
devtool: 'source-map',
mode: 'development',

//plugin 추가

plugins:[
new HtmlWebpackPlugin({
title:'keyboard', // 브라우저의 title
template:'./index.html',
inject:'body' // 번들할때 js파일을 head에 넣을 건냐 body에 넣을거냐
favicon: './favicon.ico'
})
]

optimization: {
minimizer: [new TerserWebpackPlugin()],
},
};

css 관련 설정 모듈

1
$ npm i -D mini-css-extract-plugin css-loader css-minimizer-webpack-plugin

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const path = require('path');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
clean: true,
},
devtool: 'source-map',
mode: 'development',

plugins: [
new HtmlWebpackPlugin({
title: 'keyboard',
template: './index.html',
inject: 'body',
favicon: './favicon.ico',
}),

// plugins 에 추가

new MiniCssExtractPlugin({
filename: 'style.css',
}),
],

// module 추가

module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
optimization: {
minimizer: [new TerserWebpackPlugin(), new CssMinimizerPlugin()],
},
};
Read more »

그 동안 배운걸 토대로
예전에 vue로 만들었던 영화검색 앱을 리액트로 만들어 보자..

컴포넌트 구성

App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// App.js

import Home from './routes/Home';
import Header from './components/Header';
import Movie from './routes/Movie';
import { BrowserRouter, Route, Routes, Router } from 'react-router-dom';

function App() {
return (
<>
<BrowserRouter>
<Header />
<Routes>
<Route path='/' element={<Home />} />
<Route path='movie' element={<Movie />} />
</Routes>
</BrowserRouter>
</>
);
}

export default App;

<Header/> 컴포넌트엔 페이지로 이동하는 버튼들이 있고
그 아래로 routes에서 각 페이지들을 보여주게 된다

Header.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Header.jsx

import React from 'react';
import { Link } from 'react-router-dom';
import './Header.scss';

const navigations = [
{
name: 'Home',
href: '/',
},
{
name: 'Movie',
href: 'movie',
},
];
export default function Header() {
return (
<header className='header'>
<div className='nav nav-pills'>
<div className='nav-item'>
{navigations.map((nav) => {
return (
<Link
to={nav.href}
key={nav.name}
className='btn btn-primary'
style={{ marginRight: 15 }}
>
{nav.name}
</Link>
);
})}
</div>
</div>
</header>
);
}

헤더 컴포넌트에 버튼이 늘어 날 수도있으니 데이터를 따로 만들어 주었다.
react-rounter-dom에서 제공하는 Link 컴포넌트는 html의 a태그 같은 것.
a태그를 사용하여 링크를 걸면 페이지가 새로고침이 일어 나기 때문이다.
아직 없는 페이지에 대한 처리를 하지 못했다..

Home.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import MovieList from '../components/MovieList';
import Search from '../components/Search';
import Headline from '../components/Headline';

export default function Home() {
return (
<>
<div className='container'>
<Headline />
<Search />
<MovieList />
</div>
</>
);
}

일반적인 컴포넌트가 들어 있는 페이지

Search.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import React, { useState } from 'react';
import './Search.scss';
import { Form } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { _fetchMovie, fetchAsyncMovies } from '../store/features/searchSlice';

const filters = [
{
name: 'type',
items: ['movie', 'series', 'episode'],
},
{
name: 'number',
items: [10, 20, 30],
},
{
name: 'year',
items: (() => {
const years = [];
const thisYear = new Date().getFullYear();
for (let i = thisYear; i >= 1985; i--) {
years.push(i);
}
return years;
})(),
},
];

export default function Search() {
const [searchs, setSearchs] = useState({
title: '',
year: '',
type: '',
page: 10,
});
const dispatch = useDispatch();
const onChange = (e) => {
const { name, value } = e.target;
setSearchs({ ...searchs, [name]: value });
};
const onSubmit = (e) => {
e.preventDefault();
dispatch(fetchAsyncMovies(searchs));
};

return (
<>
<Form className='search--container' onSubmit={onSubmit}>
<Form.Control
type='text'
placeholder='Search Movies'
onChange={onChange}
name='title'
/>
{filters.map((select) => {
return (
<Form.Select
name={select.name}
key={select.name}
size='sm'
className='selects'
onChange={onChange}
>
{select.name === 'year' && (
<option value=''>{select.name}</option>
)}
{select.items.map((items) => {
return (
<option value={items} key={items}>
{items}
</option>
);
})}
</Form.Select>
);
})}
<button className='btn btn-primary'>Search</button>
</Form>
</>
);
}

Read more »

useEffect 복습

useEffect는 내가 특정 순간 혹은 상황? 에서 리렌더링을 일으키고 싶을때 사용 하는 훅
만약 다음과 같은 코드가 있다면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useState, useEffect } from 'react';
import './App.css';

function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const onClick = () => setCount((prev) => prev + 1);
const onChange = (event) => setKeyword(event.target.value);
console.log('Api 호출');

return (
<div className='App'>
<input type='text' onChange={onChange} value={keyword} />
<h1>{count}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}

export default App;

처음 App이 마운트되고 나면 API 호출이 콘솔에 찍힐 것이다
하지만 문제는 버튼이 클릭되거나, 인풋에 타이핑을 하는 순간에도 API 호출이 콘솔에 찍히게 된다

실제로 서버에서 API를 호출하는 코드가 있다고 가정하면, 불필요한 호출이 계속 일어나고 있다는 소리고 성능에 저하가 생길 수도 있다

이럴때 useEffect() 훅을 사용하게 된다

useEffect(callback, deps?)

useEffect는 2개의 매개변수를 갖는다

첫 번째는 우리가 실행할 함수,
두 번째는 배열타입인데 배열의 값으로는 안넣어줘도 되고, 변화를 주고자하는 state를 넣어 줄수도 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useState, useEffect } from 'react';
import './App.css';

function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const onClick = () => setCount((prev) => prev + 1);
const onChange = (event) => setKeyword(event.target.value);

useEffect(()=> {
console.log('i run only once.')
},[])

return (
<div className='App'>
<input type='text' onChange={onChange} value={keyword} />
<h1>{count}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}

export default App;

useEffect() 안에 작성된 콘솔은 처음 마운트 될때만 실행이 되고 그 이후에는
실행이 되지 않는다. 즉 인풋에의해 리렌더링이 되거나, 버튼에의해 리렌더링이 되더라도 다시 실행이 안된다는 뜻
useEffect()의 두번째 인자로 [] 빈 배열 값을 전달 했기 때문이다.
이는 우리가 렌더링 되는것을 바라볼 것이 비어있기때문에, 최초 마운트 될때 말고는 컴포넌트가 리렌더링 되더라도 다시 실행 되지 않는 것이다
만약 [] 을 없애고 함수만 전달한다면

1
2
3
useEffect(()=> {
console.log('i run only once.')
})

useEffect를 쓰지 않았을때와 같이 모든 렌더링에 반응해 실행된다..

그럼 배열안에 특정 state 값을 넣어준다면?

useEffect는 그 state를 바라보고, 변화가 생길때마다 함수를 실행하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const onClick = () => setCount((prev) => prev + 1);
const onChange = (event) => setKeyword(event.target.value);

useEffect(() => {
console.log('i run only click.');
}, [count]);
return (
<div className='App'>
<input type='text' onChange={onChange} value={keyword} />
<h1>{count}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}

export default App;

배열안에 count 를 넣었을때,
마운트 될때 최초로 한 번 실행 되었고,
클릭 2번에 반응을해 count의 상태가 2번 바뀌었으므로 다시 2번이 실행됨

인풋에는 반응을 하지 않았다

useEffect 는 여러번 사용 할 수 있고 배열안에도 여러 state값을 넣어 줄 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const onClick = () => setCount((prev) => prev + 1);
const onChange = (event) => setKeyword(event.target.value);

useEffect(() => {
console.log('i run only Once!');
}, []);
useEffect(() => {
console.log('i run only click.');
}, [count]);
useEffect(() => {
console.log('i run keyword');
}, [keyword]);
useEffect(() => {
console.log('i run keyword & click');
}, [count, keyword]);
return (
<div className='App'>
<input type='text' onChange={onChange} value={keyword} />
<h1>{count}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}

export default App;


첫 마운트시 모든 useEffect가 실행된다

click 만 했을때의 useEffect

input만 했을때의 useEffect

Redux

  • 리액트는 컴포넌트 간의 depth가 깊어질 수록 props(state)를 주고 받는 일이 힘들어진다
  • 리덕스를 통해서 state를 한 곳에 몰아 저장할 수 있다
  • 리덕스를 통해서 state를 어떤 컴포넌트에서든지 바로 사용 할 수 있다(부모간의 props전달이 필요 없다)
  • 리덕스를 통해서만 state를 변형 할 수 있으므로 관리가 용이하다

Redux-toolkit

  • 기존 리덕스는 사용하기가 복잡하다
  • 리덕스에서 좀더 간편하게 사용할 수 있게 Redux-toolkit 이라는 라이브러리를 지원한다
1
$ npm i @reduxjs/toolkit

store

  • 애플리케이션의 state를 관리하고 업데이트하는 중앙 저장소 역할을 한다
  • toolkit 에서는 slice들을 모아 관리 한다
  • configureStore 로 store 를 설정해 줄 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from '../features/counter/counterSlice';
import colorSlice from '../features/counter/colorSlice';

const store = configureStore({
reducer: { // 리듀서들
counterSlice,
colorBtn: colorSlice, // key : value(리듀서 이름)
},

// optional
middleware
devTools
preloadedState
enhancers
});
export default store;
  • 어떤 컴포넌트던지 리덕스의 store에 접근해 state를 사용 할 수 있게 해주는게 Provider 이다
1
2
3
4
5
6
7
8
9
10
import { Provider } from 'react-redux';
import store from './store/store';

root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
  • App 컴포넌트는 모든 컴포넌트를 담고 있으므로 Provider로 App 컴포넌트를 감싸게 되면 모든 컴포넌트가 store에 접근 할 수 있게 된다
  • 여기서 알 수 있는점은 App이 아니라 특정 컴포넌트만 Provider로 감싸면 그 컴포넌트만 store에 접근 할 수 있다는 점.
  • Provider는 만들어 놓은 store를 필수적으로 props로 넘겨주어야 한다

Reducer에는

  • createAction, createReducer, createSlice, createAsyncThunk… 등등 있다
    • createAction : action 타입 정의, action 생성 함수
    • createReducer : initialState 정의, reducer 함수
  • toolkit에서 사용하는 createSlice에는 createAction 과 createReducer를 내부에서 사용할 수 있으므로 createSlice만 사용 해도 된다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { createSlice } from '@reduxjs/toolkit';

const colorSlice = createSlice({
name: 'abc',
initialState: {
// 초기 상태 값 -> useState()의 초기값
green: 'green',
red: 'red',
value: null,
value2: {
backgroundColor: '',
padding: '',
color: '',
},
},
reducers: {
// 변화를 일으키는 함수 action -> useState setter
tomato: (state) => {
state.green === 'green'
? (state.green = 'tomato')
: (state.green = 'green');
},
banana: (state) => {
state.red = 'yellow';
},
actionBtn: (state, action) => {
console.log(action);
//
state.value = action.payload;
},
actionBtn2: (state, { payload }) => {
console.log(payload)
const { padding, color } = payload;
state.value2.backgroundColor = payload.back;
state.value2.padding = padding;
state.value2.color = color;
},
},
});

export const { tomato, banana, actionBtn, actionBtn2 } = colorSlice.actions;
// action creator 함수를 내보낸다 컴포넌트에서 import해서 사용 할 수 있다

export default colorSlice.reducer;

name

  • creatAction에서 type에 해당하는 부분. action의 prefixer로 사용 된다

initialState

  • state초기 값들을 설정할 수 있다 useState()의 초기값이라고 정리했다

reducers

  • state에 변화를 일으키는 함수들을 모아놓은 객체이다

  • 함수의 매개변수로는 state = initialState, action = type 과 payload가 들어있다

    • payload에는 이 action 함수를 호출할때 넘겨주는 인수가 들어 있다
    • 객체구조분해를 통해 payload만 꺼내서 사용 할 수도 있다

action을 콘솔로 찍었을때

  • action 은 객체 타입이고 안에 type 과 payload가 들어있다
  • type은 name에서 지어준 이름이 prefixer로 붙어있고 action 함수 이름이 있다
  • payload는 action 함수를 호출할때 넣어준 인수가 들어있다

action 함수는 하나의 값만이 아닌, 객체, 배열등도 넘길 수 있다
객체로 값을 넘겼을때 payload를 콘솔로 찍었을때

컴포넌트에서 store에 접근할때 사용하는 Hook

useSelector

  • 컴포넌트에서 store안에 있는 state값에 접근하기 위해서는 useSelector 훅을 사용해야 한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { useSelector } from 'react-redux';

    export default function App() {
    const green = useSelector((state) => state.colorBtn.green);
    const red = useSelector((state) => state.colorBtn.red);keyword

    return (
    <div style={{ backgroundColor: green }}></div>
    );
    }
  • 값을 꺼내 컴포넌트의 초기값으로 사용할 수 있다

useDispatch

  • 컴포넌트에서 store안에 있는 action함수를 호출 하기 위해서는 useDispatch 훅을 사용한다
  • store 안에 있는 action 중 사용 할 것들을 import 해줘야 한다
  • useDispatch()를 바로 사용 할 수 없으므로 변수에 담아 사용 한다 (바로 사용할 수 있는지 사실 잘 모르겠지만 에러가 뜬다)
  • action 함수를 호출할때 인수로 변경할 값을 넘길 수 있다. 이때 값은 payload에 들어간다
1
2
3
4
5
6
7
8
9
10
11
import { useDispatch } from 'react-redux';
import { tomato } from '../features/counter/colorSlice';

export default function App() {
const dispatch = useDispatch();

return (
<button onClick={() => dispatch(tomato())}>green</button>
<button onClick={() => dispatch(actionBtn('black'))}> actionBtn </button>
);
}
Read more »

Object.values()

Object.values() 메소드는 전달된 파라미터 객체가 가지는 속성의 값들로 이루어진 배열을 리턴한다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/values

오늘 리액트 수업에서 배운 것.
하나의 state로 여러개 input 처리 하기

선생님이 만들어주신 템플릿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState } from 'react';
const arr = Array.from(Array(100), (_, i) => i + 1);
export default function Calc() {
const [result, setResult] = useState(0);
const [num, setNum] = useState({});
const onHandleChange = ()=>{};
const add = () => {};
return (
<div>
{arr.map((num, i) => (
<>
<input
type='number'
name={`input=${num}`}
key={i}
onChange={onHandleChange}
/>
{i !== arr.length - 1 ? '+' : '='}
</>
))}
=
<input type='number' disabled value={result} />
<button type='button' onClick={add}>
계산
</button>
</div>
);
}

  • input을 100개를 만들면 각각의 input에 state를 일일이 줄 순 없다.

문제는.. input 각각의 밸류를 어떻게 받아올것인가

일단

1
const [num, setNum] = useState({});

초기값이 객체이고..
input에다 name을 만들어 두셨다

콘솔로 찍어보니

1
2
3
4
const onHandleChange = (e) => {
console.log(e.target.name);
console.log(e.target.value);
};

이렇게 각각의 input name과
그 안의 밸류들이 찍힌다

그럼 이걸 num에다 name을 키 값으로, value를 value로 만들어서 객체 데이터로 넣어주면 된다

1
2
3
4
5
const onHandleChange = (e) => {
const { name, value } = e.target;

setNum({ ...num1, [name]: parseInt(value) });
};

객체구조분해 할당으로 각각 e.target.name 과 e.target.value 를 name,value에 할당해준다

그리고 setter함수로 num에다 값을 넣어줘야 하는데
이때 num의 초기값은 객체이기때문에 객체형태로 넣어줘야 한다
스프레드 연산자 ...는 객체의 내용을 펼쳐서 기존 객체를 복사해준다
현재 num1은 빈 객체이기때문에 빈 객체 생성
name을 key, value를 값으로 넣어준다 value는 현재 문자이기때문에 뒤에서 더해줄것 이므로 숫자로 변환해서 넣어준다

그리고 console로 num을 찍어보니 객체로 잘 들어간다

이제 num에서 value값만 가져와 더해서 result에 넣어주면 되는데..

객체에서 value만 가져오기 검색해보니 Object.values() 가 뜬다

Read more »

data 받아와서 맵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default function DailyList() {
const [data, setData] = useState(null);

useEffect(() => {
const fetchData = async () => {
const res = await axios.get(
'https://s3.us-west-2.amazonaws.com/secure.notion-static.com/4b41cea6-0a74-4f33-86b8-718f24260cc3/data.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220517%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220517T145911Z&X-Amz-Expires=86400&X-Amz-Signature=5156d7c9add92e724823ada0a623f7f270b0b051ae0700d5c3eda2da5775bbee&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22data.json%22&x-id=GetObject'
);
const dataArr = res.data;
setData(dataArr);
console.log(dataArr);
};
fetchData();
}, []);

return <div>{data.map((e) => e.date)}</div>;
}

ToyProject를 리액트로 만들어보기 위해서 데이터를 먼저 받아와봤다
콘솔로도 받아와지는게 잘 찍히는데 출력해보기 위해서 map을 써봤더니 두둥.. 오류?

검색 해보니

  1. 컴포넌트의 상태값 (data) 의 시작값은 null이다
  2. 비동기적으로 데이터를 처리할 때, 컴포넌트는 적어도 한 번, 데이터가 불러지기 전에 렌더링 된다
    이기 때문이다..

해결법은 논리연산자 && 을 사용해 조건이 참이면 렌더링 되게 해주면 된다고 한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default function DailyList() {
const [data, setData] = useState(null);

useEffect(() => {
const fetchData = async () => {
const res = await axios.get(
'https://s3.us-west-2.amazonaws.com/secure.notion-static.com/4b41cea6-0a74-4f33-86b8-718f24260cc3/data.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220517%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220517T145911Z&X-Amz-Expires=86400&X-Amz-Signature=5156d7c9add92e724823ada0a623f7f270b0b051ae0700d5c3eda2da5775bbee&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22data.json%22&x-id=GetObject'
);
const dataArr = res.data;
setData(dataArr);
console.log(dataArr);
};
fetchData();
}, []);

return <div>{data && data.map((e) => e.date)}</div>;
}

잘 출력 된다..

Moment 사용해보기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import React from 'react';
import moment from 'moment-timezone';
import { useRef, useState } from 'react';

export default function MomentEx() {
const momentDate = moment();
const newMomentDate = momentDate.add(1, 'week');
const cloneNewMomentDate = newMomentDate.clone().add(1, 'week');

const [day, setDay] = useState('');
const birthDayRef = useRef(null);
const handleBirthDayChange = (e) => {
setDay(moment(e.target.value, 'YYYY-MM-DD').format('dddd'));
};
return (
<div>
<h1>Moment</h1>
<div>Immutable Check</div>
<div>
{momentDate.format()}
<br />
{newMomentDate.format()}
<br />
{cloneNewMomentDate.format()}
</div>
<br />
<div>Summer Time (New York)</div>
<br />
<div>
2018년 3월 10일 13시에 하루 더하기 <br />
{moment
.tz('2018-03-10 13:00:00', 'America/New_York')
.add(1, 'day')
.format()}
</div>
<br />
<div>
2018년 3월 10일 13시에 하루 더하기 <br />
{moment
.tz('2018-03-10 13:00:00', 'America/New_York')
.add(24, 'hour')
.format()}
</div>
<br />
<div>Leap year (korea)</div>
<br />
<div>
2017년 1월 1일에 1년 빼기 <br />
{moment('2017-01-01 ').subtract(1, 'year').format()}
</div>
<br />
<div>
2017년 1월 1일에 365일 빼기 <br />
{moment('2017-01-01 ').subtract(365, 'day').format()}
</div>
<br />
<br />
<div> 한국어로 표기</div>
<div>{moment('07-17-2021').format('YYYY년 M월 D일')}</div>
<br />
<div>두 날짜 비교</div>
<div>
<input type='date' ref={birthDayRef} onChange={handleBirthDayChange} />
<div>무슨 요일 이었을까?</div>
<div>{day}</div>
</div>
<br />
<div>두 날짜 비교</div>
<div>2021-07-17 03:00 와 2021-07-18 02:00는 몇시간 차이인가?</div>
<div>
{moment('2021-07-17 03:00').diff(moment('2021-07-18 02:00'), 'hours')}
시간
</div>
</div>
);
}
Read more »

Context

https://ko.reactjs.org/docs/context.html#when-to-use-context

  • context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다

언제?

  • React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법
  • 그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있다

글로벌로 사용할 Theme

1
2
3
4
5
6
7
8
9
10
11
12
13
// ThemeContext.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};

export const ThemeContext = React.createContext(themes.dark);

ThemeChange Btn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ThemedButton.jsx
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

export default function ThemedButton(props) {
const theme = useContext(ThemeContext);
return (
<button
{...props}
style={{ backgroundColor: theme.background, color: theme.foreground }}
onClick={props.onClick}
>
ThemedButton
</button>
);
}

Theme Component

  • Themecontext.Provider 안쪽의 버튼은 state값이 themes.light 스타일을 갖는다
  • Themecontext.Provider 바깥쪽 버튼은 ThemeContext의 디폴트 값이 dark를 스타일로 갖는다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Theme.jsx
import { ThemeContext, themes } from './ThemeContext';
import ThemedButton from './ThemedButton';
import React, { useState } from 'react';

export default function Theme() {
const [theme, setTheme] = useState(themes.light);
const toggleTheme = () => {
setTheme((prev) => (prev === themes.dark ? themes.light : themes.dark));
};
return (
<div>
<ThemeContext.Provider value={theme}>
<ThemedButton onClick={toggleTheme} />
<ThemeContext.Consumer>
{(theme) => (
<div
style={{
height: 300,
width: 300,
backgroundColor: theme.background,
}}
></div>
)}
</ThemeContext.Consumer>
</ThemeContext.Provider>
<ThemedButton />
</div>
);
}
Read more »

state

useState , useReducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function State() {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
return (
<div>
<hr />
<h3>useState</h3>
Count : {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount((prev) => prev - 1)}>-</button>
<button onClick={() => setCount((prev) => prev + 1)}>+</button>
</div>
);
}

버튼을 클릭하면 count가 +,- 가 된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default function Reducer() {
const initialCount = { count: 0, name: 'jimmy' };

const reducer = (state, action) => {
switch (action.type) {
case 'reset':
return initialCount;
case 'increment':
return { count: state.count + 1 , name:'Tom'};
case 'decrement':
return { count: state.count - 1 , name:'Amy'};
}
};
const [state, dispatch] = useReducer(reducer, initialCount);
return (
<div>
<h3>Reducer</h3>
Count : {state.count}
<h4>Name : {state.name}</h4>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
  • 컴포넌트에서 관리하는 값이 딱 하나고, 그 값이 단순한 숫자, 문자열 또는 boolean값이라면 useState로 관리하는게 편하다.
  • 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡해지면 useReducer로 관리하는게 편하다
    Read more »