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
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}
로 간단하게 구현해줄 수 있었다.useState()
가 비동기적이기 때문인데, 아래 코드와 같이 위에서 아래로 순차적으로 코드가 실행되는 것이 아니라, useState()의 변경함수 setInput()
이 실행되는 동시에 checkInput()
이 실행되어버리기 때문에 벌어진 에러였다.//이전 코드
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')
로 페이지 라우팅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는 최대한 최상위 컴포넌트에서 선언하는 것이 좋다는 것을 깨달았다.
추가하고 싶은 기능
경서님 최고링망고링~