3-8. 마무리 작업

이번 섹션에서는, 더 자연스러운 유저경험을 위한 부가적인 작업을 진행해보겠습니다.

스피너 보여주기

데이터를 로딩을 할 때, 화면에 빙글빙글 돌아가는 스피너를 보여주면 흐름이 더욱 자연스러워집니다. 스피너가 나타나는 케이스는 총 2가지 입니다.

  • 초기 메모를 불러올 때
  • 이전 메모를 불러올 때

우선, 스피너 컴포넌트부터 만들어보겠습니다.

처음부터 직접 만들지는 않고, 인터넷에 공개 된 svg-loaders 를 기반으로 만들어보겠습니다.

스피너의 SVG 코드는 다음과 같습니다:

<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
    <g fill="none" fill-rule="evenodd">
        <g transform="translate(1 1)" stroke-width="2">
            <circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
            <path d="M36 18c0-9.94-8.06-18-18-18">
                <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="0 18 18"
                    to="360 18 18"
                    dur="1s"
                    repeatCount="indefinite"/>
            </path>
        </g>
    </g>
</svg>

이걸 리액트에서 사용하기 위에서는, 조금 바꿔주어야하는데요. 내용을 보시면 fill-rule ,stroke-opacity 처럼 - 가 사이에 들어있는것들을 camelCase 로 전환하시면 됩니다.

그리고 스피너의 색상은 맨 윗줄의 stroke 를 수정하면 되고, 사이즈는 width, height 를 수정하면됩니다.

그럼 한번, 이걸 리액트 컴포넌트로 만들어보겠습니다.

src/components/Spinner.js

import React from 'react';
import styled from 'styled-components';
import oc from 'open-color';
import PropTypes from 'prop-types';

const Wrapper = styled.div`
    text-align: center;
    margin-top: 32px;
    margin-bottom: 32px;0
`;

const Spinner = ({visible}) => visible ? (
    <Wrapper>
        <svg width="64" height="64" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke={oc.gray[7]}>
            <g fill="none" fillRule="evenodd">
                <g transform="translate(1 1)" strokeWidth="2">
                    <circle strokeOpacity=".5" cx="18" cy="18" r="18"/>
                    <path d="M36 18c0-9.94-8.06-18-18-18">
                        <animateTransform
                            attributeName="transform"
                            type="rotate"
                            from="0 18 18"
                            to="360 18 18"
                            dur="1s"
                            repeatCount="indefinite"/>
                    </path>
                </g>
            </g>
        </svg>
    </Wrapper>
) : null;

Spinner.propTypes = {
    visible: PropTypes.bool
}

export default Spinner;

그 다음엔, App 컴포넌트를 리덕스에 연결하는 부분에서, state.pender.pendingpending 으로 연결해주세요.

src/containers/App.js - 하단

export default connect(
    (state) => ({
        cursor: state.memo.getIn(['data', 0, 'id']),
        endCursor: state.memo.getIn(['data', state.memo.get('data').size - 1, 'id']),
        pending: state.pender.pending
    }),
    (dispatch) => ({
        MemoActions: bindActionCreators(memoActions, dispatch)
    })
)(App);

그리고, Spinner 컴포넌트를 불러오고, render 함수를 다음과 같이 변경하세요. Spinner 컴포넌트를 MemoListContainer 앞뒤에 한개씩 넣어주면 됩니다.

src/containers/App.js - 상단

import Spinner from 'components/Spinner';

src/containers/App.js - render

    render() {
        const { pending } = this.props;

        return (
            <Layout>
                <Header/>
                <Layout.Main>
                    <WriteMemo/>
                    <MemoListContainer/>
                    <Spinner visible={pending['memo/GET_INITIAL_MEMO'] || pending['memo/GET_PREVIOUS_MEMO']}/>
                </Layout.Main>
                <MemoViewerContainer/>
            </Layout>
        );
    }

이렇게 하고나면 페이지에서 데이터를 불러오는중일 때 스피너가 렌더링될것입니다. 하지만, 지금은 서버가 여러분의 PC에 있기때문에 아주 짧은 시간동안만 보일겁니다.

스피너가 제대로 돌아가는지 한번 보고싶다면, 브라우저의 네트워크 속도를 제한시키시면 됩니다.

개발자 도구에서 Network 탭을열은다음에, No Throttling 을 클릭하여 GPRS 로 설정해보세요. 그리고 새로고침을 하시면, 스피너가 조금 더 오래 보이게될것입니다.

(테스팅이 끝났으면 다시 No Throttling 으로 설정하세요)

카드에 애니메이션 설정하기

카드가 생기고, 사라질때마다 애니메이션을 보여주도록 설정을 해보겠습니다. 우선, style-utils 에 다음과 같이 transition 관련 코드를 작성하세요.

src/lib/style-utils.js

import { css, keyframes } from 'styled-components';

// (...)

export const transitions = {
    stretchOut: keyframes`
        0%{
            opacity: 0;
            transform: scale(0.25,0.25);
        }
        100% {
            opacity: 1;
            transform: scale(1, 1);
        }
    `,
    shrinkIn: keyframes`
        0% {
            opacity: 1;
            transform: scale(1,1);
        }
        100% {
            opacity: 0;
            transform: scale(0.25,0.25);
        }
    `
}

stretchOut 은 확대가 되면서 투명도가 사라지고, shrinkIn 은 작아지면서 투명해집니다.

그 다음엔, MemoList 컴포넌트에서 CSSTransitionGroup 을 통하여 애니메이션을 설정하시면 됩니다. Wrapper 안에 .memo-enter, .memo-leave 클래스를 지정하고 CSSTransitionGroup 을 렌더링 하게 될 때에 transitionName 을 memo 로 설정하세요. 그리고 애니메이션 실행 시간은, 메모가 나타날대는 0.3초, 사라질때는 0.15 초로 지정하세요.

src/components/MemoList/MemoList.js

// (...)
import { media, transitions } from 'lib/style-utils';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';

const Wrapper = styled.div`
    display: block;
    margin-top: 0.5rem;
    font-size: 0px; /* inline-block 위아래 사이에 생기는 여백을 제거합니다 */

    ${media.mobile`
        margin-top: 0.25rem;
    `}

    .memo-enter {
        animation: ${transitions.stretchOut} .3s ease-in;
        animation-fill-mode: forwards;
    }

    .memo-leave {
        animation: ${transitions.shrinkIn} .15s ease-in;
        animation-fill-mode: forwards;
    }
`;

const MemoList = ({memos, onOpen}) => {
    const memoList = memos.map(
        memo => (
            <Memo
                key={memo.get('id')}
                memo={memo}
                onOpen={onOpen}
            />
        )
    );
    return (
        <Wrapper>
            <CSSTransitionGroup
                transitionName="memo"
                transitionEnterTimeout={300}
                transitionLeaveTimeout={150}>
                {memoList}
            </CSSTransitionGroup>
        </Wrapper>
    );
};

// (...)

이제 정말로 모든 기능을 다 완성하셨습니다! 다음 섹션에서는 간단한 최적화를 진행하고 히로쿠에 프로젝트를 배포하는 방법을 알아보겠습니다.

results matching ""

    No results matching ""