Redux

Redux사용시 컴포넌트들을 props 없이 state 공유가능
useReducer와 비슷한 구조를 지님
redux에서는 객체를 action객체라고 말함
action객체를 reducer로 보내는 행위 = dispatch

Redux 설치방법

npm

npm i redux react-redux

yarn

yarn add redux react-redux

디자인 패턴?

파일구성을 어떻게 분리하고 관리를 할건지

Container Presenter 패턴

Container Presenter 패턴은 리액트 디자인 패턴 중 하나로, 기능과 UI를 컴포넌트 상으로 분리한 것
데이터를 처리하고, 받아오는 부분(기능)은 Container 컴포넌트에서 담당
데이터를 보여주는 부분(UI)은 Presenter 컴포넌트에서 담당

기존 패턴(정통적패턴)

  • 폴더구성
  • counter.js만들기
  1. constants -> counter.js
    어떤 action이 필요한지 만듦
  2. export const INC_COUNT = "INC_COUNT" export const DEC_COUNT = "DEC_COUNT"
  3. actions -> counter.js
    action 객체를 만듦
import { INC_COUNT, DEC_COUNT } from '../constants/counter'

export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: {diff}
  }
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: {diff}
  }
}
  1. reducers -> counter.js
    어떤 action들을 case로 넣을지 갖고오기
import { INC_COUNT, DEC_COUNT } from "../constants/counter";

//초기값설정
const initialState = {number: 0}

//state: 현재상태, action: 괄호안에 넣은 그 값
export default function counter(state= initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return {number: state.number + action.payload.diff} 
    case  DEC_COUNT:
      return {number: state.number - action.payload.diff}
    default:
      return state  
  }
}
  1. store -> counter.js(index.js로 파일 이름이 지어줘도됨)
    store는 한 앱에 하나만 있는게 규칙
    이제 App.js에서 Provider형태로 쓸 수 있게됨
import { legacy_createStore as createStore } from "redux";
import counter from '../reducers/counter'

const store = createStore(counter)

export default store
  1. App.js
import { Provider } from "react-redux";
import store from "./redux/store/counter";

function App() {
  return (
    <Provider store={store}>
      <div>앱</div>
    </Provider>
  );
}

export default App;
  1. 폴더 생성
  2. components -> Counter.jsx
//데이터를 보여주는 부분(UI)

import React from 'react'

function Counter(props) {

  const { number, onDecrease, onIncrease } = props

  return (
    <div>
      <p>{number}</p>
      <button onClick={onIncrease}>+2</button>
      <button onClick={onDecrease}>-2</button>
    </div>
  )
}

export default Counter
  1. container -> CounterContainers.jsx
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Counter from '../components/Counter'
import { incCount, decCount } from '../redux/actions/counter'

function CounterContainers() {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.number)

  const onIncrease = () => {
    dispatch(incCount(2))
  }

  const onDecrease = () => {
    dispatch(decCount(2))
  }

  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  )
}

export default CounterContainers
  1. App.js
import CounterContainers from "./containers/CounterContainers";

    <Provider store={store}>
      <CounterContainers />
    </Provider>

ducks 패턴

분리된 파일을 합쳐서 하나의 파일에다가 모든 코드를 작성하는 것

ducks 규칙

  1. 반드시 리듀서 함수를 default export 해야 한다.
  2. 반드시 액션 생성 함수를 export 해야 한다.
  3. 반드시 접두사를 붙인 형태로 액션타입을 정의해야 한다.
  4. (선택) 액션 타입은 UPPER_SNAKE_CASE 형태로 이름을 짓고 export 할 수 있다.

ducks패턴 사용방법

  1. 파일구성
  2. modules -> counter.js
    해당 파일에 constants, actions, reducers내용 넣기

// constatns폴더. 액션타입
const INC_COUNT = "INC_COUNT"
const DEC_COUNT = "DEC_COUNT"

// actions폴더. 액션 생성 함수
export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: {diff}
  }
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: {diff}
  }
}

// reducers 폴더
//초기값
const initialState = {number: 0}
// 리듀서
export default function counter(state= initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return {number: state.number + action.payload.diff} 
    case  DEC_COUNT:
      return {number: state.number - action.payload.diff}
    default:
      return state  
  }
}

Redux Looger

redux로 실행되는 로직에 대해 콘솔창에 기록을 남겨주는 역할담당 => 리덕스 미들웨어
(store, action객체 다룰때의 과정을 남겨줌)

설치

yarn

  yarn add redux-logger --dev

npm

  npm i redux-logger --dev
  import { applyMiddleware } from "redux";  
  import logger from 'redux-logger'

  const store = createStore(rootReducer, applyMiddleware(logger))

chrome Redux DevTools 확장프로그램 사용방법

Redux DevTools 확장프로그램 추가

  import { applyMiddleware, compose, legacy_createStore as createStore } from "redux";

  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
  const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger)));

검사창에서 실시간 로깅 확인가능

  1. modules -> index.js
//store내용 넣기
import { applyMiddleware, legacy_createStore as createStore } from "redux";
import logger from 'redux-logger'
import counter from './counter'

const store = createStore(counter)

export default store
  • 만약 여러개의 리듀서가 있을경우
import { legacy_createStore as createStore } from "redux";
import counter from './counter'
import { combineReducers } from "redux";

//combine을 해줬기때문에
//state.counter -> counter 리듀서가 관리하는 상태에 접근
const rootReducer = combineReducers({
  counter,
})

const store = createStore(rootReducer, applyMiddleware(logger))

export default store
  1. App.js
import { Provider } from "react-redux";
import CounterContainers from "./containers/CounterContainers";
import store from "./modules";

function App() {
  return (
    <Provider store={store}>
      <CounterContainers />
    </Provider>
  );
}

export default App;
  1. containers -> CounterContainers
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Counter from '../components/Counter'
import { incCount, decCount } from '../modules/counter'

function CounterContainers() {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.number)

  const onIncrease = () => {
    dispatch(incCount(2))
  }

  const onDecrease = () => {
    dispatch(decCount(2))
  }

  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  )
}

