본문 바로가기
JAVASCRIPT/React

DOM조작 & react_router

by sjs_2215 2019. 1. 5.

출처: 리액트를 다루는 기술-VELOPERT


DOM이란?

DOM(Document Object Model)

= 객체로 문서 '구조'를 표현하는 방법. xml이나 html로 작성.

=웹페이지를 구성하고 있는 문서/다양한 구성요소(=Document) 예를 들어 a태그 div라던지 이런 것들을 하나하나 객체로 만들어서 자바스크립트가 제어할 수 있도록 한 것이 DOM

-트리 형태-> 특정 node를 검색/수정/제거/삽입이 가능.

DOM 설명_MDN문서

DOM_생코설명

->웹 페이지를 핸들링하기 위한 프로그래밍 인터페이스. 자바스크립트를 통해 DOM를 제어해서 웹페이지를 제어

DOM사용하는 일반적인 순서

  1. 제어할 대상(각각의 엘리먼트)을 찾기

  2. 대상이 가지고 있는 메소드를 실행하거나, 이벤트 핸들러를 설치하여 엘리먼트를 제어

-단점) 웹 브라우저 단에서 DOM에 변화가 일어나면 웹 브라우저가 CSS 다시 연산/레이아웃 구성/페이지 리페인트를 함 = 시간 허비.


-해결책) DOM을 최소한으로 조작하여 작업을 처리하자->Virtual DOM 사용하기

->DOM 업데이트를 추상화함으로써 DOM 처리 횟수를 최소화하고 효율적으로 진행한다.

Virtual DOM이란? (리액트의 주요 특징 중 하나)

-실제 DOM에 접근하여 조작하는 방식X

-실제 DOM을 추상화환 자바스크립트 객체를 구성하여 사용. -> 실제 DOM의 가벼운 사본과 비슷.

Ex)

[데이터가 변하여 웹 브라우저에 실제 DOM을 업데이트할 때]의 절차

  1. 데이터 업데이트 시 전체 UI를 Virtual DOM에 리렌더링함

  2. 이전 Virtual DOM에 있던 내용과 현재 내용을 비교

  3. 바뀐 부분 실제 DOM에 적용

Virtual DOM 과정 쉬운 설명

but,,,

Virtual DOM사용한다고 해서 항상 빠른 것 X

지속적으로 데이터가 변화하는 대규모 애플리케이션 구축할 때 진가를 발휘

즉, 리액트와 Virtual DOM의 장점이자 특징은:

-> 언제나 업데이트 처리 간결성을 제공

-> UI 업데이트 과정에서 생기는 복잡함을 모두 해소

-> 더욱 쉽게 업데이트에 접근 가능


ref: DOM에 이름 다는 방법

일반 HTML의 경우)

DOM 요소에 이름을 달 때 id를 사용

이유: 특정 DOM요소에 어떤 작업을 해야할 때

효과: css에서 특정 id에 특정 스타일 을 적용하거나, 자바스킓트에서 해당 id를 가진 요소를 찾아서 작업 가능

ex)

public/index.html의 id가 root인 div 요소가 있음

  <body>
   <noscript>
     You need to enable JavaScript to run this app.
   </noscript>
   <div id="root"></div>
 </body>

이 요소를 src/index.js 파일에서 리액트 컴포넌를 렌더링하라는 코드 써서 사용함.

ReactDOM.render(<App />, document.getElementById('root'));

=> 이렇게 HTML에서 id를 사용하여 DOM에 이름을 다는 것처럼,

리액트 프로젝트 내부에서 DOM에 이름을 다는 방법이 있다

: ref(reference) 개념

  • react 컴포넌트 안에서도 id 사용 '가능' (dom에 id달면 렌더링할 때 그대로 전달되긴 함)하지만 권장 x

    ex. 하나의 컴포넌트를 여러번 사용하는 경우, (html에서 DOM의 id는 유니크해야함) 이런 상황에서는 중복 id를 가진 DOM이 여러개 생기니 잘못된 사용.

    그에 반해 ref는 전역적으로 작동하지 않고 컴포넌트 내부에서만 작동하기에 위 문제 생기지 않는다.

즉, 리액트에선 특정 DOM요소에 작업할 때는 ref를 사용해야한다.

언제? 사용?

DOM을 꼭 직접적으로 건드려야할 때

