Skip to content

Latest commit

 

History

History
166 lines (140 loc) · 9.34 KB

20211221.md

File metadata and controls

166 lines (140 loc) · 9.34 KB

React

지난 시간 복습

  • 파일명과 해시문자열을 넣은 className을 동적으로 부여하여 컴포넌트가 로컬스코프를 갖는 것처럼 CSS를 사용하도록 해주는 모듈 CSS 방식 (해시값은 css-loader가 부여해준다)
    • 이를 통해 컴포넌트의 독립적 스타일을 고수할 수 있다.
  • 화살표함수로 컴포넌트를 정의한 경우 컴포넌트 이름이 anonymous로 나와서 디버깅하기 힘들다. displayName으로 이름을 설정해주거나 named function으로 정의해주자.
  • 리액트 컴포넌트가 필수적으로 갖는 static property: displayName, defaultProps, propTypes

Prop types

  • 우리는 prop type check를 안하는 것으로 설정해두었었지만 원래는 prop type를 조건처리문으로 검사해주어야 한다.
  • 컴포넌트의 propTypes라는 객체 속에 prop이름과 동일한 메서드를 정의하면 인자로 props, propName, componentName이 들어온다.
  • type을 체크해주고 객체인지 확인해주는 유틸함수를 활용한다.
import style from './Heading.module.css';
import { typeIs, isString } from '../utils';

const Heading = ({ as: Comp, className, children, ...restProps }) => {
  return (
    <Comp className={classNames(styles.headline, className)} {...restProps}>
      <span className={styles.SK}>SK</span>
      <span className={styles.title}>{children}</span>
    </Comp>
  );
};

Heading.propTypes = {
    as(props, propName, componentName) {
        if (!isString(props[propName])) {
            throw new Error(`${componentName} 컴포넌트에 전달된 ${propName}은 string이어야 합니다.
                전달된 prop은 ${typeIs(props[propName])}입니다`);
        }
        if (isUndefined(props[propName])){
            throw new Error(`${componentName} 컴포넌트에 ${propName}은 필수 항목입니다.`);
        }
    },
    // ...
}
  • propTypes 유틸을 따로 빼서 넣어주는 것이 더 용이할 수도 있다.
    • 메서드는 결국 해당 함수이름를 키로 하는 프로퍼티의 값으로 함수가 오는 것이므로 propTypes 유틸에서 만들어둔 객체 속 메서드를 전달해주면 props, propName, componentName을 받아 처리하게 한다.
// utils/propTypes.js

export const propTypes = {
    string(props, propName, componentName) {
    let value = props[propName];
    if (!isString(value)) {
      throw new Error(
        `${componentName} 컴포넌트에 잘못된 ${propName} prop이 전달되었습니다.`
      );
    }
  },
};

// Heading.js
import { propTypes } from 'utils';

const Heading = ({ as: Comp, className, children, ...restProps }) => {
    // ...
};

Heading.propTypes = {
    as: propTypes.string,
    // ...
}
  • 타입스크립트를 쓴다면 이런 것을 할 필요가 없지만 우리는 JS니까 해줘야 한다.

Dialog: modal 만들기

Tabbable vs. Focusable

  • tabbable element는 사용자가 tab 키를 통해 접근할 수 있는 요소이다.
    • tabindex에 0 또는 양수 값을 갖는 요소 및 focus가 기본적으로 가능한 form 요소들이 있다.
    • 그렇다면 focusable과는 어떤 차이가 있을까?
    • tabindex에 -1을 주면 사용자가 tab키로 접근할 수는 없지만 js를 통해 focus()로 포커스를 줄 수는 있다.
  • 위와 같이 focusable 요소는 사용자가 tab키로 접근할 수는 없지만 js를 통해 focus를 줄 수 있는 요소까지를 포함한다.
    • href 어트리뷰트를 갖는 a와 area 태그
    • button, input, select, textarea, iframe 요소
    • control 어트리뷰트를 갖는 video, audio 요소
    • summary, details 요소
    • tabindex, contenteditable 어트리뷰트를 갖는 경우
  • 위에 해당하는 요소들을 모두 querySelectorAll로 찾아와서, 모달창 안에서만 키보드 네비게이션이 돌도록 작업해줄 것이다.

KeyBoard Trap 만들기

  • esc 키를 누르면 닫히고 모달 다이얼로그 열기 버튼에 focus를 가게 해줄 것이다.
  • 먼저 focusable 요소인지 판별하는 유틸함수를 만든다. (왜 focusable element를 데려와서 이걸 다시 하는 것일지 야무님께 물어보기)
  • 초점 이동 가능한 HTML 요소들을 모두 가져와 focusable인지 확인 후 배열에 넣는 유틸함수를 만들고, 이 배열의 처음과 끝에 해당하는 요소들에서 tab 또는 shift+tab을 눌렀을 때 서로에게 focus가 가도록 해준다.
  • ref의 current에서 자기 자손 요소들의 focusable 요소를 모두 가져와 배열로 받아온다.
  • firstFocusableElement와 lastFocusableElement를 배열의 0번 인덱스와 length-1번 인덱스로 받아 할당한다.