export default CounterContainers

1, 2, 3번까지 같은방식으로 생성 후 pages폴더 생성해서 구분하는방법
pages폴더는 components안에 구분해 놓은파일들이 들어가는 폴더

Redux toolkit 설치방법

  1. 버전확인
    "react": "^18.2.0", "react-dom": "^18.2.0" -> 두 버전이 18버전이상인지 확인

  2. npm

npm i @reduxjs/toolkit react-redux

yarn

yarn add @reduxjs/toolkit react-redux
  1. store.js파일생성
import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: { }
}) 
  1. index.js로 이동
import { Provider } from 'react-redux';
import store from '경로'

<Provider store={store}></Provider> //Provider로 감싸기

Redux store에 state 보관하는 방법


1.
import { createSlice } from '@reduxjs/toolkit'

2.
//useState와 비슷한용도. state하나를 slice라 부름
const 작명 = createSlice({
  name: 'state이름',
  initialState: '값'
})


3.
export default configureStore({
  reducer: {
    작명 : 작명.reducer
   }
}) 

Redux store에 state 꺼내는 방법

useSelector

store에 저장되있는 상태를 가져오기 위한 Hook

1. `state` 꺼낼 컴포넌트로 이동
import { useSelector } from 'react-redux';

2.
const selector = useSelector((state)=>{ return state })
// { return state }state는 모든state를 말함 
// 원하는 state만 출력가능 
// ex) {return state.작명한이름}
console.log(selector) //store에 등록한 정보가 넘어오는걸 확인가능

Redux store에 state 변경하는 방법

useDispatch

useDispatchdispatch 함수를 실행할 수 있도록 해주는 Hook

1. store.js로 이동
  `state` 변경 원하는 `state`에 아래와 같은 형식으로 작성해주기
  reducers: {
    함수명(state) {
      return 변경시켜주세요
    }
  }
2. export 해주기
  export const { 함수명 } = state함수명.actions
3. 
  import { useDispatch } from 'react-redux';
  import { 함수명 } from './../store';

  const dispatch = useDispatch()

  dispatch(state변경함수())
  <button onClick={ () =>{ dispatch( 함수명() ) }}>변경하기</button>

state가 object/array일 경우 변경하는 방법

state가 object/array일 경우 return 없이 직접 수정 가능
(그래서 값이 하나여도 object/array형태로 만들어서 사용하기도함)

  initialState: { name: 'Yu', age: 20 },
  reducers: {
    함수명(state) {
      state.name = 'Sunny'
    },

    함수명(state, action) {
      state.age += action.payload
    }
  }
  // 파라미터에 값을 넣어줘서 변경가능
  <button onClick={ () =>{ dispatch( 함수명(10) ) }}>변경하기</button>

'프론트엔드 > React' 카테고리의 다른 글

useNavigate, NavLink, Lazy Loading  (0) 2022.08.09
React router dom v6 (리액트 라우터 버전6)  (0) 2022.08.09

useNavigate 사용방법

navigatestate를 넘길 수 있음
넘긴 stateuseLocation으로 받으면 된다.
navigatereplace:true를 같이 사용해주면 이력을 없애줌

navigatelink의 차이점
navigate는 함수 안에서 쓸 수 있음

1.
 import { useNavigate } from 'react-router-dom'
2. 
  const navigate = useNavigate()
3. 
  <button onClick={()=>navigate("경로", {state: {data: '데이터넘김'} })}>경로로 가기</button>
  <button onClick={()=>navigate(-1, {replace:true})}>뒤로 가기</button>

NavLink 사용방법

1. 
  import { NavLink } from 'react-router-dom'
2. 
  <NavLink 
    style={ ({isActive}) => ({color: isActive ? "lime" : "salmon"}) }>
  </NavLink>

NavLink는 기본적으로 isActive라는 props를 가지고 있음

Lazy Loading

ReactSPA이기 때문에 처음 앱을 불로오는 시간이 오래걸림
이러한 단점을 보안하기위해 파일 자체를 분할하는게 중요
가장 기본적인방법은 dynamic import

Reactlazysuspense로 이를 구현함

Lazy Loading사용방법

1.
  import { lazy, Suspense } from 'react';
2.
  const lazy원하는곳 = lazy( () => import("lazy할경로"))
3.
  //아직 컴포넌트가 불러와지기전에 유저(사용자)에게 fallback에 있는 정보를 보여줌
  <Route path="/" element={<Suspense fallback={<div>Loading...</div>}><lazy원하는곳 /></Suspense>} />

'프론트엔드 > React' 카테고리의 다른 글

Redux  (0) 2022.08.16
React router dom v6 (리액트 라우터 버전6)  (0) 2022.08.09

React router dom v6 사용방법

  1. yarn start react-router-dom@6 설치

  2. index.js로 이동

    import { BrowserRouter } from 'react-router-dom'
    <BrowserRouter>
     <App /> 을 감싼다
    </BrowserRouter>
  3. 폴더를 만든다
    ex) components, routes, pages ......
    components ->
    Posts ->index.jsx
    Users ->index.jsx

  4. App.js로 이동

    import { Route, Routes, Link } from 'react-router-dom';
    <div className="App">
    <Link to="경로">경로이동</Link> //경로 클릭시 해당 경로로 이동
    <Routes>
     <Route path="경로" element={<연결된컴포넌트 />} />
     <Route path="*" element={<p>Not Found</p>} />
    </Routes>
    </div> 

    Routes의 역할: 주소변경 감지해 일치하는 Route를 보여줌
    *의 역할: 작성해둔 path와 일치하지 않는 모든 경우에 보여줄 element

