팀프로젝트를 진행하면서 적용하면 좋을 적절한 애니메이션을 고민하던 중,

아주 유용하게 사용할 수 있을 것 같은 라이브러리를 발견했다.

 

React-Spring의 가장 큰 장점은 애니메이션이 실행될때 re-render가 되지 않는다는 점이다.

일반적으로 react에서 useState 값을 사용하여 클릭 상태 변화에 따라 애니메이션을 다시 그려야하므로 리렌더 될 수 밖에 없다.

하지만 useSpring을 사용하면 해당 과정 없이 애니메이션을 동작시킬수 있다고 한다.

눈에 보이는 효과는 아직 없겠지만, 관리해야 할 useState가 많거나, 애니메이션을 많이 사용하게되면 매우 유용할 것 같다

 

import * as S from "./index.styled";
import { animated, useSpring } from "@react-spring/web";

export default function Page1() {
 
 const springs = useSpring({
    from: { x: 0 },
    to: { x: 100 },
  })


  return (
    <S.Container>
      <S.Wrapper>
        <animated.div
          style={{
            width: 80,
            height: 80,
            background: "#ff6d6d",
            borderRadius: 8,
            ...springs,
          }}
        />
      </S.Wrapper>
    </S.Container>
  );
}

일반적인 태그가 아니라 <animated.div/> 태그 의 style 속성으로 스타일할 수 있고, 애니메이션을 주기 위해서 useSpring을 사용해야한다. 이렇게 사용하면 첫 페이지 렌더링시 x가 100만큼 1번 이동한다. ( 배경 및 배치는 emotion/styled 사용 중 )

평소 사용하는 hooks 처럼 useSpring안에 원하는 애니메이션을 넣어주고 태그안에 ...spring을 바인딩시켜주면 끝이다!

 

클릭시 원하는 애니메이션을 작동시켜주는 것은 useSpring안에 api 라는 메서드를 추가하고, 함수로 변경하면된다.

  const [springs, api] = useSpring(() => ({
    from: { x: 0, y: 0 },
  }));

  const handleClick = () => {
    api.start({
      from: {
        x: 0,
        y: 0,
      },
      to: {
        x: 100,
        y: 100,
      },
    });
  };

handleClick은 animated.div 태그 onClick에 적용

그럼 React-Spring을 이용해서 아래 애니메이션을 만들어보자

CSS와 React만 사용해도 만들 수 있을 것 같지만 이번에는 React-Spring을 사용하면 좋을 것 같았다.

기본원리는 두 이미지가 동일한 위치와 사이즈로 있으며 투명도가 서로 0 -> 1, 1 -> 0 으로 변경되는고 x 축으로 180도씩 돌아가게 하는 것이다.

 

내가 사용하는 css 라이브러리인 emotion/styled와 예시 코드로 제공되는 css 방법이 다르기때문에 약간의 변화가 필요했다... 

useSpring 에서 제공하는 태그를 사용한 후 style을 적용해야 하므로 css에서 Wrapper 의 div, Wrapper의 span으로 구분하였다.

import * as S from "./index.styled";
import { animated, useSpring } from "@react-spring/web";
import { useState } from "react";

export default function Page1() {
  const [rotate, setRotate] = useState(false);
  const { transform, opacity } = useSpring({
    opacity: rotate ? 1 : 0,
    transform: `perspective(500px) rotateX(${rotate ? 180 : 0}deg)`,
  });

  const onClickRotate = () => {
    setRotate((prev) => !prev);
  };

  return (
    <S.Container>
      <S.Wrapper onClick={onClickRotate}>
        <animated.div
          style={{ opacity: opacity.to((o) => 1 - o), transform }}
        />
        <animated.span style={{ opacity, transform, rotateX: "180deg" }} />
      </S.Wrapper>
    </S.Container>
  );
}
import styled from "@emotion/styled";
import * as GS from "../../../../theme/global";

