티스토리 뷰
노마드 코더로 React에서 쓰이는 문법 후다닥 배워서 React로 선배 졸작에 쓰일 웹 페이지 만들어보려고 한다!!
한달 동안 열심히 해봐야겠다...
만드는 데 거의 2주 반 정도 걸린 것 같다. (조금 농땡이 피운 걸 후회...)
기능, 디자인, 컴포넌트 구성, 코드 해석, 배운 점 등등을 해당 포스트에 정리할 예정이다.
1. 기능
1.) 기본 페이지
- 주식 입력창
- 사용자가 입력한 값과 비슷한 주식 띄우기
- 주식 추가 확인, 삭제, 전송
2.) 이메일 페이지
- 이메일 양식 확인
- 이메일 전송
3.) 마지막 페이지
- 이메일 전송 완료 페이지
2. 디자인
html이랑 css로 후다닥 만들었다
포인트 컬러는 빨강!
반응형도 해보고 싶긴 한데... 기능 공부하고 구현하는 것만으로도 시간이 빠듯할 것 같아서
모바일 버전으로 반응형 만드는 것은 개인적으로 해봐야겠다.
3. 컴포넌트 구성
1.) App.js
<Home /> <Email /> <Complete /> 컴포넌트를 포함하고 있다.
<Home />
사용자가 페이지 실행 시 가장 먼저 보여지는 페이지다.
<Email />
사용자가 주식을 확인 한 후 자신의 이메일을 입력하는 페이지다.
<Complete />
사용자에게 이메일 전송을 완료했음을 알려주는 페이지다.
2.) Home.js
<PageHeader /> <InputStock /> 컴포넌트를 포함하고 있다.
<PageHeader />
svg 파일이 들어 있는, 페이지를 설명해주는 헤더 역할의 컴포넌트다.
<InputStock />
사용자에게 주식을 입력받고 추가, 삭제하는 역할을 하는 컴포넌트다.
3.) Email.js
<InputStock />에서 넘겨받은 주식과 함께 사용자에게 이메일을 입력 받는다.
사용자의 이메일 형식을 검토하고 이메일의 형식이 맞아야만 submit이 된다.
4.) Complete.js
사용자가 입력한 이메일로 이메일이 전송되었음을 알려주는 페이지다.
4. 코드
1.) App.js
import Home from "./Home";
import Emailsubmit from "./EmailSubmit";
import Complete from "./Complete";
import {
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/email" element={<Emailsubmit />}></Route>
<Route path="/email/:user_email" element={<Complete />}></Route>
</Routes>
</Router>
);
}
export default App;
❗️ 각 컴포넌트를 포함하는 import 문을 작성했다.
❗️ 리액트 라우터
import {
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom";
리액트 라우터를 다운 받고 import 시켰다. 노마드 코더의 강의에선 구버전의 리액트 라우터를 사용하고 있어서 Routes 대신 Switch 키워드를 쓴다고 한다.
<Route path="/" element={<Home />}></Route>
<Route path="/email" element={<Emailsubmit />}></Route>
<Route path="/email/:user_email" element={<Complete />}></Route>
Route 컴포넌트 내 속성의 형태로 컴포넌트와 경로값을 입력해준다.
2.) Home.js - <PageHeader /> <InpuStock />
import PageHeader from "./components/PageHeader";
import InputStock from "./components/InputStock";
function Home() {
return(
<div id="container">
<PageHeader />
<InputStock />
</div>
);
}
export default Home;
<PageHeader /> 컴포넌트와 <InputStock /> 컴포넌트를 포함시켜줬다.
<PageHeader />
function PageHeader() {
return (
<div id="header_container">
<h1 id="page_title">Page title</h1>
<h1 id="page_subtitle">Input subtitle explain your page</h1>
<p id="page_explain">리액트 너무 어려워요~!!!</p>
<div className="custom-shape-divider-bottom-1651133476">
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 120" preserveAspectRatio="none">
<path d="M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z" className="shape-fill"></path>
</svg>
</div>
<div className="custom-shape-divider-bottom-1651132699">
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 120" preserveAspectRatio="none">
<path d="M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z" className="shape-fill"></path>
</svg>
</div>
</div>
);
}
export default PageHeader;
Shape Divider App
Create fully responsive shape dividers for your next web project
www.shapedivider.app
해당 사이트를 이용해서 원하는 svg를 만들고 html과 css에 코드를 삽입해주었다.
<InputStock />
배우고 헤맨 게 가장 많은 컴포넌트였다. 이리저리 구글링 하면서 필요한 기능을 찾고... 더 원하는 방향으로 최적화 시키느라 애를 많이 먹었다. 여전히 많이 부족한 부분이 있는 것 같지만...! 그래도 하나씩 정리하며 복습하려 한다.
기능
1. 사용자 주식 입력
2. 주식에 따른 자동 추천
3. 주식 추가, 삭제
전체 코드
import {useState, useEffect} from "react";
import stockjson from "./stockdata.json"
import {Link} from "react-router-dom";
// * 컴포넌트 시작
function InputStock() {
// * 처음 한 번만 데이터를 생성
const [data, setData] = useState([]);
// * [ useEffect ] for data
useEffect(() => {
setData( (current) => {
for(const stock_key in stockjson) {
const a = {};
a.stockCode = stock_key;
a.stockNm = stockjson[stock_key];
current = [a, ...current];
}
return current;
})
}, []);
// * value state
const [user_value, set_user_value] = useState("");
// * 필터링된 데이터 (객체 배열 형태)
const [filterData, setFilterData] = useState([]);
// * 사용자가 추가한 주식 (객체 배열 형태 - 최대 15개)
const [user_stock, set_user_stock] = useState([]);
// * input
// ! onChange
const onChange = (event) => {
const value = event.target.value;
if(value === "" ) {
setFilterData(current => {
current = [];
return current;
})
}
else {
const remove_reg = /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/ ]/gim;
const filter_value = value.replace(remove_reg, "");
const new_data = data.filter(stock => {
const regex = new RegExp(filter_value, "gi");
return stock.stockCode.match(regex) || stock.stockNm.match(regex);
})
setFilterData(current => {
current = new_data;
return current;
})
}
set_user_value(current => {
current = value;
return current;
})
}
// ! <NoneSearch /> 컴포넌트
function NoneSearch() {
return (
<div id="none_search">
{/* 가상요소로 이모티콘 넣어야함 */}
<p>찾으시는 검색결과가 없습니다.</p>
</div>
);
}
// ! <StockDataList /> 컴포넌트
function StockDataList({data}) {
// ? onclick이벤트
const listOnClick = (event) => {
const current_index = event.target.className.slice(0,1);
const current_data = data[current_index];
if(user_stock.length < 15) {
set_user_stock(current => {
current.push(current_data);
return current;
})
alert('항목이 추가되었습니다.');
set_user_value (current => {
current = "";
return current;
});
console.log('현재 user_stock 상태: ', user_stock);
setFilterData(current => {
current= [];
return current;
})
}
else {
alert('최대 15개까지 선택 가능합니다.');
}
}
return (
<div id="stock_lists_container" className={filterData.length > 0 ? "show" : "hidden"}>
<ul id="stock_lists_ul" >
{data.map((stock, index) => {
return <li key={index} onClick={listOnClick} className={index + " stock_list"}><span className={index + " stock_name"}>{stock.stockNm}</span> <span className={index + " stock_code"}>{stock.stockCode}</span></li>
})}
</ul>
</div>
);
}
// ! MakeStockList 컴포넌트
function MakeStockList({data}) {
const remove_button = (event) => {
const button_index = Number(event.target.parentElement.id.slice(0,1));
console.log('삭제할 인덱스 : ', button_index);
console.log('현재 user_stock 상태: ', user_stock);
const another_user_stock = user_stock.map(x => x);
another_user_stock.splice(button_index, 1)
set_user_stock(current => {
current = another_user_stock;
return current;
})
}
return (
<div id="user_stock_container">
<form action="" onSubmit={(event) => {
event.preventDefault();
}}>
<ul>
{data.map((stock, index) => {
return (<li key={index} id={index+"stock"} className="user_list"><span className="stock_name">{stock.stockNm}</span> <span className="stock_code">{stock.stockCode}</span><button onClick={remove_button}>❌</button></li>);
})}
</ul>
<Link to="/email" state = {
{
data: user_stock
}
} style={{textDecoration: 'none'}}>
<input id="user_stocks_submit" type="submit" value="Submit"/>
</Link>
</form>
</div>
);
}
//! <Footer /> 컴포넌트
function Footer() {
return(
<div id="footer_container">
</div>
);
}
// * 렌더링 함수
return (
<div id="inner_container">
<div id="input_container">
<p id="explain_input">Input your Stock</p>
<div className="white_background">
{filterData.length < 1 ? <NoneSearch /> : null}
<form action="" onSubmit={(e)=> {
e.preventDefault();
}} >
<input type="text"
placeholder="찾으시는 종목을 검색하세요."
name = "stock"
id = "stock_input"
required
autoFocus
autoComplete="off"
onChange = {onChange}
onSubmit = {(e) => {
e.preventDefault();
}}
value = {user_value}
/>
<input id="search_button" type="submit" value="Search" />
{filterData === [] ? null : <StockDataList data={filterData}/>}
</form>
</div>
<div className={user_stock.length > 0 ? "show" : "hidden"}>
<div id="makestock_title">
<p>Current Lists</p>
</div>
<MakeStockList data={user_stock}/>
</div>
</div>
<Footer />
</div>
);
}
export default InputStock;
import
import {useState, useEffect} from "react";
import stockjson from "./stockdata.json"
import {Link} from "react-router-dom";
useState, useEffect
리액트를 아직 잘 몰라도 제일 중요한 메소드 중 하나라는 것 쯤은 안다!
useState
useState 로 페이지의 상태(state)를 관리하는 변수를 만들어서, state가 변할 때마다 다시 렌더링이 일어나게 한다.
useEffect
State 변경으로 재렌더링이 발생할 때 필요없는 부분까지 렌더링을 시키지 않기 위해 필요한 메소드다.
stockjson
사용자 입력이 실시간으로 바뀜에 따라 그에 맞는 주식 목록들을 아래로 추천해줘야했다.
네이버나 구글의 자동완성 처럼...
그래서 주식 종목 데이터가 필요했고, 선배는 코스피 주식만 포함하고 있으면 된다고 해서 주식 데이터가 필요했다.
https://kind.krx.co.kr/corpgeneral/corpList.do?method=loadInitPage
대한민국 대표 기업공시채널 KIND
업종 전체 농업, 임업 및 어업 광업 제조업 - 식료품 제조업 - 음료 제조업 - 담배 제조업 - 섬유제품 제조업; 의복제외 - 의복, 의복액세서리 및 모피제품 제조업 - 가죽, 가방 및 신발 제조업 - 목
kind.krx.co.kr
주식 데이터는 해당 페이지에서 찾았다.
엑셀 형태로 다운 받아 필요없는 열들을 삭제하고 주식 종목 코드와, 주식 종목 이름만 남겼다.
JS에도 엑셀 파일을 불러오는 메소드가 있을 것 같은데... 이땐 그 생각을 못해서 excel => json 파일로 변환시킬 방법을 생각했다.
그리고 json 형식으로 변환 하기 위해 다음 웹 사이트들을 사용했다.
1. Excel 파일을 Json 형식으로 바꿔주는 사이트
http://shancarter.github.io/mr-data-converter/
Mr. Data Converter
shancarter.github.io
2. Json 형식이 맞는지 검사해주고 Json 파일로 변환, 다운받는 사이트
https://jsonformatter.curiousconcept.com/#
JSON Formatter & Validator
Format and validate JSON data so that it can easily be read by human beings.
jsonformatter.curiousconcept.com
문제는... 코스피 코드들이 다 숫자(Number) 형태인 줄 알았는데
"001045":"CJ1우선주",
"00104K":"CJ4우선주(전환)",
이런 식으로 숫자 뒤에 K나 L이 붙어있는 코드들이 있었다.
그런데 데이터 변환파일은... stockCode를 계속 숫자로 인식하고, 숫자가 아닌 것들이 섞여있으니 계속 오류가 나고... 난리였다...
그래서 key:value 형태의 딕셔너리 형태로 json 파일을 저장했다.
이 방식을 생각못해서 이틀을 골머리를 썩었던 것 같다... .
link
리액트 라우터를 사용하기 위해 import 시켜주었다.
다른 페이지로 넘어가는 <a> 태그의 역할을 해주면서 새로고침이 일어나지 않게 한다.
<InputStock> 컴포넌트의 return
return (
<div id="inner_container">
<div id="input_container">
<p id="explain_input">Input your Stock</p>
<div className="white_background">
{filterData.length < 1 ? <NoneSearch /> : null}
<form action="" onSubmit={(e)=> {
e.preventDefault();
}} >
<input type="text"
placeholder="찾으시는 종목을 검색하세요."
name = "stock"
id = "stock_input"
required
autoFocus
autoComplete="off"
onChange = {onChange}
onSubmit = {(e) => {
e.preventDefault();
}}
value = {user_value}
/>
<input id="search_button" type="submit" value="Search" />
{filterData === [] ? null : <StockDataList data={filterData}/>}
</form>
</div>
<div className={user_stock.length > 0 ? "show" : "hidden"}>
<div id="makestock_title">
<p>Current Lists</p>
</div>
<MakeStockList data={user_stock}/>
</div>
</div>
<Footer />
</div>
);
처음 형태는 이렇다.
const [user_value, set_user_value] = useState("");
사용자가 입력하는 값에 따라 실시간으로 input 창에서 onChage 이벤트로 input 창의 값이 변하는지 검사를 해준다.
<input type="text"
placeholder="찾으시는 종목을 검색하세요."
name = "stock"
id = "stock_input"
required
autoFocus
autoComplete="off"
onChange = {onChange}
onSubmit = {(e) => {
e.preventDefault();
}}
value = {user_value}
/>
onChange 가 발생하면 onChange 함수를 실행한다.
매개변수로는 event 객체를 넘겨준다.
onChange 함수 전체 코드
const onChange = (event) => {
const value = event.target.value;
if(value === "" ) {
setFilterData(current => {
current = [];
return current;
})
}
else {
const remove_reg = /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/ ]/gim;
const filter_value = value.replace(remove_reg, "");
const new_data = data.filter(stock => {
const regex = new RegExp(filter_value, "gi");
return stock.stockCode.match(regex) || stock.stockNm.match(regex);
})
setFilterData(current => {
current = new_data;
return current;
})
}
set_user_value(current => {
current = value;
return current;
})
}
이벤트가 발생한 타겟의 값을 value 변수에 저장해준다.
const value = event.target.value;
if(value === "" ) {
setFilterData(current => {
current = [];
return current;
})
}
사용자가 값을 끝까지 다 지웠는데도 자동추천 목록이 밑에 계속해서 남아 있어서
value 값이 ""가 되면 setFilterData(자동추천 목록이 들어가 있는 배열) 을 빈 배열로 만들어준다.
else {
const remove_reg = /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/ ]/gim;
const filter_value = value.replace(remove_reg, "");
const new_data = data.filter(stock => {
const regex = new RegExp(filter_value, "gi");
return stock.stockCode.match(regex) || stock.stockNm.match(regex);
})
setFilterData(current => {
current = new_data;
return current;
})
}
만약 input에 사용자가 글자를 입력했다면 정규식을 통해 사용자가 입력한 값과 비슷한 주식을 자동으로 띄워주기 위한 새 배열을 만든다.
// * 필터링된 데이터 (객체 배열 형태)
const [filterData, setFilterData] = useState([]);
그리곤 filterData 라는 state 변수에 넣어준다.
자동추천을 어느 정도의 유연성으로 만들지 고민했는데 결국 세운 기준은 다음과 같다.
1. 대소문자 구분이 없을 것
2. 중간에 들어간 문자도 검사할 것
3. 기호가 들어간다면 그 기호를 없앤 값으로 값을 검사할 것
const remove_reg = /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/ ]/gim;
const filter_value = value.replace(remove_reg, "");
const new_data = data.filter(stock => {
const regex = new RegExp(filter_value, "gi");
return stock.stockCode.match(regex) || stock.stockNm.match(regex);
})
정규식을 만들어 remove_reg에 저장한다.
remove_reg에 포함된 특수기호들은 replace() 메소드를 사용해서 없애준다.
현재 data엔 json으로 부른 주식 정보들이 들어있다.
// * 처음 한 번만 데이터를 생성
const [data, setData] = useState([]);
// * [ useEffect ] for data
useEffect(() => {
setData( (current) => {
for(const stock_key in stockjson) {
const a = {};
a.stockCode = stock_key;
a.stockNm = stockjson[stock_key];
current = [a, ...current];
}
return current;
})
}, []);
렌더링이 될 때 마다 데이터를 계속 불러오는 것이 신경쓰여서 data를 state 변수에 저장하고 처음 한번만 데이터를 만들도록 useEffect의 두번째 인자로 빈 배열을 넣어주었다.
json의 key 값에는 주식의 코드값이 들어가있고, json의 value 값에는 주식 이름이 들어가있다.
이를 for in 문으로 객체 배열 형태로 data에 저장하도록 만들었다.
const remove_reg = /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/ ]/gim;
const filter_value = value.replace(remove_reg, "");
const new_data = data.filter(stock => {
const regex = new RegExp(filter_value, "gi");
return stock.stockCode.match(regex) || stock.stockNm.match(regex);
})
아무튼 다시 정규식 처리로 돌아와서...
filter 메소드를 통해 stockCode나 stockNm 둘 중 하나라도 사용자의 값과 일치하면 해당 주식을 담은 객체 배열을 new_data에 저장한다.
// * 필터링된 데이터 (객체 배열 형태)
const [filterData, setFilterData] = useState([]);
setFilterData(current => {
current = new_data;
return current;
})
new_data 에 담긴 필터링 된 데이터를 setFilterData() 메소드를 사용해서 filterData의 state를 변경한다.
{filterData === [] ? null : <StockDataList data={filterData}/>}
state가 변경되면서 <StockDataList /> 컴포넌트가 보여지게 되고, props로 filterData state를 전달하게 된다.
<StockDataList /> 컴포넌트
작동모습
유저의 입력값에 따라 입력한 값과 비슷한 list를 실시간으로 렌더링해주는 컴포넌트다.
전체코드
// ! <StockDataList /> 컴포넌트
function StockDataList({data}) {
// ? onclick이벤트
const listOnClick = (event) => {
const current_index = event.target.className.slice(0,1);
const current_data = data[current_index];
if(user_stock.length < 15) {
set_user_stock(current => {
current.push(current_data);
return current;
})
alert('항목이 추가되었습니다.');
set_user_value (current => {
current = "";
return current;
});
console.log('현재 user_stock 상태: ', user_stock);
setFilterData(current => {
current= [];
return current;
})
}
else {
alert('최대 15개까지 선택 가능합니다.');
}
}
return (
<div id="stock_lists_container" className={filterData.length > 0 ? "show" : "hidden"}>
<ul id="stock_lists_ul" >
{data.map((stock, index) => {
return <li key={index} onClick={listOnClick} className={index + " stock_list"}><span className={index + " stock_name"}>{stock.stockNm}</span> <span className={index + " stock_code"}>{stock.stockCode}</span></li>
})}
</ul>
</div>
);
}
<StockListComponent /> - return
return (
<div id="stock_lists_container" className={filterData.length > 0 ? "show" : "hidden"}>
<ul id="stock_lists_ul" >
{data.map((stock, index) => {
return <li key={index} onClick={listOnClick} className={index + " stock_list"}><span className={index + " stock_name"}>{stock.stockNm}</span> <span className={index + " stock_code"}>{stock.stockCode}</span></li>
})}
</ul>
</div>
);
filterData의 길이가 0, 즉 비어있을 때면
.hidden {
display: none;
}
hidden CSS 스타일을 적용시켜주고 그게 아니라면 list 형태로 필터링된 주식들을 보여준다.
컨테이너 height를 고정해놓고, height를 넘어가는 list들은 스크롤로 볼 수 있도록 디자인했다.
그리고 리스트를 클릭하면 사용자가 추가한 리스트에 들어간다.
data.map((stock, index) => {
return <li key={index} onClick={listOnClick} className={index + " stock_list"}><span className={index + " stock_name"}>{stock.stockNm}</span> <span className={index + " stock_code"}>{stock.stockCode}</span></li>
}
onClick ={listOnClick} 으로 이벤트가 실행된다.
listOnClick 이벤트
const listOnClick = (event) => {
const current_index = event.target.className.slice(0,1);
const current_data = data[current_index];
if(user_stock.length < 15) {
set_user_stock(current => {
current.push(current_data);
return current;
})
alert('항목이 추가되었습니다.');
set_user_value (current => {
current = "";
return current;
});
console.log('현재 user_stock 상태: ', user_stock);
setFilterData(current => {
current= [];
return current;
})
}
else {
alert('최대 15개까지 선택 가능합니다.');
}
}
❗️list를 클릭하면 span으로 나눠놔서... event.target이 자꾸만 다르게 나왔다.
currentTarget으로 list를 target으로 주려다가, 각 리스트마다 같은 className을 줌으로써 인덱스를 통해 필터링된 주식에서 클릭된 주식을 뽑아왔다.
여기서 data는 props로 받은 filterData를 저장한 지역변수다.
// * 사용자가 추가한 주식 (객체 배열 형태 - 최대 15개)
const [user_stock, set_user_stock] = useState([]);
그리고 사용자가 선택한 current_stock을 user_stock에 저장한다.
user_stock엔 최대 15개까지만 저장 가능하도록 if문으로 나눠줬다.
<MakeStockList /> 컴포넌트
사용자가 하나라도 주식을 추가하면 나타나는 컴포넌트다.
사용자가 추가한 주식이름, 주식코드를 알려주고, 삭제할 수 있도록 버튼을 추가해준다.
작동 모습
전체 코드
// ! MakeStockList 컴포넌트
function MakeStockList({data}) {
const remove_button = (event) => {
const button_index = Number(event.target.parentElement.id.slice(0,1));
console.log('삭제할 인덱스 : ', button_index);
console.log('현재 user_stock 상태: ', user_stock);
const another_user_stock = user_stock.map(x => x);
another_user_stock.splice(button_index, 1)
set_user_stock(current => {
current = another_user_stock;
return current;
})
}
return (
<div id="user_stock_container">
<form action="" onSubmit={(event) => {
event.preventDefault();
}}>
<ul>
{data.map((stock, index) => {
return (<li key={index} id={index+"stock"} className="user_list"><span className="stock_name">{stock.stockNm}</span> <span className="stock_code">{stock.stockCode}</span><button onClick={remove_button}>❌</button></li>);
})}
</ul>
<Link to="/email" state = {
{
data: user_stock
}
} style={{textDecoration: 'none'}}>
<input id="user_stocks_submit" type="submit" value="Submit"/>
</Link>
</form>
</div>
);
}
return
return (
<div id="user_stock_container">
<form action="" onSubmit={(event) => {
event.preventDefault();
}}>
<ul>
{data.map((stock, index) => {
return (<li key={index} id={index+"stock"} className="user_list"><span className="stock_name">{stock.stockNm}</span> <span className="stock_code">{stock.stockCode}</span><button onClick={remove_button}>❌</button></li>);
})}
</ul>
<Link to="/email" state = {
{
data: user_stock
}
} style={{textDecoration: 'none'}}>
<input id="user_stocks_submit" type="submit" value="Submit"/>
</Link>
</form>
</div>
);
현재 user_stock에 들어있는 데이터들을 map메소드를 통해 list를 그려준다.
만일 submit을 클릭하면 "/email" 주소의 <Email /> 컴포넌트로 클릭한다.
리스트 삭제
const remove_button = (event) => {
const button_index = Number(event.target.parentElement.id.slice(0,1));
console.log('삭제할 인덱스 : ', button_index);
console.log('현재 user_stock 상태: ', user_stock);
const another_user_stock = user_stock.map(x => x);
another_user_stock.splice(button_index, 1)
set_user_stock(current => {
current = another_user_stock;
return current;
})
}
❌ 버튼을 누르면 리스트가 삭제된다.
위에... list 추가도 parentElement로 해결했으면 됐을 텐데...라는 생각이 갑자기 든다.
아무튼 paranteElement의 index는 고유 key값과 같은 값이다.
그 인덱스를 삭제한 새로운 배열을 만들고, state를 변경해줌으로써 user_stock에서 해당 주식을 없앤다.
<Email /> 컴포넌트
submit 버튼을 누르면 react-louter의 link 를 통해 "/email" 로 이동한다.
해당 컴포넌트는 이메일 형식을 체크해준다.
이메일 형식이 틀릴 때
이메일 형식이 올바를 때
이메일 정규식 확인하는 함수
function email_check(email) {
const reg = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/g;
return(reg.test(email));
}
정규식에 대한 정리는 나중에 해야겠다.....
이메일 정규식 기준
1. 숫자, 영어 소문자, 대문자로 시작할 것
2. 특수기호 (_-/.) 와, 숫자, 영어 소문자, 영어 대문자가 들어가거나 안들어갈 수도 있음
3. 특수기호 @ 가 들어감.
4. 특수 기호 온점 (.)이 들어감.
5. com이나 net으로 끝나므로 2~3글자의 영어로 끝나야함.
<Complete /> 컴포넌트
이메일이 전송됐다고 알려주는 페이지다!
여기까지 하고... 선배가 서버를 만들어서 링크를 알려주면 내가 데이터를 전송하기만 하면 된다.
이때 사용자의 주식 정보들과 사용자의 이메일을 props로 전달할 수 있지만 나는 link객체로 전달했다.
<Link to={`/email/${user_email}`}
state= {
{
data: user_stock,
user_email: user_email
}
}
style={{textDecoration: "none"}}>
to 와 같은 위치에 state 객체 형태로 데이터를 넘겨주고...
하이퍼 링크처럼 밑줄이 그어지길래 인라인 스타일로 text-decoration 스타일을 none으로 지정했다.
느낀 점!
리액트 문법이나 컴포넌트 사이 props전달, 리액트 라우터 등을 프로젝트로 직접 써보니까 어떤 식으로 사용해야 할지 감이 잡힌다.
더불어 filter, map, match, replace, 정규식 등 JS에서 사용하는 문법이나 객체 등도 프로젝트가 아니었다면 몰랐을 것이다.
이번 프로젝트에선 정규식 문법과 렌더링 최적화, 데이터 전달(props, link)에 대해 가장 많이 고민했다.
선배가 서버를 만들면 post 방식으로 전달해달라고 했는데... 이것 때문에 nodejs 에 대한 기초지식도 알아야할 것 같다.
아쉬운 점이라고 하면 prop들의 타입을 따로 지정하지 않은 점, 반응형으로 만들지 않은 점 등등... 노마드 코더 강의에서 배운 것들을 모두 다 써보진 못했다는 점이다. 더불어 내가 state관리를 제대로 하고 있는지, 렌더링 최적화를 더 할 수 있는 방법은 없는지를 알 도리가 없으니 그게 아쉽다. 스터디를 한 번 구해봐야하나...?
렌더링 최적화와 서버와의 통신을 더 공부해야겠다. 컴포넌트 구성 방식에 대해서도 더 고민해봐야겠고... 다음 프로젝트는 내가 좋아하는 것들을 포함시켜 만들어보고 싶다. node js까지 가볍게 공부해서 개인 프로젝트를 하나 완성할 수 있었으면 한다.
'Web > React' 카테고리의 다른 글
[React] slide 구현하기 (0) | 2022.09.27 |
---|---|
[React] React예제 200 (78~84) - Redux (0) | 2022.06.24 |
[React] React 예제 200 - 76~77 (0) | 2022.06.24 |
[React] 노마드코더 useState예제 (0) | 2022.04.25 |
[React] React 기본 (0) | 2022.04.24 |