nested router 사용방법

  1. Route안에 Route작성

  2. path에 변수이름 작성 ex)Id, pathId

    <div className="App">
    <Routes>
     <Route path="경로" element={<연결된컴포넌트 />}>
       <Route index element={<연결된컴포넌트 />} />
       {/* `path="경로"`로 들어간 후 기본적으로 보여줄 요소 */}
       <Route path:"변수이름" element={<연결된컴포넌트 />} />
     </Route>
    </Routes>
    </div> 
  3. path="경로" 해당파일로 이동
    Outlet은 하위파일이 보일 위치를 설정해줌

    import { Outlet } from 'react-router-dom'
function 경로() {
  return (
    <div>
      경로
      <Outlet />
    </div> 
  )
}
  1. Outlet안에 들어갈 컴포넌트로 이동
    import { useParams } from 'react-router-dom'
function 이름() {

  const params = useParams()

  return (
    <div>
      {/* `path="변수이름"`을 Id로 가정했을때 */}
      {params.Id}
    </div> 
  )
}

주소창에 입력한 주소가 {params.Id}부분에 표시됨

데이터를 받았을경우 예제

postData.js 내부구조
{
"userId": 1,
"id": 1,
"title": ''
"body": ''
}

import React from 'react'
import { Link } from 'react-router-dom'
import { postData } from '../../../constants/postData'
// export로 넘겼을 경우 {}해주기
// export default 넘겼을 경우 중괄호를 안해줘도됨

function PostIndex() {
  return (
    <div>{postData.map((post)=>{
      return(
      <Link to={`/posts/${post.id}`}><p>{post.title}</p></Link>
      )
    })}</div>
  )
}

export default PostIndex
import React from 'react'
import { useParams } from 'react-router-dom'
import { postData } from '../../../constants/postData'

function PostDetail() {

  const params = useParams()
  const post = postData.find((post)=>{return post.id === parseInt(params.postId)}) 
  //params.postId = String, post.id = Number이기때문에 결과가 표시안됨
  //해결방법: params.postId에 parseInt()해주기

  return (
    <>
    <p>{post.title}</p>
    <p>{post.body}</p>
    </>

  )
}

export default PostDetail

'프론트엔드 > React' 카테고리의 다른 글

Redux  (0) 2022.08.16
useNavigate, NavLink, Lazy Loading  (0) 2022.08.09

Git 원격 저장소

현재 git에 등록된 원격 저장소 리스트 확인

git remote -v

변경할 원격 저장소를 입력해주기

git remote set-url <name><변경할 url>

원격 저장소 해제

git remote remove <name>

원격 저장소 등록

git remote add <name><url>

'프론트엔드 > Git&GitHub' 카테고리의 다른 글

Github fork  (0) 2022.04.04
Git 명령어 (추가)  (0) 2022.04.04
.gitignore  (0) 2022.04.03
Git Branch와 Git Flow  (0) 2022.04.02
Git clone과 Git init  (0) 2022.04.01

22.06.08 Vue.js 공부 정리

www: 서브도메인
서브도메인은 무한정 뽑아낼 수 있음
/안에 들어있는 정보: 파라미터
쿼리스트링: 검색필터. ?뒤쪽은 쿼리스트링이 나오면 key, value로 쪼개기
https://www.omdbapi.com?apikey=7035c60c&s=frozen&page=3

Vite.js 실행

npm create vite@latest 생성할폴더이름

vue router도 할거라 설치해주기
npm i -D vie-router
eslint랑 sass 설치해주기
npm i -D eslint eslint-plugin-vue sass

vite.config.js

경로별칭 지정해주기

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: [{
      find: '~',
      replacement: `${__dirname}/src`
    }]
  }
})

.eslintrc.json

src폴더안에 생성해주기

{
  "env": {
    "browser": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:vue/vue3-recommended"
  ],
  "rules": {
    "semi": ["error", "never"],
    "quotes": ["error", "single"],
    "eol-last": ["error", "always"],

    "vue/html-closing-bracket-newline": ["error", {
      "singleline": "never",
      "multiline": "never"
    }],
    "vue/html-self-closing": ["error", {
      "html": {
        "void": "always",
        "normal": "never",
        "component": "always"
      },
      "svg": "always",
      "math": "always"
    }],
    "vue/comment-directive": "off",
    "vue/multi-word-component-names": "off"
  }
}

src -> routes 폴더 생성
route = page

routes 폴더 안에 index.js 파일 생성 후
vue-router에서 객체분해할당 createRouter, createWebHistory 가져오기

import { createRouter, createWebHistory } from 'vue-router' 

가져와서 생성한 결과를 export로 내보내기
제공옵션 2개
mode 2개: hashmode, historymode(로컬에서 그냥 가능하나 베포할때 서버에 기본셋팅필요)

export default createRouter({
  history: createWebHistory()
  routes: [
    {
      path: '/' (메인페이지)
      component: 
    }
  ]
})
예시)
routes안에 Home.vue 파일 만들었을 시 연결하는 방법
import Home from './Home.vue'
{
  path: '/' 
  component: Home
}

만들어진 index.js를 main.js에 연결시켜주자
import router from './routes'
.use(router)

연결이 됐으면 router가 화면에 보여지게끔 연결해줘야하는데
App.vue 파일에 가서 
<RouterView />로 연결
`RouterView` 화면에 보여질 영역
fetch메소드 기본구조
async name() {
  fetch 나온 결과 res로 받고
      let res = await fetch('')
  res.json을 res로 반환 받고
      res = await res.json()
      console.log(res)
}

끄적

이미지 사이즈가 다 다를 경우 백그라운드 이미지로 주기
scoped는 현재 페이지 내에서만 영향이 가도록설정

<style scoped lang="scss">
.poster {
  width: 400px;
  height: calc(400px * 3 / 2);
  background-size: cover;
  background-color: lightgray;
}
</style>

vue router에서 제공하는 객체

$route: 정보를 담고있는
$router : 페이지 이동관련. -> html // router.push() -> JS

router.push(name:'지정한이름써주기')
router.push(경로써줘도됨)
router.go(1) 한번앞으로, router.go(-1) 한번뒤로

:sunny -> 동적인 정보 제공할때 콜론기호(:)사용
{
      path: '/:sunny'
      component: Home
}
:sunny정보를 가져올때 {this.$route.params.sunny}

