위스타그램 (1)
프로젝트 구조
public
├── data
│ └── asideData.json
│ └── feedData.json
├── images
├── index.html
src
├── components
│ ├── Nav.js
│ ├── Nav.scss
├── pages
│ ├── Login
│ │ └── input.js
│ │ └── Login.js
│ │ └── Login.scss
│ ├── Main
│ │ └── Mainpage
│ │ │ └── Aside
│ │ │ │ └── Footer - Footer.js
│ │ │ │ └── Recommend - Recommend.js
│ │ │ │ └── StoryForm - StoryForm.js
│ │ │ │ └── Aside.js
│ │ │ └── Feed
│ │ │ │ └── Comment - Comment.js
│ │ │ │ └── Feed.js
│ │ │ └── Mainpage.js
│ │ └── Main.js
│ │ └── Main.scss
├── styles
│ ├── common.scss
│ └── reset.css
│ └── variables.css
├── index.js
└── Router.js
└── .prettierrc
└── .eslintrc
실행화면
개발환경
Mac OS 사용
VSC Version: 1.65
react 17.0.2
사용 기술
React
코드 흐름 + 새로 알게 된 부분
로그인 페이지 | 사용자 입력 데이터 저장과 로그인 버튼 활성화 (validation)
const [newInput, setNewInput] = useState({
id: '',
password: '',
});
- 아이디와 비밀번호
input value
를 저장할state
를 선언 →input
이 2개라고해서state
를 따로 선언하는 것이 아니라 객체 형식으로 초기값 지정해서 관리하며 잊지말고input
의 속성값 value에 넣어준다. (input 태그에 속성값 안넣고 이거외않되함ㅋ)
const handleInputValue = e => {
const { name, value } = e.target;
setIsValue({ ...isValue, [name]: value });
};
<input>
에서onChange
발생 시handleInput
함수 발생 → 이벤트 타겟인input
을 속성값인name
과value
를 구조분해할당 → 리렌더링 발생시키기 위해 변경함수setIsValue
에 넣어줌setIsValue({...isValue, [name]: value });
이 부분이 어려웠는데spread
문법으로isValue
객체를 풀어주고,[name]
이라는 속성값을 찾아서e.target.value
를 추가해달라는 의미이다.- 초기에 이 부분을 아래 코드처럼 구현했는데 리액트
state
의 불변성을 위해 원본state
를deep copy
하고 복사본을state
에 추가했었다. 이제 굳이 이렇게 할 필요 없이 위처럼 간결한 ES6 문법을 쓰도록 하자!
let arrayCopy = [...isValue]; //원본파일복사
arrayCopy.push(inputValue);
setIsValue(arrayCopy);
이메일 형식과 비밀번호 자릿수 유효성 검사
const isValid = isValue.id.includes('@') && isValue.password.length > 4;
아래는 리팩토링 전 코드이다.
const checkInput = () => {
newInput.id.indexOf('@') > 1 && newInput.password.length > 4
? setColor('#0095f6')
: deActivateBtn();
};
- 리팩토링 이전에는 삼항연산자로 구현하여 함수를 호출하였다. 하지만 조건식
input.indexOf('@') > 1 && inputPw.length > 4
자체가Boolean
값의 기준이 될 수 있으므로 굳이 삼항연산자로 구현하지 않고로그인 button
의disabled={!isValid}
로 간단하게 구현해줄 수 있었다. - 또한 함수로 구현할 경우, 패스워드의 길이가 5 이상일 때 버튼이 활성화되지않고 6자리부터 활성화되는 에러를 겪게된다. 그 이유는
useState()
가 비동기적이기 때문인데, 아래 코드와 같이 위에서 아래로 순차적으로 코드가 실행되는 것이 아니라,useState()의 변경함수 setInput()
이 실행되는 동시에checkInput()
이 실행되어버리기 때문에 벌어진 에러였다.
- 이러한 문제로 useState 값이 바뀔 때마다 렌더링 하는 방법이 함수형 업데이트인데, setState를 줄 때 위 코드처럼 어떠한 값을 바로 주는 것이 아니라 함수를 통해서 전달하는 방식을 이용하는 것이다. 하지만 더 간단한 불리언 값을 직접 넣어주는 방법으로 해결하였다.
//이전 코드
const onClick = () => {
setCount(count+1);
console.log('click');
setCount(count+1);
console.log('click');
}
//함수형 업데이트 코드
const onClick = () => {
setCount(count => count+1);
console.log('click');
setCount(count => count+1);
console.log('click');
}
로그인 페이지 | 로그인 버튼 클릭 시 메인 페이지 이동
import { useNavigate } from 'react-router-dom';
사용const navigate = useNavigate();
변수 생성해서 불러오기- 로그인 버튼에
onClick
이벤트 발생 시goMain()
함수 발생 시켜navigate('/suh/main')
로 페이지 라우팅
메인페이지 | Mock data 이용해 여러 피드 구현, 여러 댓글 구현
public/data
에Mock data
인feedData.json
,asideData.json
생성
[
{
"id": 1,
"userProfileImg": "/images/hyeseong/otter.png",
"userName": "a.orazy_sudnics",
"userLocation": "wecode",
"content": "my name is Moon",
"thumbnail": "/images/kyungsuh/d.jpeg",
"likesCount": 15,
"commentList": [
{
"id": 1,
"userName": "wecode",
"content": "Welcome to world best coding bootcamp!",
"isLiked": true
},
{
"id": 2,
"userName": "wecode2",
"content": "Hi there.",
"isLiked": false
},
{
"id": 3,
"userName": "wecode3",
"content": "Hey.",
"isLiked": false
}
]
},
{
"id": 2,
"userProfileImg": "/images/hyeseong/otter.png",
"userName": "jaehyuksssss",
"userLocation": "서울 어딘가",
"content": "2번 피드",
"thumbnail": "/images/kyungsuh/c.jpeg",
"likesCount": 10,
"commentList": [
{
"id": 1,
"userName": "2-1",
"content": "Welcome to seoul",
"isLiked": true
},
{
"id": 2,
"userName": "2-2",
"content": "Hi",
"isLiked": false
},
{
"id": 3,
"userName": "2-3",
"content": "Hey there.",
"isLiked": false
}
]
},
{
"id": 3,
"userProfileImg": "/images/hyeseong/otter.png",
"userName": "jeong_hyeon_zzz",
"userLocation": "LA",
"content": "3번 피드",
"thumbnail": "/images/kyungsuh/a.webp",
"likesCount": 35,
"commentList": [
{
"id": 1,
"userName": "wecode",
"content": "best coding bootcamp!",
"isLiked": true
},
{
"id": 2,
"userName": "joonsikyang",
"content": "Welcome",
"isLiked": false
},
{
"id": 3,
"userName": "jayPark",
"content": "Hey. Welcome",
"isLiked": false
}
]
},
{
"id": 4,
"userProfileImg": "/images/hyeseong/otter.png",
"userName": "hyeonegod",
"userLocation": "서울 어딘가",
"content": "4번 피드",
"thumbnail": "/images/kyungsuh/b.webp",
"likesCount": 52,
"commentList": [
{
"id": 1,
"userName": "cpu",
"content": "hello world!",
"isLiked": true
},
{
"id": 2,
"userName": "joonsikyang",
"content": "Hi there.",
"isLiked": false
},
{
"id": 3,
"userName": "jayPark",
"content": "Hey.",
"isLiked": false
}
]
},
{
"id": 5,
"userProfileImg": "/images/hyeseong/bonobono.jpeg",
"userName": "lluxlx_xx",
"userLocation": "숲속 어딘가",
"content": "포로리야",
"thumbnail": "/images/kyungsuh/d.jpeg",
"likesCount": 100,
"commentList": [
{
"id": 1,
"userName": "포로리",
"content": "왜 보노보노야",
"isLiked": true
},
{
"id": 2,
"userName": "너부리",
"content": "나도 간다 ",
"isLiked": false
}
]
}
]
아래는 메인 페이지 최상단 <Mainpage/>
코드이다.
function MainPage() {
const [feedArr, setFeedArr] = useState([]);
useEffect(() => {
fetch('/data/kyungsuh/feedData.json')
.then(res => res.json())
.then(data => setFeedArr(data));
});
return (
<main className="contMain">
<section className="mainBox">
<div>
{feedArr.map(list => {
return <Feeds key={list.id} {...list} />;
})}
</div>
<Aside />
</section>
</main>
);
}
<Feed/>
컴포넌트를 뿌려주기 위해서 가장 최상단 부모 폴더인<Mainpage/>
에서state
를 관리해준다.mock data
를 담아줄 빈 배열state
를 선언하고,fetch
함수로 불러올 데이터 주소를 넣어주는데fetch
함수는 첫번째 인자로http
요청을 보낼 API주소, 두번째 인자로 요청을 보낼때의 옵션들을 객체형태로 받는다.http://localhost:3000/data/commentData.json
로 넣어주게 되면 포트번호가 변경될수 있으므로/data/kyungsuh/feedData.json
이 부분만 넣어주는 것이 유지보수 측면에서 좋다. 이 때 API 주소는 문자열로 입력한다.- 그런데 이때 데이터를 요청하는 시점을 특정해야하는데
useEffect
훅을 활용하여 컴포넌트가 렌더링 된 이후 데이터를 요청한다. 요청이 성공적으로 완료되면setFeedArr
함수를 사용하여feedArr
state
를 응답 받은 값으로 바꿔준다. - 요청을 받을 때
json
형태로 바꿔주는데json
을 쓰는 이유는 프론트엔드와 백엔드에서 서로 다른 언어로 통신하므로 우리가 갖는 객체랑 파이썬의 딕셔너리 자료형과 같지가 않다. 그래서 둘 다 같은 형태로 볼 수 있는게json
형태이다. 다른 언어도 마찬가지로 해당하는 언어를 내 언어에 맞게 컴파일해서 볼 수 있다. 통신할 때는string
형태로 전달한다. 이 때fetch
함수는 비동기이므로then
메서드를 이용해 다음 작업을 진행한다.
{feedArr.map(list => {
return <Feeds key={list.id} {...list} />;
})}
map
으로 배열feedArr
의 요소 하나 하나를 방문하며 그 데이터를list
라는 이름으로 뽑아내서 자식 컴포넌트Feeds
에 전달한다. 이때List
데이터의 고유한 값인id
를key
값으로 전달하며 배열의index
를 따로 빼서 전달하지 않는다. 여기서key
를 지정해야하는 이유는 예를들어, 3개의 리스트를 가진 변수를 통해 key가 없이 배열 랜더링을 진행하게 한다면 해당 리스트변수에 1개가 더 추가되는 경우라도 React 는 총 4개를 처음부터 다시 리렌더링 하게 된다. 하지만 key 를 지정한다면 기존의 요소들은 변경되지 않았다는걸 React 에서 자동으로 파악 후 새로생기는 요소에 대해서만 리렌더링을 진행하게 된다. 단순히 key 요소만 추가한것만으로도 더욱 최적화 된 랜더링을 진행할수 있다.
[React] 배열의 index를 key로 쓰면 안되는 이유
느낀 점
Mock data를 받아와 처리하고 반복문으로 돌려 데이터 바인딩 하는 것을 처음 해보았고 아직은 복잡하지 않아 크게 어려움은 없지만 data의 구조가 복잡해질 때를 생각해봐야겠다. 반복으로 돌릴 수 있는 부분을 최대한 반복문을 사용해서 재사용하려고 노력하였다. state를 전달하는 과정에서 자식 컴포넌트에서 선언해놓고 부모에서 필요할 때 아래에서 위로 끌어올려서 쓰려는 실수를 하였고 state는 최대한 최상위 컴포넌트에서 선언하는 것이 좋다는 것을 깨달았다.
2편에서는?
- 댓글 컴포넌트화 + 피드마다 다른 댓글 데이터 뿌리기(props로 데이터 전달)
- Aside의 반복되는 부분 모듈화 시켜서 반복문
- props 구조 분해 할당
- 코드 리뷰 피드백
추가하고 싶은 기능
- 모달창
- 검색 기능
- 로딩화면 및 에러화면 생성
Author And Source
이 문제에 관하여(위스타그램 (1)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@po05360/위스타그램-1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)