어쩔수없이 DOM에 직접적으로 접근해야할 때

ref말고 대체법

근데, input값을 검정해야하는 경우는 굳이 DOM 접근 안해도 되고 state로 구현할 수 있다

예제)

js

import React, { Component } from 'react';
import './ValidationSample.css';

class ValidationSample extends Component {
 state = {
   password: '',
   clicked: false,
   validated: false
}

 handleChange = (e) => {
   this.setState({
     password: e.target.value
  });
}

 handleButtonClick = () => {
   this.setState({
     clicked: true,
     validated: this.state.password === '0000' //validated값을 검증 결과로 설정
  })
   this.input.focus();
}

 render() {
   return (
     <div>
       <input
         //ref={(ref)=>this.input=ref}
         type="password"
         value={this.state.password}
         onChange={this.handleChange} //onChange가 발생하면 handelChange를 호출하여 state의 password를 업데이트함
         className={this.state.clicked && (this.state.validated ? 'success' : 'failure')}
       />
       <button onClick={this.handleButtonClick}>검증하기</button>
     </div>
  );
}
}

export default ValidationSample;

css

.success {
 background-color: lightgreen;
}

.failure {
 background-color: lightcoral;
}

ref사용법

DOM에 ref 속성 추가: props 설정하듯이

ref 값: 콜백 함수를 전달

콜백 함수: ref를 파라메터로 가짐, 함수 내부에서 컴포넌트의 멤버 변수에 ref를 담는 코드를 작성

<input //DOM에 직접적으로 접근하기 위해 DOM에 ref 속성을 추가함
       ref={(ref)=>this.input=ref} //이렇게 하면, this.input은 input요소의 DOM을 가리킨다.
</input>

컴포넌트에 ref달기

사용법: DOM에 ref 다는 방법과 동일

<MyComponent ref={(ref) => this.myComponent=ref}} />

컴포넌트에 메서드 생성

만든 메서드는 부모 컴포넌트인 App 컴포넌트에서 만든 메서드가 있는 컴포넌트(ScrollBox.js)에 ref를 달면 사용할 수 있다.

ScrollBox.js 안에 있는 메서드 부분

  scrollToBottom = () => {
   const { scrollHeight, clientHeight } = this.box;

   this.box.scrollTop = scrollHeight - clientHeight;
}

부모 컴포넌트에 만든 메서드에 ref 달아서 사용하기

App.js 일부

render() {
   return (
     <div>
     <ScrollBox ref={(ref) => this.scrollBox=ref}/>
     <button onClick={()=>this.scrollBox.scrollToBottom()}>
      밑으로
     </button>
     </div>
  );
}

컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모<->자식 흐름으로 교류해야한다

(나중엔 꼬여서 유지보수 힘들기 때문에) 컴포넌트끼리 데이터 교류할 때 ref 사용하면 좀 그럼


react-router

뷰 렌더링을 유저의 웹 브라우저가 담당하도록 하고, 애플리케이션을 우선 웹 브라우저에 로드시킨 후 필요한 데이터만 전달 받아 보여 주게 하자.

*라우팅=> 주소에 따라 다른 뷰를 보여주는 것

리액트는 view만 담당하는 라이브러리이기에 라투팅을 담당하는 react-router를 따로 설치해줘야 한다.

react-router를 사용하면 SPA(하나의 페이지/고정된 레이아웃에서 내용만 바뀌는)처럼 깜박임이 없으면서도 주소를 가질 수 있게 해준다.

-> 새 페이지에서 필요한 데이터 받아와 그에 따라 웹 브라우저가 다른 종류의 뷰를 만들어준다.

but,

SPA prob) 하나의 페이지이기에 앱의 규모가 커지면 자바스크립트 파일 사이즈도 같이 커지게 된다는 점.

->sol) 코드 스플리팅(=라우트 별로 파일들을 나눠서 트래픽과 로딩속도를 개선할 수 있).

기본 설정

-절대 경로 설정하기 (윈도우 기준) : 디렉터리 구조가 깊어질수록 / ~/ ~/~가 복잡해지기에 프로젝트의 루트 경로를 지정하여 파일을 절대 경로로 쉽게 불러오자~

package.json 일부

  "scripts": {
   "start": "cross-env NODE_PATH=src react-scripts start",
   "build": "cross-env NODE_PATH=src react-scripts build",
   "test": "react-scripts test",
   "eject": "react-scripts eject"
},