404page

제일 아래쪽에 써줘야함
/:notFound(.) 자바스크립트 정규표현식규칙

{
      path: '/:notFound(.*)*',
      component: NotFound
}

nested routes

자식주소
children: [
  {
    path: '',
    component: ''
  }
]

Header.vue에 라우터링크 모아넣기

RouterLink를 배열화 시켜서 for in 문으로 쪼개기

data() {
  return {
    navigations: [
      { path: '/', name: '클릭당할이름'}
      { path: '/', name: '클릭당할이름'}
      { path: '/', name: '클릭당할이름'}
      { path: '/', name: '클릭당할이름'}
    ]
  }
}

Named view

파스칼케이스
객체데이터는 순서가 존재하지 않는다

components: {
        TheHeader,
        default: Movie
      }
components: {
        // 데이터이름과 속성의 이름이 같다
        TheHeader,
        default: Home,
      }

Redirect

// 페이지를 다시 팅겨낸다?. 임시로 보수중일때활용

redirect: '/fix'

Route Meta Fields

내용을 담을 수 있음

meta: {
        auth: ture
      }
created() {
      // 페이지 정보를 들고있는 아이 $route
      // meta:{여기에 저보를 담을 수 있음}
      // [[Prototype]]: Object
      console.log(this.$route)
}

Navigation Guards

모든 페이지에 접속하기 전, 모든페이지에서 빠져나올 때
네비게이션 가드를 통해 사용할 수 있음

Global Before Gurads(모든페이지에 접속하기 직전에)
라우터가드가 검문소 역할(걸러내준다?)

//라우터가드를 만들자
//main.js에도 넣어주는거 까먹지 말기
// createRouter를 router변수에 담아 실행

import router from './index.js'
// router.beforeEach((to, from) to는 어디로 들어갈건지, from은 어디에서 출발
// return 키워드 필수! true, false
router.beforeEach(to => {
  //to는 내가 접근하고자 하는 정보의 객체를 들고 있음
  console.log(to)
  console.log(to.meta.auth)
  // JSON은 기본적으로 문자형식. 로컬스토리지에 키(currentUser)벨류(name,age)값 직접 적고, 콘솔찍어보기
  console.log(localStorage.getItem('currentUser'))


  if (to.meta.auth) {
    console.log(JSON.parse(localStorage.getItem('currentUser')))
    const {
      name
    } = JSON.parse(localStorage.getItem('currentUser') || '{}')
    if (name) {
      return true
    } else {
      // return '/login'
      return '/'
    }
  }

  //boolean값을 반환해줘야함
  return true
})

페이지 스크롤 scrollBehavior

  // 페이지가 바뀔 때 스크롤을 어느위치에 둘 것인지
  // 페이지 관리 시 필 수 옵션
  scrollBehavior: () => ({
    top: 0
  })
//   아래코드를 축약
//   scrollBehavior: () => {
//       return {
//       top: 0
//       }
//   }

'프론트엔드 > Vue.js' 카테고리의 다른 글

Vue.js  (0) 2022.06.06
(22.05.26)Vue.js  (0) 2022.05.26
(22.05.25) Vue.js  (0) 2022.05.25
(22.05.23) Vue.js  (0) 2022.05.23

Vue.js

Vue.js는 프레임워크이자 라이브러리다. 공식 사이트에서는 뷰를 점진적인 프레임워크라고 부르고 있음

  • 프레임워크
    일정한 틀과 규칙에 따라 개발하도록 미리 구조를 정해 놓은 도구. 생산성 증가

  • 라이브러리
    기술 모음집. 자주 사용되는 기능들 재활용할 수 있도록

Vue.js특징

  • MVVM(Model-View-ViewModel)패턴.
    화면을 모델 - 뷰 - 뷰 모델로 구조화하여 개발하는 방식. 구조화 되어있어 코드 직곽적 이해, 추후 유지보수 편함
    View: 사용자 화면. Dom(돔. html문서에 들어가는 요소의 정보를 담고 있는 데이터 트리)
    Model: 데이터를 담는 용기. 보통 서버에서 가져온 데이터를 자바스크립트 객체형태로 저장
    ViewModel: 뷰와 모델의 중간 역역. 돔 리스너(돔의 변경 내역 즉각 반응 후 로직 수행하는 장치)와 데이터 바인딩(뷰에 표시되는 내용과 모델의 데이터 동기화)제공

  • 컴포넌트 기반 프레임워크
    왜 컴포넌트 기반? 코드의 재사용이 쉬워서
    컴포넌트: 레고블록과 같은. 잘 쌓으면 원하는 모형을 만들 수 있는 것과 같은.

  • 단방향 데이터 바인딩, 양방향 데이터 바인딩
    단방향데이터바인딩: 항상 상위->하위로 데이터전달
    양방향데이터바인딩: 모든 데이터 값 동기화. 한쪽을 바꾸면 다른쪽도 바뀜

뷰 인스턴스

  • new생성자 함수로 new Vue({})생성
    필요 기능(객체.속성들의 집합. 추상화:속성을 선택적으로 작성가능하기때문에 )을 생성자에 정의.
    new Vue({
      ...
    }) ->인스턴스
  • 뷰 인스턴스 옵션 속성
    template: html,css 등 화면에 표시할 마크업 요소를 정의하는 속성
    methods: 화면의 전반적인 동작과 관련된 로직 추가
    created: 뷰 인스턴스 생성 직후

  • 뷰 인스턴스 라이프 사이클
    라이프 사이클(생명주기): 인스턴스 상태에 따라 호출할 수 있는 속성들
    라이플 사이클 훅: 속성들이 커스텀 로직(개발자가 임의로 작성한 추가 로직)에 의해 실행되는것
    라이플사이클은 총 8개이며 4단계로 나눔
    인스턴스 생성 -> 인스턴스를 화면에 부착 -> 인스턴스 내용갱신 -> 인스턴스 소멸
    (인스턴스부착과 갱신구간은 데이터가 변경되는 경우에만 거치게 됨)
    created
    인스턴스 생성 직후기때문에 data와 methods 속성이 정의되서 this.data로 접근 가능. 단, 화면부착전이라 template 속성 접근 할 수 없음
    mounted
    el속성에서 지정한 화면 요소에 인스턴스 부착된직후기때문에 template속성에 접근 가능.
    watch
    뷰의 반응성
    변경 데이터 값과 관련된 로직을 미리 넣을 수 있음. 관찰만 가능
    computed
    데이터 값 변경 가능. 변경 데이터의 화면요소와 관련된 로직