export const Container = styled.div`
  width: 100%;
  height: 100vh;
  background-color: ${GS.base.primary};
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
`;

export const Wrapper = styled.div`
  width: 82%;
  height: 80%;
  position: relative;

  div {
    display: flex;
    width: 500px;
    height: 300px;
    background-color: black;
    position: absolute;
    opacity: 1;
    cursor: pointer;

    ::before {
      content: "kk-jae 앞면입니다.";
      color: white;
    }
  }

  span {
    display: flex;
    width: 500px;
    height: 300px;
    background-color: pink;
    position: absolute;
    opacity: 0;
    cursor: pointer;

    ::before {
      content: "kk-jae 뒷면입니다";
    }
  }
`;

 

애니메이션을 다채롭게 적용하기 위해서는 css 셀렉터도 다시 한번 학습할 필요가 있을 것 같다.

 

 

React-Spring  https://www.react-spring.dev/

 

React Spring

Bring your components to life with simple spring animation primitives for React

www.react-spring.dev

구조분해 할당(비구조화 할당)

const aboutMe = {
		name : "정혜원",
		age : 23456,
		position : "frontend",
		adress : "성북구",
		favoriteFood : "치킨",
	}

위에 보시는 것 과 같이 객체가 하나 있습니다.

만일, 저 객체 안에 있는 값들을 하나씩 모두 분해해서 변수에 할당을 해줘야 하는 상황이라면 어떨까요?

아마 여러분들은 객체의 키값에 접근해서 할당을 해주실 것 입니다. 아래와 같이요.

const name = aboutMe.name

const age = aboutMe.age

const position = aboutMe.position

const address = aboutMe.address

const favoriteFood = aboutMe.favoriteFood

생각만해도 굉장히 귀찮고, 보는것만으로도 가독성이 매우 떨어지지 않나요?

그런데 위에서 한 여러번의 선언과 할당과정을 한번에 처리하는 방법이 있습니다.

바로 **구조분해 할당(비구조화 할당)** 입니다.

구조분해 할당에는 배열과 객체의 구조분해 할당이 있으며 아래에서 자세히 알아보도록 하겠습니다.


배열의 구조 분해할당

위에서 말씀 드렸듯, 구조분해 할당은 여러번의 선언과 할당과정을 한번에 처리하는 과정이라고 말씀 드렸습니다.

따라서 **배열의 구조분해 할당**은 배열 내부의 값을 여러 변수에 나눠 담는 과정을 한번에 처리한다 생각하시면 됩니다.

const numbers = [0, 1, 2];

**let [ zero, one, two ] = numbers;** // 선언과 동시에 할당

즉,

const zero = number[0]

const one = number[1]

const two = number[2]

와 같이 여러번에 거친 과정을 배열의 구조분해 할당을 이용하시면 위와같이 한번에 처리가 가능합니다.

우리가 자주 사용해왔던 배열의 구조분해 할당!

우리는 이미 자주 사용하는 배열구조분해가 있습니다.

예상하신 분들도 계시겠지만, 바로 useState 를 선언할 때 입니다.

**const [state,setState] = useState()**

이제 보이시나요? 우리는 지금까지 배열의 구조분해 할당을 사용해서 useState를 사용하고 있었습니다.

<aside> 💡 할당하려는 변수가 더 많거나 적으면? → 적은 경우에는 더이상 할당되지 않고, 많은경우에는 undefined가 할당 됩니다.

// 적을 경우
let [one,two] = [1,2,3,4]

	//결과값은 아래와 같이 구조분해 된다.
	let one = 1
	let two = 2
	
// 많을 경우
let [one,two,three,four] = [1,2,3]
	//결과값은 아래와 같이 구조분해 된다.
	let one = 1
	let two = 2
	let three = 3
	let four = undefined

</aside>

💡배열 구조분해 어떤상황에서 사용할까요?

배열에서 따로 담아둬야 할 값이 2개 이상이라면 한번에 구조분해 할당을 이용해 선언과 할당을 진행하면 편리합니다.

