이번 주는 다사다난한 일주일이였다. 섹션2에서는 페어랑 함께 보낼 수 있었던 시간조차 없었기에 거의 자습이 대부분이였다. 이게 뭔가 싶었고, 페어프로그래밍이라는 시간을 활용하지 못해서 많이 아쉬웠다. 내가 모르는 부분들을 좀 채워나가고 싶었지만 이번 페어 운은 왜 이러는지🤬 섹션1이 차라리 더 괜찮았던 것 같다.
계속해서 쉬지 않고 공부하는 시간이 늘어나니 조금씩 지쳐가는 것 같다. HA2가 끝나고 하루 정도는 신나게 놀아서 충전을 해야 앞으로 공부를 더 잘할 수 있을 거라는 합리화와 놀 생각만 가득해서 공부가 너무너무 하기 싫은 그런 느낌이다.
아직까지 공부했던 내용들 중에서는 서버가 제일 재밌었다. 너무 짧게 끝난 것 같아 아쉽다. 섹션3에서는 백앤드 부분에 대한 공부를 더 하게 될테니 이번 보다는 더 재밌었으면 좋겠다. css는 나와 맞지 않다는 생각이 가득하기 때문이다😝 다음주면 벌써 HA2를 보게 되는데 저번 시험 볼 때는 많이 떨리고 떨어지지 않을까 걱정을 많이 했는데 지금은 아무 생각이 없다. 열심히 했으니까 통과되었으면 좋겠다는 생각밖에 없다.
9주차 배운 내용 중 정리하고 싶은 내용
CDD
CSS 방법론의 공통지향점
특징 | 장점 | 단점 | |
---|---|---|---|
CSS | 기본적인 스타일링 | - | 일관된 패턴을 갖기 어려움 |
SASS | 프로그래밍 방법론을 도입하여 컴파일 된 CSS를 만들어 내는 전처리기 | 변수 / 함수 / 상속 개념을 활용하여 재사용 가능. CSS의 구조화 | 전처리과정이 필요. 디버깅의 어려움. 컴파일한 CSS파일이 거대해짐 |
BEM | CSS클래스명 작성에 일관된 패턴을 강제하는 방법론 | 네이밍으로 문제해결. 전처리 과정 불필요 | 선택자의 이름이 장황하고, 클래스 목록이 너무 많아짐. |
CSS-in-JS (styled-component) | 컴포넌트 기반으로 CSS를 작성할 수 있게 도와주는 라이브러리 | CSS를 컴포넌트 안으로 캡슐화. 네이밍이나 최적화를 신경 쓸 필요가 없음 | 빠른 페이지 로드에 불리 |
CSS-in-JS
custom-component
props로 조건
const Button = styled.button`
background: ${(props) => (props.primary ? "yellow" : "white")}
color: ${(props} => (props.primary ? "white" : "yellow")}
`
const Tomato = styled(Button)`
color: tomato
border-color: tomato
`
=> primary가 있을 경우 / 없을 경우 / Tomato로 받는 경우 3가지로 변경 가능
*Modal*
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = () => {
setIsOpen(!isOpen)
};
return (
<>
<ModalContainer>
<ModalBtn onClick={openModalHandler}>
{isOpen === true ? 'Opened!' : 'Open Modal'}
</ModalBtn>
{isOpen === false ?
null :
<ModalBackdrop onClick={openModalHandler}>
<ModalView onClick={(e) => e.stopPropagation()}>
<div onClick={openModalHandler} className='close-btn'>×</div>
<div className='desc'>Hi Hello?</div>
</ModalView>
</ModalBackdrop>
}
</ModalContainer>
</>
);
};
*Toggle*
export const Toggle = () => {
const [isOn, setisOn] = useState(false);
const toggleHandler = () => {
setisOn(!isOn)
};
return (
<>
<ToggleContainer onClick = {toggleHandler} >
<div className= {`toggle-container ${isOn ? "toggle--checked" : ""}`}/>
<div className= {`toggle-circle ${isOn ? "toggle--checked" : ""}`}/>
</ToggleContainer>
{isOn ? <Desc>Toggle Switch ON</Desc> :<Desc>Toggle Switch OFF</Desc>}
</>
);
};
*Tab*
export const Tab = () => {
const [curIndex, setIndex] = useState(0)
const menuArr = [
{ name: 'Tab1', content: 'Tab menu ONE' },
{ name: 'Tab2', content: 'Tab menu TWO' },
{ name: 'Tab3', content: 'Tab menu THREE' },
];
const selectMenuHandler = (index) => {
setIndex(index)
};
return (
<>
<div>
<TabMenu>
{menuArr.map((section, index) => {
return (
<li key={index} className={curIndex === index ? 'submenu focused' : 'submenu'}
onClick={() => selectMenuHandler(index)}>
{section.name}
</li>
)
})}
</TabMenu>
<Desc>
<p>{menuArr[curIndex].content}</p>
</Desc>
</div>
</>
);
};
*Tab*
export const Tag = () => {
const initialTags = ['Hi', 'Hello?'];
const [tags, setTags] = useState(initialTags);
const removeTags = (indexToRemove) => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== indexToRemove)])
};
const addTags = (event) => {
if(event.key === "Enter" && event.target.value !== "" && !tags.includes(event.target.value)) {
setTags([...tags, event.target.value])
event.target.value = ""
}
}
return (
<>
<TagsInput>
<ul id='tags'>
{tags.map((tag, index) => (
<li key={index} className='tag'>
<span className='tag-title'>{tag}</span>
<span className='tag-close-icon' onClick={() => removeTags(index)}>
×
</span>
</li>
))}
</ul>
<input className='tag-input' type='text' onKeyUp={(event)=> event.key === 'Enter' ? addTags(event) : null}
placeholder='Press enter to add tags'/>
</TagsInput>
</>
);
};
*Autocommplete Component*
export const Autocomplete = () => {
const [hasText, setHasText] = useState(false);
const [inputValue, setInputValue] = useState('');
const [options, setOptions] = useState(deselectedOptions);
const [selected, setSelected] = useState(-1);
useEffect(() => {
if (inputValue === '') {
setHasText(false);
}
}, [inputValue]);
const handleInputChange = (event) => {
setInputValue(event.target.value)
setHasText(true);
setOptions(deselectedOptions.filter(el => el.includes(event.target.value)))
};
const handleDropDownClick = (clickedOption) => {
setInputValue(clickedOption)
setOptions( [clickedOption] )
};
const handleDeleteButtonClick = () => {
setInputValue('')
};
const handleKeyUp = (event) => {
if (event.getModifierState("Fn") || event.getModifierState("Hyper") || event.getModifierState("OS") || event.getModifierState("Super") || event.getModifierState("Win")) return; if (event.getModifierState("Control") + event.getModifierState("Alt") + event.getModifierState("Meta") > 1) return;
if (hasText) {
if (event.code === 'ArrowDown' && options.length - 1 > selected) {
setSelected(selected + 1);
}
if (event.code === 'ArrowUp' && selected >= 0) {
setSelected(selected - 1);
}
if (event.code === 'Enter' && selected >= 0) {
handleDropDownClick(options[selected]);
setSelected(-1);
}
}
};
return (
<div className='autocomplete-wrapper' onKeyUp={handleKeyUp}>
<InputContainer>
<input type='text' value={inputValue} onChange={handleInputChange}></input>
<div className='delete-button' onClick ={handleDeleteButtonClick}>×</div>
</InputContainer>
{hasText ? <DropDown handleComboBox ={handleDropDownClick} options={options} selected={selected}/> : null}
</div>
);
};
export const DropDown = ({ options, handleComboBox, selected }) => {
return (
<DropDownContainer>
{options.map((el, i)=> {
return <li key = {i} onClick={() => handleComboBox(el)}
className={selected === i ? 'selected' : ''}>{el}</li>
})}
</DropDownContainer>
);
};
*ClickToEdit*
export const MyInput = ({ value,handleValueChange }) => {
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false);
const [newValue, setNewValue] = useState(value);
useEffect(() => {
if (isEditMode) {
inputEl.current.focus();
}
}, [isEditMode]);
useEffect(() => {
setNewValue(value);
}, [value]);
const handleClick = () => {
setEditMode(!isEditMode)
};
const handleBlur = () => {
handleValueChange(newValue);
setEditMode(false);
};
const handleInputChange = (e) => {
setNewValue(e.target.value)
};
return (
<InputBox>
{isEditMode ? (
<InputEdit
type='text'
value={newValue}
ref={inputEl}
onBlur={handleBlur}
onChange={(e) => handleInputChange(e)}
/>
) : (
<span
onClick ={handleClick}
>{newValue}</span>
)}
</InputBox>
);
}
const cache = {
name: '메진',
age: 23
};
export const ClickToEdit = () => {
const [name, setName] = useState(cache.name);
const [age, setAge] = useState(cache.age);
return (
<>
<InputView>
<label>이름</label>
<MyInput value={name} handleValueChange={(newValue) => setName(newValue)} />
</InputView>
<InputView>
<label>나이</label>
<MyInput value={age} handleValueChange={(newValue) => setAge(newValue)} />
</InputView>
<InputView>
<div className='view'>이름 {name} 나이 {age}</div>
</InputView>
</>
);
};
Redux
: 상태 관리 라이브러리
=> 모든 state를 저장하고 있는 store
Redux의 3가지 원칙
Single source of Truth
하나의 저장소(store)에 애플리케이션의 모든 상태들이 객체 트리구조로 저장
State in read-only
state는 읽기 전용, 읽기 전용 상태를 변화하기 위해서는 액션 객체 전달
Changes are made with pure functions
액션에 의해 상태 트리가 변경시키기 위해서 순수 함수인 리듀서(reducer)를 작성
Redux 필수 요소
Store => 애플리케이션의 상태 저장 객체
두번째 인자도 존재하며, 두번째 인자는 초기 상태를 지정해주고 싶을때 사용
getState()를 통해 상태 접근
dispatch(action)를 통해 상태 수정
import { createStore } from 'redux'
...
const store = createStore(reducer)
Reducer => 액션(객체)을 받아서 새로운 state(객체)를 반환하는 역할
인자 2개 받음(이전상태, 액션)
const reducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
const newToDoObj = {
text: action.text
};
return [newToDoObj, ...state];
case DELETE_TODO:
const cleaned = state.filter((toDo) => toDo.id !== action.id);
return cleaned;
default:
return state;
}
};
+ 인수들을 변경X
+ API호출이나 라우팅처럼 사이드 이펙트를 야기하는것들을 사용X.
+ Date.now()나 Math.random() 같이 순수하지 않은 함수를 호출X.
+ spread syntax나 assign을 사용하여 새로운 객체를 리턴
Action => 애플리케이션에서 저장소로 보내는 데이터 묶음(JS 객체)
액션은 반드시 어떤 형태의 액션이 실행될 지 나타내는 type 속성 필요 (별도의 모듈로 분리 가능)
store.dispatch()를 통해 보낼 수 있음
const ADD_TODO = "ADD_TODO"; --> 혼동 방지, 오타 방지
const DELETE_TODO = "DELETE_TODO";
const addToDo = (text) => {
return {
type: ADD_TODO,
text,
};
};
const deleteToDo = (id) => {
return {
type: DELETE_TODO,
id,
};
};
*액션생산자 : 액션을 만드는 함수(액션과 액션생산자는 다름)*
combineReducer => combineReducers의 역할은 모든 리듀서들을 한곳에 모아 객체로 만들 수 있는 것
assign과 spread syntax와 유사하게 모아 주는 역할
import { combineReducers } from "redux"
import settingReducer from "./setting"
import videoReducer from "./video"
import searchReducer from "./search";
export default combineReducers({
searchReducer,
settingReducer,
videoReducer,
})
<흐름 정리>
store에 모든 state들이 저장
↓
react component가 state를 요구
↓
action에서 dispatch(action)을 통해서 reducer로 전달
↓
순수함수인 reducer에서 action을 받아서 새로운 객체(새 상태)로 반환
↓
store에 새 상태를 넘김