뷰 컴포넌트

화면영역을 컴포넌트화 -> 코드직관적
크리구조(컴퓨터의 자료구조): 노드(부모-자식)

  • 컴포넌트 등록하기
    전역컴포넌트: 여러인스턴스 사용가능
    지역컴포넌트: 특정인스턴스만 사용가능(지정한곳이 아닌 다른곳에서 쓰면 html 사용자 정의로 인식)
    둘의 차이점은 유효범위.

뷰 컴포넌트 통신

뷰는 컴포넌트 단위로 구성. 다른 컴포넌트 값을 직접 참조 불가능
왜? 서로 고유한 유효범위(scope)를 갖기 때문에. 이는 뷰 프레임워크 내부적으로 정의된 특징
간접적으로 전달을 해야함
상•하위 컴포넌트 관계
상위컴포넌트 -> 하위컴포넌트: props을 통해 데이터전달
하위컴포넌트 -> 상위컴포넌트: 이벤트 발생

  • 상위컴포넌트에서 하위컴포넌트로 데이터 전달하기
    하위컴포넌트에 props: 'props 속성 이름' 정의
    상위컴포넌트에 v-bind: 'props 속성 이름' = "상위컴포넌트의data속성"

  • 하위컴포넌트에서 상위컴포넌트로 이벤트 전달하기
    이벤트발생과 수신
    인벤트발생(emit event)시켜 상위컴포넌트의 메서드를 호출
    하위컴포넌트에 이벤트발생(신호보냄): this.$emit('이벤트명')
    상위컴포넌트에 v-on: 이벤트명 = "상위 컴포넌트의 메서드명"

하위에서 상위로 데이터전달방법은 없나? event bus를 이용해 전달가능. 단, 단방향 데이터 흐름에 어긋나는 구현 방법

  • 관계없는 컴포넌트 간 통신- event bus
    별개의 새로운 인스턴스 생성 후
    보내는 컴포넌트쪽에서 .$emit('이벤트명,',데이터)
    받는 컴포넌트쪽에서 created구간 .$on('이벤트명', function(데이터받을인자))

단점: 컴포넌트가 많아지면 어디서 어디로 보냈는지 관리가 되지 않기때문에 뷰엑Vuex 상태관리도구 필요

뷰 라우터(Vue Router)

  • 라우팅
    라우터를 알기위해선 우선 라우팅을 이해
    라우팅: 웹페이지 간의 이동방법. SPA(Single Page Application. 미리 해당 페이지 받아놓고 화면을 갱신하는 패턴을 적용)에서 주로 사용
    일반적인 웹브라우저 웹페이지 요청은 서버에서 응답받아 다시 사용자에게 돌려주는 시간동안 화면 상에 깜빡거리는 현상이 있음
    이를 라우팅으로 처리하면 화면 매끄럽게전환 가능, 화면을 빠르게 조작할 수 있게되서 사용자 경험 향상

  • 뷰 라우터
    라우팅을 구현할 수 있도록 지원하는 공식 라이브러리

태그 설명
<router-link to ="URL값"> 페이지 이동 태그. 화면에 <a>로 표시
<router-view> 페이지 표시 태그. 해당 컴포넌트를 뿌려주는 영역
  • $mount() API
    el 속성과 동일하게 인스턴스를 화면에 붙이는 역할. 생성된 인스턴스를 $mount()를 이용해 붙임 ex)$mount('#app')

여러 개의 컴포넌트를 동시에 표시할 수 있는 라우터 네스트드 라우터, 네임드 뷰

  • 네스티드 라우터 (nested router)

    children: [
      { 
      path: ..., 
      component: ...
      },
      { 
      path: ..., 
      component: ...
      }
    ]

    네스티드 라우터에는 최상위 컴포넌트와 최상위 컴포넌트 하위 컴포넌트에도 router-vuew 가 있다는 점
    화면을 구성하는 컴포넌트의 수가 적을 때 유용하지만 많은 컴포넌트 표시하는데 한계가 있음

  • 네임드 뷰 (named view)
    특정 페이지로 이동했을 때 여러 개의 컴포넌트를 동시에 표시하는 라우팅 방식
    name속성을 추가

    componets: {
    default: ...,
    header: ...,
    footer: ...  
    }
    



'프론트엔드 > Vue.js' 카테고리의 다른 글

(22.06.08)Vue.js  (0) 2022.06.08
(22.05.26)Vue.js  (0) 2022.05.26
(22.05.25) Vue.js  (0) 2022.05.25
(22.05.23) Vue.js  (0) 2022.05.23

22.05.26

컴포넌트 기초

2가지 개념

  1. 캡슐화(모듈화)//얄약같은 감싸는용도. 같이작동할수도 있고 따로 동작할수도 있음
  2. 재사용
    드롭다운메뉴같은것
    컴포넌트 만드는방법 2가지
  3. 전역 등록 전역컴포넌트를 만드는건 적음
  4. 지역 등록

부모 자식간의 데이터 통신
props 부모와 자식관계 데이터 전달
부모에서 자식으로 들어가기만 하는 !!단방향 데이터
그래서 수정권한은 부모한테 있다
props속성만들기(이름자유지만 유지보수쉽게)
그래서! emits을 사용해 줘야지 됨!!!
emits 자식에서 부모를 올려주는 것. 이벤트발생하는것 v-on , @

부모->자식 props 단방향데이터
자식->부모 emits