예를 들면 아래와 같은 상황일때 구조분해 할당을 이용하시면 편리합니다!

// 2,5,7번 인덱스 값을 뽑아 변수에 담아야 한다.
let arr = [1,2,3,4,5,6,7,8,9]
let [ index2,index5,index7 ] = [ arr[2],arr[5],arr[7] ]

객체의 구조분해 할당

객체의 구조분해 할당은 배열의 구조분해 할당과는 조금 다릅니다.

객체의 구조분해 할당에서의 변수명을 객체의 key값으로 선언해주셔야 합니다.

const aboutMe = {
		name : "정혜원",
		age : 23456,
		position : "frontend",
		adress : "성북구",
		favoriteFood : "치킨",
	}

// 이름과 나이만 뽑아서 새로운 변수에 담아주고 싶을 때
const { name,age } = aboutMe

//결과
// name = "정혜원"
// age = 23456

또한 배열에서는 선언과 할당이 따로 일어나도 상관이 없었지만, 객체는 선언과 할당이 동시에 일어나야 합니다.

let [ name ]

[ name ] = ”정혜원”

배열은 위와 같이 선언과 할당을 따로 해도 문제가 없지만, 객체는 안된다는 뜻 입니다.

<aside> 💡 만일 없는 키를 선언하고 할당하게 되면? → 없는키는 할당값이 undefined로 들어가게 됩니다.

const aboutMe = {
		name : "정혜원",
		age : 23456,
		position : "frontend",
		adress : "성북구"
	}

// hight라는 없는 key를 선언하고 할당하기
const { hight, age } = aboutMe

// 결과
// hight = undefined
// age = 23456

</aside>


Rest 파라미터

우리가 특정 객체에서 지우고싶은 데이터가 있을 때 어떻게 할까요?

과연 **delete**를 사용해 원본 데이터를 삭제할까요?

사실 원본을 건드리는 일은 그리 바람직하지 못합니다.

원본이 어디서 어떻게 사용되고 있을 지 모르기 때문에 원본을 사용하는 곳에서 예상치 못한 에러를 직면하게 될 수 있습니다.

따라서 원본을 건들이지 않고 삭제하기 위해 rest 파라미터를 이용합니다.

rest 파라미터는 구조분해 할당과 함께 사용합니다.

const me = {
	name: "정혜원",
	age: 123,
	hobby: "공부",
	dream: "30년차 자바스크립트 개발자"
	}

// 구조분해 할당
**const { money, hobby, ...rest } = child**

이렇게 적으면 rest변수에는 dream과 hobby를 제외한 모든게 들어가게 됩니다.

즉, rest를 호출하게 되면 dream과 hobby를 제외한 프로퍼티가 담긴 객체가 호출되는 것 입니다.

따라서 rest 파라미터를 사용하게 되면, 삭제할 프로퍼티를 제외한 모든 데이터를 간편하게 골라낼 수 있습니다.

 

 

 

 

출처 : 코드캠프

 

 

1. Pagination 

페이지의 번호를 클릭하여 게시물을 보여주는 방식

 

일반적으로 이전, 다음 버튼이 존재하고 보여주는 게시글에 숫자가 강조된다.

얼마나 늘어날지 모르기때문에 map을 사용하여 작성한다.

 

 

2. Infinite Scroll 

스크롤을 내리면 나머지 게시글을 화면에 띄우는 방식

 

InfiniteScroll 라이브러리 (react-infinite-scroller) 를 사용하여 구현한다.

 

두가지 방법 모두 페이지의 컨셉에 맞게 사용하면 된다.

 

 

useRef 는 리액트제공하는 Hooks 기능 중 하나이다.

 

useRef를 선언 후 태그에 ref={ 변수명 } 로 지정할 수 있다.

마치 태그의 고유 이름인 id를 주는 것과 비슷하다.

 

