React HOC(Higher Oder Component) Auth 인증체크

 

리액트 컴포넌트를 인자로 받아서 다른 리액트 컴포넌트를 반환하는 함수를 고차함수라고 한다.

파라미터로 컴포넌트를 받고, 함수 내부에서 새 컴포넌트를 만들어 리턴해준다.

 

리액트 페이지에서 회원만 진입할 수 있는 페이지, 또는 관리자 페이지 등 사용자 인증에 따라

접근하지 못하도록 할때 HOC을 사용할 수 있다.

 

 

그림과 같이 backend로 request 를 날려서 landing page에 들어와있는 사람의 상태정보를 가져온다.

 

 

Server
// server/index.js

app.get('/api/users/auth', auth, (req, res) => {
  // 미들웨어를 통과한 후 실행되는 코드 => Authentication이 True 이다.
  // 성공했으므로 user 정보를 제공해준다. 페이지에서 user정보를 이용할 수 있어 편하다.
  res.status(200).json({
    _id: req.user._id,
    isAdmin: req.user.role === 0 ? false : true,
    isAuth: true,
    email: req.user.email,
    name: req.user.name,
    lastname: req.user.lastname,
    role: req.user.role,
    image: req.user.image
  })

})

먼저 서버쪽에서 구현했던 소스이다. 미들웨어 auth가 먼저 실행되게 된다.

 

// server/middleware/auth.js

const {User} = require('../models/User');

let auth = (req, res, next) => {
    // 인증처리를 한다.

    // 클라이언트 쿠키에서 토큰을 가져온다.
    let token = req.cookies.x_auth;

    // 토큰을 복호화 한 후 유저를 찾는다.
    User.findByToken(token, (err, user) => {
        if(err) throw err;

         // 유저가 없으면 인증 실패
        if(!user) return res.json({ isAuth: false, error: true})

        // 유저가 있으면 인증 성공
        // 정보를 사용할 수 있도록 토큰과 유저정보를 req에 넣어준다
        req.token = token;
        req.user = user;

        next(); // 미들웨어이기 떄문에 next 해줘야 한다.
    })
}

module.exports = { auth };

미들웨어에서 유저 인증을 수행하게 된다.

 

 

 

Client - Action 
// client/src/_action/user_actions.js

export function auth() {
    const request = axios.get('/api/users/auth')
        .then(response => response.data)


    return {
        type: AUTH_USER,
        payload: request
    }
}

 