컴포넌트 속성 상속

App.vue

<template>
  <MyBtn>Banana</MyBtn>
  <!-- Props -->
  <!-- 부모의 데이터바인딩 색상적용 -->
  <MyBtn :color="color">
    <span style="color: red">Cherry</span>
  </MyBtn>
  <MyBtn
    color="royalblue"
    large>
    Grape
  </MyBtn>
  <MyBtn>Apple</MyBtn>
  <MyBtn
    class="jessi">

    <span style="color: yellow">JEESIE</span>
  </MyBtn>
  <MyBtn
    class="jessi"
    style="color: red"
    title="Hello world">
    JEESIE
  </MyBtn>
  <button>Banana</button>
</template>
<script>
import MyBtn from '~/components/MyBtn.vue'

export default {
  components: {
    MyBtn
  },
  data() {
    return  {
      color: '#000'
    }
  }
}
</script>

MyBtn.vue

<template>
  <div 
    :class="{large: large}"
    :style="{ backgroundColor: color }"
    class="btn">
    <slot></slot>
  </div>
  <h1 v-bind="$attrs"></h1>
</template>
<script>
  export default {
    // 어떠한 속성도 상속 안하고 싶을때
    inheritAttrs: false,
    props: {
      color: {
        type: String,
        default: 'lightpink'
      },
      large: {
        type: Boolean,
        dsfault: false
      },     
    },
    // 상속을 특정한 요소에 직접적으로 해주고 싶을때 $attrs
    created() {
      // $attrs객체연결
      console.log(this.$attrs)
    } 
  }
</script>

<style>
  .btn {
    display: inline-block;
    padding: 6px 12px;
    border-radius: 4px;
  }
  .btn.large {
    font-size: 20px;
    padding: 10px 20px;
  }
</style>

컴포넌트 Emit

부모컴포넌트와 자식 컴포넌트연결

App.vue

<template>
  <!-- 카멜케이스는 안먹히니까 대시케이스로 꼭 수정 -->
  <MyBtn
    @onHello="log"
    @change-msg="logMsg">
    BlueBerry
  </MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn.vue'

export default {
  components: {
    MyBtn
  },
  methods: {
    log(event) {
      console.log('DoubleClick')
      console.log(event)
    },
    logMsg(msg) {
      console.log(msg)
    }
  }
}
</script>
<template>
  <!-- App.vue는 MyBtn의 최상위 요소에 적용되 있음 -->
  <!-- 만약 형제요소가 생긴다면? log()가 다시 안먹힘! -->
  <div class="btn">
    <!-- BlueBerry가 들어가는 slot -->
    <slot></slot>
  </div>
  <!-- $emit은 두번째인수로는 데이터를 같이 넘겨줄 수 있음 $event라는 객체를 넘겨주면? MouseEvent-->
  <h1 @dblclick="$emit('onHello', $event)">
    ABC
  </h1>
  <!-- v-model 양방향 데이터 -->
  <input
    v-model="msg">
</template>

MyBtn.vue

<script>
  export default {
    // 속성 상속 거부 App.vue파일의 log()함수가 작동안됨
    // inheritAttrs: false
    // emits를 배열데이터로 작성 후 부모요소의App.vue에 있는 click이벤트를 사용하기위해 가져와서
    // 자식요소 $emit()이 붙어있는 곳에 명시된 'click'을 그대로 붙여넣으면 적용이 됨!
    // 즉 특정한 이벤트를 상속받아서 $emit메소드를 통해서 부모요소에 연결이 되있는 클릭이벤트를 실행하게됨
    // 컴포넌트가 사용되는 곳에서 우리가 1.연결하는 이벤트는 원하는 이름 아무거나,
    // 2.대신에 정확히 emits라는 옵션에 받아서 3.그것이 어디서 어떻게 사용될지 $emit()정의 후 실제 원하는이벤트 연결해서 쓸 수 있음
    emits: [
      'onHello',
      'changeMsg'
    ],
    data() {
      return {
        msg: 'Hello Vvue!'
      }
    },
    watch: {
      msg(e) {
        this.$emit('changeMsg', this.msg.trim(e))
      }
    }
  }
</script>

컴포넌트 Slot

v-slot의 약어 : #
이름을 갖는 슬롯(Named Slots)

App.vue

<template>
  <MyBtn>
    <!-- `v-slot`의 약어 : `#` -->
    <template #text>
      <span>Bolt</span>
    </template>

    <template #icon>
      <span>&#9889;</span>
    </template>
  </MyBtn>
</template>

<script>
import MyBtn from '~/components/MyBtn.vue'
export default {
  components: {
    MyBtn
  }
}
</script>

MyBtn.vue

<template>
  <div class="btn">
    <!-- Fallback contents MyBtn내용이 없을때 대체되서 나올 수 있는 내용 -->
    <!-- 이름을 갖는 슬롯(Named Slots) slot에 name을 부여해서 순서를 보장해줌 App.vue파일에 순서가 바껴도 순서바뀜없이 잘 나옴 -->
    <slot name="icon"></slot>
    <slot name="icon"></slot>
    <slot name="text"></slot>
    <slot name="icon"></slot>
    <slot name="icon"></slot>
  </div>
</template>

<style>
  .btn {
    background-color: lightcoral;
    display: inline-block;
  }
</style>

컴포넌트 Provide, Inject

App.vue파일에 자식인 Parent.vue가 있고 그 하위요소로 Child.vue가 있는 상태
Child.vue파일에 있는 내용을 App.vue 파일에 전달할려면,
Child->Parent->App으로 import해서 가야하는 비효율적인 단계를 거쳐야함
아래 예시를 보면 Parent.vue는 전달만 하는 비효율적인 사태가 벌어짐
props에 다시 props를 적용하고있음

효율성을 높이기 위해서
조상요소에서 특정한 하위요소로 보내줄 수 있음
단점은 조상요소에서 데이터가 변경되더라도 하위요소에는 적용이 안됨. props와 다르게 반응성을 가지지 않음
App.vue 파일에 Provide:{} 를 만들어주고,
Child.vue 파일에 Inject:[] 배열데이터를 만들어준다
!-- Provide, Inject 적용 전 --------------------------------------------------