가장 기본적인 기능은 input의 focus를 맞추는데 사용할 수 있다.

 

기본적으로 페이지에 접속하거나 특정 버튼 및 동작을 하고 특정 input에서 깜빡거리는것을 볼 수 있다

useRef() 를 활용하여 가능하다.

 

import { useRef } from "react";

export default function USERefPage() {
  const writerInput = useRef(null);

  const onClickRefTest = () => {
    writerInput.current.focus();
    console.log(writerInput);
  };
  return (
    <div>
      <div>
        작성자 : <input ref={writerInput} />
      </div>
      <div>
        내용 : <input />
      </div>
      <div>
        비밀번호 : <input />
      </div>
      <button onClick={onClickRefTest}>ref 버튼</button>
    </div>
  );
}

 

ref 버튼을 클릭하면 지정해놓은 "작성자"의 input으로 focus 된다.

 

이 기능은 스타일을 꾸밀때 활용하면 좋을 것 같다.

Lifecycle 은 클래서형에서는 compent의 메서드로, 함수형에서는 useEffect로 사용할 수 있다.

 

1. 그리기
2. 그리고 난 뒤 실행
3. 그리도 난 뒤 변경됐을 때
4. 그리고 난 뒤 사라질때

 

위의 예시처럼 a 실행시키고 바로 다음 동작을 명령시킬때 해당 동작만을 실행시킨다. 

 

이것은 코드 작성을 간편하게 하는 것도 있지만, 다음 동작을 위해 전체 코드를 리렌더링하지 않고,

원하는 부분만 인식시켜 컴퓨터를 더 효율적으로 동작시키는 장점도 있다.

 

시작하기 전에 useEffect는 전체코드를 실행한 뒤 동작되는 메서드 인 점을 기억하자

 

class형 lifecycle

 

  componentDidMount(): void {
    // Component 의 기능
    console.log("그려지고 나서 실행!!");
  }
  componentDidUpdate(): void {
    // Component 의 기능
    console.log("변경되고 나서 실행!!");
  }
  componentWillUnmount(): void {
    // Component 의 기능
    console.log("사라지기전에 실행");
    // 예시) 채팅방 나가기 API, 나가기전에 마지막으로 할 것들
  }

 

componentDidMount : 함수가 모두 그려지면 바로 실행된다. (최초 1회)

componentDidUpdate : 함수가 변경될 때마다 무한대로 실행된다.

componentWillUnmount : 해당 함수를 벗어나기 전에 1번 실행 ex) 채팅방 나가기 등

 

전체 함수에 적용하거나 함수안에 적용시켜서 사용이 가능할 것 같다.

  onClickCountUp = (): void => {
    // console.log(this.state.count);
    this.setState({
      count: 1,
    });
  };

  onClickMove = (): void => {
    void Router.push("/");
  };

  render(): JSX.Element {
    return (
      <>
        <div>{this.state.count}</div>
        <button onClick={this.onClickCountUp}>카운트 올리기!!</button>
        <button onClick={this.onClickMove}>나가기!!</button>
      </>
    );
  }

 

함수형 lifecycle

 

함수형은 Hooks 를 활용한 useEffect를 사용할 수 있다.

 

// 기본 모양

useEffect( () => { 
	실행시킬 함수 작성 
    },[ 반복 횟수 작성 ]);

 

" 반복 횟수 작성 " 공간에 아무것도 작성하지 않으면 의존성 배열로 초기에 한번만 실행되고 실행되지 않습니다.

" 반복 횟수 작성 " 공간 자체가 없을 경우 무엇이 바뀌던 바뀔때마다 무한적으로 다시 실행 시킵니다.

 

이때 componentDidUpdate와 다른점은, 처음 Mount 됐을 때도 한번 실행된다는 점입니다.

 

// useEffect 활용

useEffetc( () => {
	console.log("그려지고 나서 실행!!")
    
    return () => {
    console.log("사라지기전에 실행!!");
    };
});