Client - Reducer
export default function (state = {}, action) {
    switch (action.type) {
        .
        .
        .
        case AUTH_USER:
            return { ...state, userData: action.payload }
            break;
        default:
            return state;
    }

 

Client - Auth Hoc
// client/src/hoc/auth.js


import { Axios } from 'axios';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { auth } from '../_action/user_actions'

export default function (SpecificComponent, option, adminRoute = null) {

    // option
    // null => 아무나 출입가능
    // true => 로그인한 유저만 출입 가능
    // false => 로그인한 유저는 출입 불가능

    function AuthenticationCheck(props) {

        const dispatch = useDispatch();

        useEffect(() => {

            dispatch(auth()).then(response => {
                console.log(response)


                if (!response.payload.isAuth) {
                    // 로그인하지 않은 상태
                    if (option) {
                        props.history.push('/login')
                    }
                } else {
                    // 로그인한 상태
                    if (adminRoute && !response.payload.isAdmin) {
                        props.history.push('/')
                    } else {
                        if (option == false)
                            props.history.push('/')
                    }
                }
            })

        }, [])

        return (
            <SpecificComponent />
        )
    }

    return AuthenticationCheck
}
// client/src/App.js

import LandingPage from './components/views/LandingPage/LandingPage';
import LoginPage from './components/views/LoginPage/LoginPage';
import RegisterPage from './components/views/RegisterPage/RegisterPage';
import Auth from './hoc/auth'

function App() {
  return (
    <Router>
    <div>
      <hr />
      {/*
        A <Switch> looks through all its children <Route>
        elements and renders the first one whose path
        matches the current URL. Use a <Switch> any time
        you have multiple routes, but you want only one
        of them to render at a time
      */}
      <Switch>
        <Route exact path="/" component={Auth(LandingPage, null)} />
        <Route exact path="/login"  component={Auth(LoginPage, false)} />
        <Route exact path="/register" component={Auth(RegisterPage, false)} />
      </Switch>
    </div>
  </Router>

auth.js 에서 사용자 인증에 따른 페이지 접근을 관리한다. 

App.js 에서는 컴포넌트를 Auth로 감싸주고 option에 인자를 넣어 페이지들의 접근 권한을 컨트롤한다.

  • option
    • null => 아무나 출입가능
    • true => 로그인한 유저만 출입 가능
    • false => 로그인한 유저는 출입 불가능

'Study > react' 카테고리의 다른 글

[React] 회원가입 페이지 및 로그아웃  (0) 2021.10.31
[React] 로그인 페이지  (0) 2021.10.31
[React] React Hooks  (0) 2021.10.27
[React] Redux  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27

 

회원가입 화면
// client/src/components/views/RegisterPage/RegisterPage.js

import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { registerUser } from '../../../_action/user_actions'
function RegisterPage(props) {

    const dispatch = useDispatch();


    // state 생성
    const [Email, setEmail] = useState("")
    const [Password, setPassword] = useState("")
    const [Name, setName] = useState("")
    const [ConfirmPassword, setConfirmPassword] = useState("")

    // onChange 이벤트로 state 변경 => value 변경됨
    const onEmailHandler = (event) => {
        setEmail(event.currentTarget.value)
    }

    const onNameHandler = (event) => {
        setName(event.currentTarget.value)
    }

    const onPasswordHandler = (event) => {
        setPassword(event.currentTarget.value)
    }
    const onConfirmPasswordHandler = (event) => {
        setConfirmPassword(event.currentTarget.value)
    }

    const onSubmitHandler = (event) => {
        event.preventDefault();

        if (Password !== ConfirmPassword) {
            return alert('비밀번호와 비밀번호 확인은 같아야 합니다.')
        }

        let body = {
            email: Email,
            password: Password,
            name: Name
        }

        dispatch(registerUser(body))
            .then(response => {
                if (response.payload.success) {
                    props.history.push('/login') // 성공시 페이지 이동
                } else {
                    alert('Error')
                }
            })

    }

    return (
        <div style={{display:'flex', justifyContent:'center', alignItems:'center', width:'100%', height: '100vh'}}>

            <form style={{ display: 'flex', flexDirection: 'column' }}
                onSubmit={onSubmitHandler}
            >
                <label>Email</label>
                <input type="email" value={Email} onChange={onEmailHandler}/>

                <label>Name</label>
                <input type="text" value={Name} onChange={onNameHandler}/>

                <label>Password</label>
                <input type="Password" value={Password} onChange={onPasswordHandler}/>

                <label>Confirm Password</label>
                <input type="Password" value={ConfirmPassword} onChange={onConfirmPasswordHandler}/>


                <br />
                <button>
                    회원가입
                </button>
            </form>
        </div>
    )
}

먼저 회원가입 화면을 그려준다. 로그인 화면과 마찬가지로 dispatch를 통해 action을 발생시킨다.

 

Action
// client/src/_action/user_actions.js

import axios from 'axios'
import {
    LOGIN_USER,
    REGISTER_USER
} from './types';

export function loginUser(dataToSubmit) {
    // 서버에서 받은 데이터를 request 변수에 저장
    const request = axios.post('/api/users/login', dataToSubmit)
        .then(response => response.data)
    return {
        type: LOGIN_USER,
        payload: request
    }
}

export function registerUser(dataToSubmit) {
    // 서버에서 받은 데이터를 request 변수에 저장
    const request = axios.post('/api/users/register', dataToSubmit)
        .then(response => response.data)


    return {
        type: REGISTER_USER,
        payload: request
    }
}

로그인기능과 마찬가지로 회원가입에 대한 action을 추가해준다.

 

Reducer
// client/src/_reducers/user_reducer.js

import {
    LOGIN_USER,
    REGISTER_USER
} from '../_action/types';

export default function (state = {}, action) {
    switch (action.type) {
        case LOGIN_USER:
            // 스프레드오퍼레이터
            return { ...state, loginSuccess: action.payload }
            break;
        case REGISTER_USER:
            return { ...state, register: action.payload }
            break;

        default:
            return state;

 

로그아웃 기능 추가
// client/src/components/views/LandingPage/LandingPage.js

import React, { useEffect } from 'react'
import axios from 'axios';

function LandingPage(props) {

    useEffect(() => {
        axios.get('/api/hello')
            .then(response => console.log(response.data))
    }, [])

    const onClickHandler = () => {
        axios.get('/api/users/logout')
            .then(response => {
                if (response.data.success) {
                    props.history.push("/login")
                } else {
                    alert('로그아웃에 실패했습니다')
                }
            })
    }

    return (
        <div style={{diplay:'flex', justifyContent:'center', alignItems:'center', width:'100%', height: '100vh'}}>
            <h2>시작페이지</h2>
            <button onClick={onClickHandler}>
                로그아웃
            </button>
        </div>
    )
}

로그아웃 버튼을 추가하여 로그아웃 기능을 추가하였다.

 

 

 

실행화면

 

'Study > react' 카테고리의 다른 글

[React] HOC Auth 인증체크  (0) 2021.10.31
[React] 로그인 페이지  (0) 2021.10.31
[React] React Hooks  (0) 2021.10.27
[React] Redux  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27

 

Dispath
// client/src/components/views/LoginPage/LoginPage.js

import { Axios } from 'axios'
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { loginUser } from '../../../_action/user_actions'

function LoginPage(props) {
    const dispatch = useDispatch();


    // state 생성
    const [Email, setEmail] = useState("")
    const [Password, setPassword] = useState("")

    // onChange 이벤트로 state 변경 => value 변경됨
    const onEmailHandler = (event) => {
        setEmail(event.currentTarget.value)
    }

    const onPasswordHandler = (event) => {
        setPassword(event.currentTarget.value)
    }

    const onSubmitHandler = (event) => {
        event.preventDefault();

        let body = {
            email: Email,
            password: Password
        }

        dispatch(loginUser(body))
            .then(response => {
                if (response.payload.loginSuccess) {
                    props.history.push('/') // 성공시 페이지 이동
                } else {
                    alert('Error')
                }
            })

    }

    return (
        <div style={{display:'flex', justifyContent:'center', alignItems:'center', width:'100%', height: '100vh'}}>

            <form style={{ display: 'flex', flexDirection: 'column' }}
                onSubmit={onSubmitHandler}
            >
                <label>Email</label>
                <input type="email" value={Email} onChange={onEmailHandler}/>
                <label>Password</label>
                <input type="Password" value={Password} onChange={onPasswordHandler}/>

                <br />
                <button>
                    Login
                </button>
            </form>
        </div>
    )
}

먼저 LoginPage에서 화면을 그려준다.

입력받은 email 과 비밀번호 정보를 인자로 넘겨주며 dispatch를 통해 Action을 발생시킨다.

 

Action 
// client/src/_action/user_actions.js

import axios from 'axios'
import {
    LOGIN_USER
} from './types';

export function loginUser(dataToSubmit) {


    // 서버에서 받은 데이터를 request 변수에 저장
    const request = axios.post('/api/users/login', dataToSubmit)
        .then(response => response.data)


    return {
        type: LOGIN_USER,
        payload: request
    }
}

dispath를 통해 호출한 Action을 구현한다. axios를 통해 로그인 기능을 수행하게 된다.

 

 

Reducer
// client/src/_reducers/user_reducer.js

import {
    LOGIN_USER
} from '../_action/types';

export default function(state = {}, action) {
    switch (action.type) {
        case LOGIN_USER:
            // ( ... 스프레드오퍼레이터) => 파라미터 state를 그대로 가져온 것. 빈 상태를 의미 
            return { ...state, loginSuccess: action.payload }
            break;

        default:
            return state;
    }
}

reducer 에서는 state와 Action을 인자로 받아 다음 state 값을 리턴해준다.

 

 

// client/src/_reducers/index.js

// 여러개의 reducer  CombineReducer를 이용하여 Root Reducer 에서 하나로 합쳐줌

import { combineReducers } from "redux";
import user from './user_reducer';


const rootReducer = combineReducers({
    user
})

export default rootReducer;

index.js 에서 reducer 를 등록해주었다.

 

 

 

 

'Study > react' 카테고리의 다른 글

[React] HOC Auth 인증체크  (0) 2021.10.31
[React] 회원가입 페이지 및 로그아웃  (0) 2021.10.31
[React] React Hooks  (0) 2021.10.27
[React] Redux  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27
React Hooks

Hooks 는 리액트 v16.8 에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해준다.

이전에는 함수형 컴포넌트에서의 기능이 많이 제한됐었는데 Hooks 발표 이후 많은 기능들을 쓸 수 있게 되었다.

즉, 생명주기메소드를 함수형 컴포넌트에서도 이용할 수 있게 된 것이다.

 

 

 

 

 

https://ko.reactjs.org/docs/hooks-overview.html

 

Hook 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

 

Hooks 발표이후 클래스컴포넌트에서의 기능을 함수형컴포넌트에서도 구현할 수 있게 되었다.

 

  • useState : 가장 기본적인 Hook 으로서, 함수형 컴포넌트에서도 가변적인 상태를 지니고 있을 수 있게 해줌
  • useEffect : 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정 할 수 있는 Hook

'Study > react' 카테고리의 다른 글

[React] 회원가입 페이지 및 로그아웃  (0) 2021.10.31
[React] 로그인 페이지  (0) 2021.10.31
[React] Redux  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27
[React] Axios 및 Proxy 설정  (0) 2021.10.27
Redux란?

React 프로젝트의 규모가 커질때마다 자식으로 넘겨주어야 하는 props가 점점 복잡해져 간다.

따라서, 원하는대로 state를 사용할 수 있는 라이브러리 Redux가 나오게 되었다.

즉, 상태를 관리해 주는 라이브러리이다.

 

 

  • Props 
    • 부모 컴포넌트가 자식 컴포넌트한테 전달하는 데이터(읽기전용)
    • 부모 컴포넌트에서 설정 (자식 컴포넌트에서 수정 불가)
  • State
    • 상태에 따라 변화하는 데이터(쓰기전용)
    • 자식 컴포넌트에서 직접 변경 가능
    • 컴포넌트 안에서 데이터를 교환하거나 전달할 때 사용
    • state가 변경되는 컴포넌트 랜더링 필요

 

 

  • action : 상태를 알려줌
  • reducer : 변경된 값을 알려줌. 이전의 State와 Action 객체를 받은 후에 바뀐 State를 리턴해줌.
  • store : 어플리케이션의 state 을 감싸주는 역할. 수많은 메소드들로 State를 관리함

 

https://react-redux.js.org/

 

React Redux | React Redux

Official React bindings for Redux

react-redux.js.org

 

Redux 사용하기

redux를 사용하기 위해 아래의 Dependency들을 다운 받아야한다.

  • redux
  • react-redux : redux를 더 편하기 사용하기 위함
  • redux-promis : 프로미스 기반의 비동기 작업을 조금 더 편하게 해주는 미들웨어
  • redux-thunk : 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어. 액션 객체가 아닌 함수를 디스패치 할 수 있음

store의 state변경하기 위해서 dispatch를 이용해 action으로 해야한다.
하지만 store에는 action만 오는것이 아닌 promise 또는 functions으로 올 수도 있다.

이를 위해 redux-thunk는 dispatch한테 어떻게 function을 받을지 알려주며,
redux-promise는 promise가 왔을때의 처리를 도와준다.

 

$npm install redux react-redux redux-promise redux-thunk --save

 

 

// client/src/index.js 

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux'; // redux 연결
import 'antd/dist/antd.css';
import { applyMiddleware, createStore } from 'redux';
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import Reducer from './_reducers' // index.js 를 알아서 찾음

const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore)
// promise, function을 받을 수 있도록 middleware와 함께 create

ReactDOM.render(

  // redux 연결
  <Provider
    store={createStoreWithMiddleware(Reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ &&
        window.__REDUX_DEVTOOLS_EXTENSION__()
      )}
  >
    <App />
  </Provider>,

  document.getElementById('root')
);
//  client/src/_reducers/index.js 

// 여러개의 reducer들을  CombineReducer를 이용하여 Root Reducer 에서 하나로 합쳐줌

import { combineReducers } from "redux";
// import user from './user_reducer';


const rootReducer = combineReducers({
    // user,
})

export default rootReducer;

redux를 사용하기 위한 기본 설정을 완료하였다.

 

'Study > react' 카테고리의 다른 글

[React] 로그인 페이지  (0) 2021.10.31
[React] React Hooks  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27
[React] Axios 및 Proxy 설정  (0) 2021.10.27
[React] react-router-dom  (0) 2021.10.27
Concurrently 란?

여러개의 commands를 동시에 작동 시킬수 있게 해주는 Tool이며 프론트 및 서버를 한번에 start 할 수 있는 라이브러리이다. 

 

아래 사이트에서 사용법을 참고할 수 있다.

 

https://www.npmjs.com/package/concurrently

 

concurrently

Run commands concurrently

www.npmjs.com

 

 

npm install concurrently --save

Concurrently 를 설치해준다.

 

// package.json

"scripts": {
    "start": "node server/index.js",
    "backend": "nodemon server/index.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""
  },

다음과 같이 설정 후 npm run dev 를 입력 하면 한번에 시작된다.

'Study > react' 카테고리의 다른 글

[React] React Hooks  (0) 2021.10.27
[React] Redux  (0) 2021.10.27
[React] Axios 및 Proxy 설정  (0) 2021.10.27
[React] react-router-dom  (0) 2021.10.27
[Node js] Authentication 유저 인증 기능  (0) 2021.10.04
Axios란?

Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리이다.

백엔드 및 프론트엔드에서 통신을 쉽게하기 위해 Ajax와 함께 사용된다.

 

사용법 및 예제는 아래 공식문서를 참고하면 된다.

https://axios-http.com/docs/intro

 

Getting Started | Axios Docs

Getting Started Promise based HTTP client for the browser and node.js What is Axios? Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase). On the server-side it u

axios-http.com

 

npm install axios --save

먼저 axios 패키지를 설치해준다.

 

// client/src/components/views/LandingPage/LandingPage.js

import React, { useEffect } from 'react'
import axios from 'axios';

function LandingPage() {

    useEffect(() => {
        axios.get('/api/hello')
            .then(response => console.log(response.data))
    }, [])

    return (
        <div>
            LandingPage
        </div>
    )
}
export default LandingPage

다음과 같이 axios 객체를 이용하여 간단하게 HTTP 요청을 보낼 수 있다.

 

// server/index.js

app.get('/api/hello', (req, res) => {
  res.send("안녕하세요~");
})

서버측에서도 요청을 받기위해 다음과 같이 추가해주었다.

 

 

사용자의 서버 포트는 5000번이고 클라이언트 포트는 3000번이기 때문에  이대로 사용한다면 에러가 난다.

 

 

 useEffect(() => {
        axios.get('http://localhost:5000/api/hello')
            .then(response => console.log(response.data))
    }, [])

이를 해결하기 위해 5000번으로 요청을 보내도 여전히 에러가 난다.

이는 CORS 정책 때문이다. 외부에서 요청을 보내면 보안상의 이슈가 있을 수 있기 때문에 CORS 정책이 필요하다.
(CORS란? : Cross-Origin Resource Sharing의 약자로 서로 다른 Origin 사이에서 자원을 공유할 때 사용되는 정책)

 

 

Proxy로 CORS 해결

CORS를 해결하기 위해서 proxy 서버를 이용하는 방법을 사용하였다.

 

https://create-react-app.dev/docs/proxying-api-requests-in-development/

 

Proxying API Requests in Development | Create React App

Note: this feature is available with react-scripts@0.2.3 and higher.

create-react-app.dev

 

npm install http-proxy-middleware --save

먼저 http-proxy-middleware 를 설치해준다.

 

// client/src/setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

proxy 설정을 추가해줌으로 /api로 시작되는 API는 target으로 설정된 서버 URL로 호출하도록 설정된다.

changeOrigin 설정은 대상 서버 구성에 따라 호스트 헤더가 변경되도록 설정해주는 옵션이다.

'Study > react' 카테고리의 다른 글

[React] Redux  (0) 2021.10.27
[React] Concurrently (프론트, 백 서버 한번에 켜기)  (0) 2021.10.27
[React] react-router-dom  (0) 2021.10.27
[Node js] Authentication 유저 인증 기능  (0) 2021.10.04
[Node js] JWT 를 이용한 로그인  (0) 2021.10.04
react-router-dom 이란

react는

spa(single page application) 이다. spa특성상 페이지를 새로고침하지 않으면서 화면을 전환해야 한다.이 때 화면 전환을 위해 react-router가 필요하며 별도의 패키지를 설치해야 한다.

 

- react-router : 웹&앱

- react-router-dom : 웹

- react-router-native : 앱

 

react-router 에는 세 가지 종류가 있는데 나는 웹에서 사용하기 위해 react-router-dom을 사용하였다.

 

아래 주소에서 사용법 및 예제를 확인할 수 있다.

https://reacttraining.com/react-router/web/example/basic

 

React Router: Declarative Routing for React

Learn once, Route Anywhere

reactrouter.com

 

 

react-router-dom 사용하기
npm install react-router-dom --save

먼저 npm으로 설치를 진행한다.

 

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

react-router-dom을 사용하기 위해 import가 필요하다.

 

function App() {
  return (
    <Router>
    <div>
      <hr />

      {/*
        A <Switch> looks through all its children <Route>
        elements and renders the first one whose path
        matches the current URL. Use a <Switch> any time
        you have multiple routes, but you want only one
        of them to render at a time
      */}
      <Switch>
        <Route exact path="/" component={LandingPage} />
        <Route exact path="/login"  component={LoginPage} />
        <Route exact path="/register" component={RegisterPage} />
      </Switch>
    </div>
  </Router>
  );
}

App.js에 다음과 같이 router를 설정해 주었다.

  • BrowserRouter(Router) - <Route>와 <Redirect> 컴포넌트가 함께 유기적으로 동작하도록 묶어주는데 사용. history API를 사용해 URL과 UI를 동기화하는 라우터
  • Route - 컴포넌트의 속성에 설정된 URL과 현재 경로가 일치하면 해당하는 컴포넌트, 함수를 렌더링한다.
  • Switch - 자식 컴포넌트 Route또는 Redirect중 매치되는 첫 번째 요소를 렌더링한다. Switch를 사용하면 하나의 매칭 요소만 렌더링한다는 점을 보장해준다. 사용하지 않을 경우 매칭되는 모두를 렌더링한다.

+ Recent posts