!-- App.vue --------------------------------------------------

<template>
  <Parent :msg= "message" />
</template>

<script>
import Parent from '~/components/Parent'

export default {
  components: {
    Parent
  },
  data() {
    return {
      message: 'Hello world'
    }
  }
}
</script>

!-- Parent.vue --------------------------------------------------

<template>
  <Child :msg="msg" />
</template>

<script>
import Child from '~/components/Child'

export default  {
  components: {
    Child
  },
  props: {
    msg: {
      type: String,
      default:''
    }
  }
}
</script>

!-- Child.vue --------------------------------------------------

<template>
  <div>
    {{ msg }}
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ''
    }
  }
}
</script>

!--! Provide, Inject 적용 후 --------------------------------------------------
!--? App.vue --------------------------------------------------

<template>
  <button @click="message = 'Good?'">
    Click
  </button>
  <h1>App: {{ message }}</h1>
  <Parent />
</template>

<script>
import Parent from '~/components/Parent'
// 계산된
import { computed } from 'vue'

export default {
  components: {
    Parent
  },
  // provide()로 반응성을 유지하는 데이터를 만들려면 computed를 사용해 콜백사용
  provide() {
    return {
      msg: computed ( () => this.message )
    }
  },
  data() {
    return {
      message: 'Hello world'
    }
  }
}
</script>

!--? Parent.vue --------------------------------------------------

<template>
  <Child />
</template>

<script>
import Child from '~/components/Child'

export default  {
  components: {
    Child
  }
}
</script>

!--? Child.vue --------------------------------------------------

<template>
  <div>
    Child: {{ msg.value }}
  </div>
</template>

<script>
export default {
  //데이터처럼취급
  inject: ['msg']
}
</script>

컴포넌트 Refs

class id 선택자 대신 vue에서 제공하고 있는 ref(reference참조)
mounted 라이플사이클 사용할것. 왜? html이 연결된후라서

App.vue

<template>
  <Hello ref="hello" />
</template>

<script>
import Hello from '~/components/Hello.vue'

export default {
  components: {
    Hello
  },
  // ref접근은 $refs로 reference이름으로 hello를 사용함
  // this.$refs.hello = <h1 ref = "hello"></h1>
  // * created는 컴포넌트가 생성된 직후
  // * mounted는 html구조에 컴포넌트가 연결된 직후
  mounted() {
    console.log(this.$refs.hello.$el) //최상위가 한개가 아니면 제대로된 값을 불러오지 못함
    console.log(this.$refs.hello.$refs.good)
  },
}
</script>

Hello.vue

<template>
  <h1>
    Hello Vue!
  </h1>
  <h2 ref="good">
    Good Vue!
  </h2>
</template>

컴포지션 API

setup()은 반응성이 없기 때문에 vue에서 제공하고 있는 ref를 객체분해로 들고와서
반응성을 제공해 줘야함
초기값을 할당을 해주고 동작시키면 객체데이터를 할당
객체데이터를 데이터로 쓰기 위해서는 value속성을 사용해주기

<template>
  <div @click="increase">
    {{ count }}
  </div>
</template>
<script>
import { ref } from 'vue'
  export default {

    // data() {
    //   return {
    //     count: 0
    //   }
    // },
    // methods: {
    //   increase() {
    //     this.count += 1
    //   }
    // },
    // 반응성 x 그래서 ref기능을 객체구조분해로 vue패키지에서 가져와줘야함
    setup() {
      // 함수를 실행해서 그 함수의 인수로 초기값을 만들어줘야함! 초기화할 0을 넣어줌
      // 반응성을 가진 하나의 객체데이터가 count로 반환이됨
      // count는 객체데이터이다 보니까 바로 사용할 수 없음
      // 데이터로 사용해 주기 위해서 value속성 사용해줌
      let count = ref(0)
      function increase() {
        count.value += 1
      }
      return {
        count, 
        increase
      }
    }
  }
</script>

컴포지션 API 비교

App.vue

<template>
  <h1 @click="increase">
    {{ count }} / {{ doubleCount }}
  </h1>
  <h1 @click="changeMessage">
    {{ message }} / {{ reverseMessage }}
  </h1>
</template>
<script>
export default {
  data() {
    return {
      count: 0,
      message: 'Hello World'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    },
    reverseMessage() {
      return this.message.split('').reverse().join('')
    }
  },
  watch: {
    message(msgValue) {
      console.log(msgValue)
    }
  },
  // 라이프사이클
  // created()는 컴포넌트 생성된 직후
  created() {
    console.log(this.count)
  },
  // mounted() html, 컴포넌트가 연결될 직후
  mounted() {
    console.log(this.count)
  },
  methods: {
    increase() {
      this.count += 1
    },
    changeMessage() {
      this.message = 'Good!'
    }
  } 
}
</script>

App.Composition.vue

<template>
  <h1 @click="increase">
    {{ count }} / {{ doubleCount }}
  </h1>
  <h1 @click="changeMessage">
    {{ message }} / {{ reverseMessage }}
  </h1>
</template>
<script>
// vue패키지에서 ref 객체구조분해를 통해 가져옴
// mount -> onMounted
// 객체구조분해후 computed는 변수선언후 computed콜백함수실행, watch도 감시할대상, 두번째에 변수선언.
// onMounted 함수 선언 후 콜백함수사용
// created, beforeCreated는 라이프사이클이필요하지 않음 왜??
// setup은 created, beforeCreated 라이프사이클 훅 사이에 실행되는 시점이라서
import { ref, computed, watch, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    // computed는 변수 선언 후 함수처럼 사용해주면 됨
    // 콜백함수로 반환된 값이 doubleCount에서 사용 가능해지게됨
    const doubleCount = computed(function () {
      return count.value * 2
    })
    function increase() {
      count.value += 1
    }

    const message = ref('Hello World')
    const reverseMessage = computed(() => {
      return message.value.split('').reverse().join('')
    })
    watch(message, msgValue => {
      console.log(msgValue)
    })
    function changeMessage() {
      message.value = 'Good!'
    }
    //created()라이프사이클과 동일효과
    console.log(message.value)

    onMounted(()=> {
      console.log(count.value)
    })

    // setup()데이터 내부에서 반환을 해줘야함
    return {
      count,
      doubleCount,
      increase,
      message,
      changeMessage,
      reverseMessage
    }
  }


}
</script>