이렇게 "실행시킬 함수 작성" 공간에 return 을 추가하게 되면, 마치 class형의 componentWillUnmount  와 비슷하게 동작합니다.

 

 

useEffect의 잘못된 사용 방법 

useEffect는 코드 순서와 상관없이 함수를 모두 그린 후 실행되는 특징을 가지고있습니다.

그럼 만약에 useEffect안에서 set을 사용하여 변수의 값을 변경시키면 어떻게 될까?

 

  useEffect(() => {
    setCount((prev) => prev + 1);
  }, [count]);

위 함수처럼 count 값이 변경될때마다 useEffect를 반복하고, useEffect는 계속해서 count + 1을 실행하므로 무한 루프에 빠지게 된다.

지금까지 자바스크립트, 타입스크립트를 학습하면서 아래 형식을 기본적으로 사용하였다.

 

export default function 함수명 () {
 // 자바스크립트
	return ();
 // HTML   
    }
    
    // 함수형 컴포넌트

예전에는 함수형 컴포넌트에서 상태를 관리할수가 없었는데, Hook의 출현으로 상태 관리가 가능해졌다. useEffect도 없었으니 렌더링 전후로 어떤 작업을 하고 싶어도 할수가 없었다. 그래서 사용한 것이 클래스형 컴포넌트이다.

 

 

그래도 클래스형 컴포넌트를 활용할 기회가 생길 수 있으니, 어떻게 사용하는지 알아보자

 

1.  사용 방법 :  함수형과 다르게 클래스형은 함수와 변수를 사용할때 const, let, function 등을 사용하지 않습니다.

class Monster {
  power = 50;
  attack(): void  {
    console.log("공격합니다!!");
  }
}

2.  상속과 오버라이딩 : 미리 선언한 함수에 기능을 추가하거나 다시 정의할 수 있습니다. 

// 클래스 상속 : 기존 클래스에 기능을 추가해서 만든 클래스
class SuperMonster extends Monster {
  run(): void {
    console.log("도망가자!!");
  }

  // 오버라이딩: Monster의 메소드를 다시 정의해서 덮어씌운다.
  attack(): void {
    console.log("슈퍼 몬스터 필살기!!");
  }
}
const monster = new Monster();
console.log(monster.power); // 50
monster.attack(); // 공격합니다!!

const supermonster = new SuperMonster();
console.log(supermonster.power); // 50
supermonster.attack(); // 슈퍼 몬스터 필살기!!

 

class형 컴포넌트 사용 예시

import { Component } from "react";

export default class ClassCounterPage extends Component {
  // extends를 사용하여 리액트 기능 중 하나인 Component를 상속 받습니다.
  state = {
    count: 5,
  };

  // 화살표 함수로 변경하여 this 사용
  onClickCountUp = (): void => {
    // 변경전 onClickCountUp(): void {
    console.log(this.state.count);
    this.setState({
      count: 1,
    });
  };

  // render 작성 (정해진 규칙)
  render(): JSX.Element {
    return (
      <>
      {/* this는 class 자기자신을 뜻합니다. */}
        <div>{this.state.count}</div>
        
        {/* 직접 바인딩할 때는 onClick={this.onClickCouter.bind(this)} 라고 적어주셔야 합니다. */}
        <button onClick={this.onClickCountUp}>카운트 올리기!!</button>
      </>
    );
  }
}

 

this

함수형에서는 보이지 않던 this가 자주 사용된 것을 볼 수 있다.

이때 this 는 선언했던 변수를 함수에 가져오는 방법이라고 생각하면 좋을 것 같다.

 

그러나 this를 어디서 실행하느냐에 따라 변화하는 이슈가 있다 (동적 this)

 

변수를 가져오는 함수를 선언할때 함수표현식으로 선언하면 정상적으로 이전의 this를 받아오지 못한다. (this 자체가 함수로 변경되는 이슈가 생김 ex) onClickCounter 클릭시 this가 onClickCounter로 변경됨

 

