코드스테이츠 9주차 -[React] 컴포넌트 디자인 / [React] 상태관리

엄혜진·2021년 8월 15일
2

CodeStates

목록 보기
9/15
post-thumbnail

이번 주는 다사다난한 일주일이였다. 섹션2에서는 페어랑 함께 보낼 수 있었던 시간조차 없었기에 거의 자습이 대부분이였다. 이게 뭔가 싶었고, 페어프로그래밍이라는 시간을 활용하지 못해서 많이 아쉬웠다. 내가 모르는 부분들을 좀 채워나가고 싶었지만 이번 페어 운은 왜 이러는지🤬 섹션1이 차라리 더 괜찮았던 것 같다.
계속해서 쉬지 않고 공부하는 시간이 늘어나니 조금씩 지쳐가는 것 같다. HA2가 끝나고 하루 정도는 신나게 놀아서 충전을 해야 앞으로 공부를 더 잘할 수 있을 거라는 합리화와 놀 생각만 가득해서 공부가 너무너무 하기 싫은 그런 느낌이다.
아직까지 공부했던 내용들 중에서는 서버가 제일 재밌었다. 너무 짧게 끝난 것 같아 아쉽다. 섹션3에서는 백앤드 부분에 대한 공부를 더 하게 될테니 이번 보다는 더 재밌었으면 좋겠다. css는 나와 맞지 않다는 생각이 가득하기 때문이다😝 다음주면 벌써 HA2를 보게 되는데 저번 시험 볼 때는 많이 떨리고 떨어지지 않을까 걱정을 많이 했는데 지금은 아무 생각이 없다. 열심히 했으니까 통과되었으면 좋겠다는 생각밖에 없다.


9주차 배운 내용 중 정리하고 싶은 내용

[React] 컴포넌트 디자인


  • CDD
    -> 부품단위로 UI 컴포넌트를 만들어 나가는 개발 ex) Storybook(독자적인 개발 환경에서 실행 가능)


  • CSS 방법론의 공통지향점
    -> 코드의 재사용 / 코드의 간결화 (유지보수 용이) / 코드의 확장성 / 코드의 예측성 (클래스명으로 의미 예측)
특징장점단점
CSS기본적인 스타일링-일관된 패턴을 갖기 어려움
SASS프로그래밍 방법론을 도입하여 컴파일 된 CSS를 만들어 내는 전처리기변수 / 함수 / 상속 개념을 활용하여 재사용 가능. CSS의 구조화전처리과정이 필요. 디버깅의 어려움. 컴파일한 CSS파일이 거대해짐
BEMCSS클래스명 작성에 일관된 패턴을 강제하는 방법론네이밍으로 문제해결. 전처리 과정 불필요선택자의 이름이 장황하고, 클래스 목록이 너무 많아짐.
CSS-in-JS
(styled-component)
컴포넌트 기반으로 CSS를 작성할 수 있게 도와주는 라이브러리CSS를 컴포넌트 안으로 캡슐화. 네이밍이나 최적화를 신경 쓸 필요가 없음빠른 페이지 로드에 불리



  • CSS-in-JS
    • 어떤 컴포넌트가 렌더링 되었는지 추적해서 해당 컴포넌트에 대한 스타일을 자동 삽입
    • 스스로 유니크한 className을 생성
    • 컴포넌트를 사용하지 않은 부분 삭제 → 이에 대한 스타일 속성도 삭제
    • props나 전역 속성을 기반으로 컴포넌트에 스타일 속성 부여



  • 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'>&times;</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)}>
                &times;
              </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}>&times;</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>
    </>
  );
};



[React] 상태관리


  • Redux : 상태 관리 라이브러리
    => 모든 state를 저장하고 있는 store


  • Redux의 3가지 원칙

    1. Single source of Truth
      하나의 저장소(store)에 애플리케이션의 모든 상태들이 객체 트리구조로 저장

    2. State in read-only
      state는 읽기 전용, 읽기 전용 상태를 변화하기 위해서는 액션 객체 전달

    3. 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에 새 상태를 넘김



0개의 댓글