'프론트엔드 > Vue.js' 카테고리의 다른 글

(22.06.08)Vue.js  (0) 2022.06.08
Vue.js  (0) 2022.06.06
(22.05.25) Vue.js  (0) 2022.05.25
(22.05.23) Vue.js  (0) 2022.05.23

22.05.25

라이프사이클

created()는 컴포넌트 생성된 직후
mounted() html, 컴포넌트가 연결될 직후

<template>
  <h1 @click="increase">
    {{ count }} / {{ doubleCount }}
  </h1>
  <h1 @click="changeMessage">
    {{ message }} / {{ reverseMessage }}
  </h1>
</template>
<script>
export default {
  data() {
    return {
      count: 0,
      message: 'Hello World'
    }
  },
  // 계산된데이터
  computed: {
    doubleCount() {
      return this.count * 2
    },
    reverseMessage() {
      return this.message.split('').reverse().join('')
    }
  },
  watch: {
    message(msgValue) {
      console.log(msgValue)
    }
  },
  // 라이프사이클
  // created()는 컴포넌트 생성된 직후
  created() {
    console.log(this.count)
  },
  // mounted() html, 컴포넌트가 연결될 직후
  mounted() {
    console.log(this.count)
  },
  methods: {
    increase() {
      this.count += 1
    },
    changeMessage() {
      this.message = 'Good!'
    }
  } 
}
</script>

객체구조분해할당

<template>
  <button @click="click">
    Click!
  </button>
  <!-- 객체구조분해할당 fruit -->
  <li
    v-for="{name, index} in newfruits"
    :key="name">
    {{ name }}-{{ index }}
  </li>
  <hr />
  <!-- v-for문 -->
  <li
    v-for="fruit in newfruits"
    :key="fruit">
    {{ fruit.name }}-{{ fruit.id }}-{{ fruit.index }}
  </li>
</template>
<script>
// id속성 부여해보기
import { nanoid } from 'https://cdn.skypack.dev/nanoid'

export default {
  data() {
    return {
      fruits: ['apple', 'banana', 'grape']
    }
  },
  computed: {
    newfruits() {
      // map은 새로운 배열반환
      return this.fruits.map( (fruit, index) => ({
        id: nanoid(),
        name: fruit,
        index
      }))
    }
  },
  methods: {
    click() {
      this.fruits.push('oragne')
    }
  }
}

</script>

양방향 데이터 바인딩

v-model 디렉티브를 사용함
CJK 중국어, 일본어, 한국어는 v-model이 업데이트 되지 않음

v-on:input
input요소에 데이터가 입력될때마다 이벤트 -> @input

<template>
  <h1 @click="click">
    {{ msg }}
  </h1>
  <!--? 한글도 바로 작성되는걸 볼 수 있는 수식어 양뱡향 데이터 바인딩-->
  <input
    :value="msg"
    @input="msg = $event.target.value" />

  <!--? v-model로 사용법은 간단해졌지만 한글출력이 한박자 늦음 양뱡향 데이터 바인딩-->
  <input
    v-model="msg" />

  <h1>{{ checked }}</h1>
  <input
    v-model="checked"
    type="checkbox" />
</template>
<script>
export default {
  data() {
    return {
      msg : 'Hello Vue!!',
      // 체크가 해제된 상태에서 실행
      checked: false
    }
  },
  //event출력, event.target 대상출력 ->이벤트객체출력
  methods: {
    click(event) {
      console.log(event)
      console.log(event.target)
      console.log(event.target.textContent)
    }

  },
}
</script>

양방향 데이터 바인딩 v-model 수식어

.lazy

<input
    v-model.lazy="msg"/>
.lazy: 입력완료되고 다른행동을할때 값 출력

.number

<input 
  v-model.number="msg"/>
.number: 입력값을 숫자로 유지하고싶을때  

.trim

<input
    v-model.trim="msg"/>
.trim: 앞뒤 공백입력값 없앨때
<template>
  <h1>{{ msg }}</h1>
  <input
    v-model="msg"/>
</template>


<script>
export default {
  data() {
    return {
      msg: ''
    }    
  },
  watch: {
    msg() {
      console.log(typeof this.msg) //string으로 출력되기때문에 숫자값유지하고싶을때.number사용해주기
    }
  }
}
</script>

<style>
  body {
    font-size: 3em;
  }
</style>

폼 입력 바인딩

팩토리함수: 함수가 어떤데이터를 반환할건데 함수가 참조형(객체, 배열, 함수)데이터를 return으로 반환하는걸
리턴키워드를 통해서 객체데이터를 반환하니까

데이터를 반환(return)하는 형식으로 해줘야지 데이터 함수를 한번 호출될 때 객체리터럴은 곳 객체를 선언하는것.
그래서 데이터는 꼭 함수로 만들어줘야지 꼬이지 않음

끄적

https로 만드는 이유!
로컬에서는 http상관없음.

http와 https와 통신하는걸 막아버림
http -> https 양뱡향 가능하지만
https -> http 는 안됌! 브라우저 차원에서 통신차단

실시간이미지는 선생님 홈페이지에서 확인해보기

'프론트엔드 > Vue.js' 카테고리의 다른 글

(22.06.08)Vue.js  (0) 2022.06.08
Vue.js  (0) 2022.06.06
(22.05.26)Vue.js  (0) 2022.05.26
(22.05.23) Vue.js  (0) 2022.05.23

+ Recent posts