외부 요소를 자식으로 렌더링하기

  • vue.js에서는 slot이라는 요소 안에 외부에서 지정한 children을 끼워넣어준다. 또한 named slot을 통해 원하는 위치에 넣을 수 있도록 해준다.
  • 리액트에서는 children을 통해 custom component의 컨텐츠 영역에 들어온 다른 요소들을 렌더링 해줄 수 있지만, 지정된 자리에 넣고 싶은 경우 compound component 패턴으로 이를 구현할 수 있다.
    • vue.js에는 미리 컴포넌트 안에 slot을 마련해 두었기 때문에 외부에서 children을 넣어주지 않아도 마크업 상 container slot이 남아 있는데, 리액트는 밖에서 조립하기 때문에 children이 없는 경우 마크업이 깔끔해진다
  • Dialog라는 컴포넌트 안에 static 멤버로 컴포넌트를 리턴하는 메서드를 만들어주자
export class Dialog extends React.Component {
    
    static Head({ className = '', ... restProps }){
        return <div className={classNames("head", className)} {...restProps}/>;
    }

    // property에 함수를 넣으면서, named function으로 만들어주면 디버깅할 때 편하다.
    static Main = function DialogMain({ as: Comp ='article', className, ... restProps}) {
        return <Comp className={classNames('content', className)} ... restProps />;
    }
    
    // Foot, CloseButton 등도 위와 같이 넣어줄 수 있다.
}

// 활용할 때는 아래와 같이 끼워넣어준다.

export function App() {
    return (
        <Dialog>
          <Dialog.Head>
            <h2>다이얼로그 제목<h2>
          </Dialog.Head>
          <Dialog.Main>
            <p>다이얼로그 내용</p>
          </Dialog.Main>
        </Dialog>
    )
}

로딩 접근성

  • 로딩 중이라는 것이 시각적으로만 표현되고 텍스트로 상태가 제공되지 않으면 스크린리더로만 상황을 파악해야 하는 유저는 오류라고 생각해서 계속 새로고침을 할 수 밖에 없다.
  • 로딩 스피너도 Portal을 통해 가장 바깥에 container을 두고 role="alert" aria-live="assertive"로 스크린리더가 읽을 수 있도록 제공해야 한다.
    • 로딩이 시작될 때 container에 로딩시작 문구가 삽입되면 aria-live가 assertive이기 때문에 스크린리더가 다른 작업을 중단하고 바로 읽는다.
    • 로딩을 마치면 로딩 시작 문구를 삭제하고 로딩을 마쳤다는 문구를 삽입한다.

Hook

왜 React는 함수 컴포넌트로의 패러다임 전환을 이끄는 Hook을 만들었을까?

  1. Wrapper Hell: 고차 컴포넌트(추상레이어)로 한개씩 싸야 했던 클래스 컴포넌트의 문제. 몇 개의 중첩만 되어도 wrapper이 너무 많아진다.
  2. Huge Component: 코드가 비대해진다.
  3. Confusing Classes: 코드가 복잡하고 어렵다
  • 이런 문제로 인하여 refactoring과 TDD가 어려워지기 때문에, 기존의 class component를 대체할 function component를 만들어 코드를 심플하게 만들자는 의도
  • 근데 function component는 매번 재실행되므로 state를 가질 수 없는데...?
    • component 내의 상태나 ref를 추출하여 다른 component에서 재사용할 수 있게 하는 hook을 통해 가능하다!
  • React Hook은 캡슐화된 local stated와 logic을 외부로 꺼내 다른 컴포넌트에서도 재사용할 수 있게 해준다.

React Hook은 무엇인가

  • hook은 제약사항이 있는, 일반함수보다 조금 더 특별한 함수이다.
    • 단방향 데이터 흐름을 가능케 한다.
    • 배열에서 상태를 꺼내와 사용하게 한다.
  • Hook의 장점
    • wrapper hell 없는 clean tree를 만들어준다.
    • 코드를 쉽고 slim하게 한다.
    • 클래스는 최신문법이기 때문에 polyfill로 변환해줘야 했는데 function은 그럴 필요가 없기 때문에 성능최적화된 형태이다.
  • Hook의 단점
    • 클래스와 함수를 구분지었던 것이 state의 유무였는데 다 function으로 쓰면 어떤 것이 stateful인지 stateless인지 명확하지 않다. (하지만 이게 크게 문제될 것은 아니다.)

useState

  • 초기값을 넣어 호출하면 state와 이 state를 조작할 수 있는 함수를 담은 배열을 반환한다.
  • 하나의 state를 하나씩 관리해서 관심사를 분리할 수 있다.

useEffect

  • componentDidMount, componentDidUpdate, componentWillUnmount의 역할을 수행한다.
  • 첫 번째 인자로 콜백함수를 전달하고, 두 번째 인자로는 dependency Array를 전달한다.
    • 콜백함수 몸체는 component가 mount되거나 update 될 때 실행되며 함수를 리턴하면 이를 unmount 시점에 실행한다.
    • dependencyArray에 특정 state를 넘기면 그 state가 변경될 때만 실행한다.