그래서 함수를 선언할때 화살표 함수로 선언하거나 .bind(this)를 사용하여 this를 class로 고정해야한다.

 

변수를 useState로 담게 되면 state는 함수가 끝날때 마지막으로 선언된 값을 가져온다.

 

아래와 같이 선언 할 경우 마지막을 제외한 결과 값들은 임시 저장공간에 저장만 되며,

버튼을 클릭했을때 결국에는 마지막의 +2만 적용된다.

    function onClickCountUp():void{
      setCount(count + 2)
      setCount(count + 1)
      setCount(count + 3)
      setCount(count + 5)
      setCount(count + 2)
      //setState의 특징으로(임시저장공간) 마지막 카운터만 ( +2 ) 적용됩니다.

 

그러나 prev를 사용하게되면 임시공간에 저장한 값을 버리지 않게 된다. 

 

import { useState } from "react";

export default function PrevstatePage() {
  const [state, setState] = useState(0);

  function sumAll() {
    setState((prev) => prev+1) ;
    setState((prev) => prev+2);
    setState((prev) => prev+3);
    setState((prev) => prev+4);
  }

  return (
    <>
      <div>결과는: {state}</div>
      <button onClick={sumAll}>실행!</button>
    </>
  );
}
	//한번 클릭할때마다 1+2+3+4 인 10을 더하게됩니다.

 

템플릿 리터널은 내장된 표현식을 허용하는 문자열 리터럴입니다.

const name = "철수"
const age = 12

//기본 방식
console.log(name + "는" + age + "살 입니다") //'철수는12살 입니다'

//템플릿 리터널
console.log(`${name}는 ${age}살 입니다.`) //'철수는 12살 입니다.'

이렇게 ``안에 문자열과 변수를 함께 사용하며 띄어쓰기도 모두 그대로 적용할 수 있으며,

${}안에는 변수와 연산자 ( +, -, *, /, 등) 도 모두 사용할 수 있다.

 

 

'리액트' 카테고리의 다른 글

클래스형(Class) 컴포넌트와 함수형(Functional) 컴포넌트  (0) 2023.02.05
state prev는 무엇일까?  (0) 2023.01.29
try ...catch 사용하기  (0) 2023.01.24
Git 명령어  (0) 2023.01.24
import와 Export  (0) 2023.01.24

try...catch 문은 실행할 코드블럭을 표시하고 예외가 발생할 경우의 응답을 지정합니다.

 

try안에는 실행될 선언들을 작성하고, catch에는 try에서 예외 즉, 오류가 발생하여 실행하지 못하게 될 경우 에러메세지로 경고 알림을 줄 수 있습니다.

 

try는 세가지 형식이 존재합니다.

 

try...catch : try 블록 안에서 예외가 발생(throw)하는 경우 무엇을 할지 명시하는 코드

try...finally :  try 블록과 catch 블록(들)이 실행을 마친 후 항상 실행됩니다. 
예외가 발생했는지에 관계없이 항상 실행

try...catch...finally : 하나 이상의 try 문을 중첩 할 수 있습니다. 내부의 try 문에 catch 블록이 없으면, 
둘러싼 try 문의 catch 블록이 입력됩니다.

 

'리액트' 카테고리의 다른 글

state prev는 무엇일까?  (0) 2023.01.29
Template Literals (템플릿 리터널 : ``)  (0) 2023.01.24
Git 명령어  (0) 2023.01.24
import와 Export  (0) 2023.01.24
JSX 란?  (0) 2023.01.24

출처 : 코드캠프

 

'리액트' 카테고리의 다른 글

Template Literals (템플릿 리터널 : ``)  (0) 2023.01.24
try ...catch 사용하기  (0) 2023.01.24
import와 Export  (0) 2023.01.24
JSX 란?  (0) 2023.01.24
Destructuring Assignment, 구조분해할당  (0) 2023.01.24

+ Recent posts