-컴포넌트 생성/준비해두기

App.js 예시 ( 웹 브라우저의 주소에 따라서 어떤 컴포넌트를 보여 줄지 정의)

import React from 'react';
import { Route } from 'react-router-dom';
import {
 Home,
 About,
 Posts
} from 'pages';
import Menu from 'components/Menu';

const App = () => {
 return (
   <div>
     <Menu/>
     <Route exact path="/" component={Home}/>
     <Route path="/about/:name?" component={About}/>
     <Route path="/posts" component={Posts}/>
   </div>
);
};

export default App;

->Route 컴포넌트에서 경로는 path값으로 설정, 보여 줄 컴포넌트는 component값으로 설정

라우트 이동 (=앱 내에서 다른 라우트로 이동하기!)

  • 일반적으로 떠올릴 수 있는 <a href ~~~로 하면 안되고 link 컴포넌트를 사용해야 한다.

a 태그 클릭 시 새로고침을 해버려서 link 컴포넌트를 사용해 페이지를 새로 불러오는 것을 막고 원하는 라우트로 화면 전환을 해주기 때문

  • 설정한 url이 활성화가 되면 특정 스타일/클래스로 지정해 줄 수 있는 NavLink컴포넌트

사용법:

Menu.js

        <div>
           <ul>
               <li><Link to="/">Home</Link></li>
               <li><Link to="/about">About</Link></li>
               <li><Link to="/about/foo">About Foo</Link></li>
               <li><NavLink exact to="/about" activeStyle={activeStyle}>About</NavLink></li>
               <li><NavLink to="/about/foo" activeStyle={activeStyle}>About Foo</NavLink></li>
           </ul>
           <hr/>
       </div>
 

-> 이동할 주소를 컴포넌트의 to값으로 지정해주면 된다.

-> 중첩될 수 있는 라우트들은 exact로 설정. (switch도 있)

이후, App.js에서 이 컴포넌트를 불러오면 됨!

import {Route} from 'react-router-dom';
import{
   Home,
   About
} from 'pages';
import Menu from 'components/Menu';

const App=()=> {
   return(
   <div>
  <Menu/>
  <Route exact path="/" component={Home}/>
<Route path="/about/:name?" component={About}/>
   </div>
);
};
  • 자바트크립트에서 페이지를 이동해야하는 로직을 작성할 경우

ex)

const Home = ({ history }) => {
 return (
   <div>
     <h2></h2>
     <button onClick={() => {
       history.push('/about/javascript')
    }}>자바스크립트를 사용하여 이동</button>
   </div>
);
};

-> 라우트로 사용된 컴포넌트가 받아 오는 props중 하나인 history 객체의 push함수를 활용하기

history는 현재 라우터를 조작할 때 사용됨. (ex. 뒷/앞 페이지로 넘어가거나 새로운 주소로 이동할 때)

헷갈릴 수 있는 함수 [push 함수 vs replace 함수]

replace함수는 push와 다르게 방문 기록을 남기지 않아 페이지 이동 후 뒤로가기 버튼을 눌렀을 때 방금 전의 페이지가 아니라 방금 전의 전 전 페이지가 나타난다.

라우트 안의 라우트

라우트 안에 또 다른 라우트를 정의하는 방법

posts.js

<div>
     <h3>포스트 목록</h3>
     <ul>
       <li><Link to={`${match.url}/1`}>포스트 #1</Link></li>
       <li><Link to={`${match.url}/2`}>포스트 #2</Link></li>
       <li><Link to={`${match.url}/3`}>포스트 #3</Link></li>
     </ul>
     <Route exact path={match.url} render={() => (<p>포스트를 선택하세요</p>)}/>
     <Route exact path={`${match.url}/:id`} component={Post}/>
   </div>

->match.url을 사용하여 링크 설정: 나중에 라우트 주소 변경되도 내부 주소도 같이 자동으로 반영되기 때문에 좋음.

->match.url은 현재 라우트에 설정된 경로를 알려줌. (지금의 경우 /posts)

-> 첫 번째 라우트: id값 없을때. match.url과 정확히 일치할때만 render에 있는 내용 보여줘라

-> 두 번째 라우트: id값 있을때 Post 컴포넌트를 보여줘라

velopert_동영상

Comments