본문 바로가기

블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기

[96일차] 20210727 React 리액트, 레이아웃 CSS, 새로고침할 때 CSS 풀리는 현상,

반응형

1. Header 모듈로 만들기 + ul, li styled component 

components > layout 폴더 > Header.jsx 만들어서 

BlogLayout 의 header부분 따로 빼기 

 

import Link from 'next/link'
import NavToggle from '../NavToggle'
// import Styled from 'styled-components'

// const 

const Header =()=>{
    return(
        <>
            <div className="header">
                {/* 로고 & 메뉴  */}
                <h1>로고</h1>
                <ul>
                    <li>
                        <Link href="/">
                            <a>Home</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/posts/post">
                            <a>글쓰기</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/login">
                            <a>login</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/join">
                            <a>join</a>  
                        </Link>
                    </li>
                </ul>
                <NavToggle/>
            </div>
        </>
    )
}

export default Header

 

CSS : Styled  주기 

import Link from 'next/link'
import NavToggle from '../NavToggle'
import Styled from 'styled-components'

const HeaderContainer = Styled.div`
    display:flex;
    flex-direction:row;
    align-items:center;
    justify-content:space-between;
    padding: 0 5vw;
    box-sizing:border-box;
    border-bottom:1px solid #ddd;
    width: 100vw;
`

const Header =()=>{
    return(
        <>
            <HeaderContainer>
                {/* 로고 & 메뉴  */}
                <h1>로고</h1>
                <ul>
                    <li>
                        <Link href="/">
                            <a>Home</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/posts/post">
                            <a>글쓰기</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/login">
                            <a>login</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/join">
                            <a>join</a>  
                        </Link>
                    </li>
                </ul>
                <NavToggle/>
            </HeaderContainer>
        </>
    )
}

export default Header
npm run dev   --> localhost:3001 

 

 

ul - sytled 만들어서 대체하기 

import Link from 'next/link'
import NavToggle from '../NavToggle'
import Styled from 'styled-components'

const HeaderContainer = Styled.div`
    display:flex;
    flex-direction:row;
    align-items:center;
    justify-content:space-between;
    padding: 0 5vw;
    box-sizing:border-box;
    border-bottom:1px solid #ddd;
    width: 100vw;
    height:10vh;
`

const Gnb = Styled.ul`
    // 모바일 
    display:flex;
    flex-direction:row;

    // pc 내용들 
    @media only screen and (max-width:768px){
        display:none;
    }
`

const Header =()=>{
    return(
        <>
            <HeaderContainer>
                {/* 로고 & 메뉴  */}
                <h1>로고</h1>
                <Gnb>
                    <li>
                        <Link href="/">
                            <a>Home</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/posts/post">
                            <a>글쓰기</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/login">
                            <a>login</a>  
                        </Link>
                    </li>
                    <li>
                        <Link href="/join">
                            <a>join</a>  
                        </Link>
                    </li>
                </Gnb>
                <NavToggle/>
            </HeaderContainer>
        </>
    )
}

export default Header

ul 이랑 li 는 셋트 

const Gnb = Styled.ul`
    // 모바일 
    display:flex;
    flex-direction:row;

    & > li{
        margin-left:20px;
    } 

    // pc 내용들 
    @media only screen and (max-width:768px){
        display:none;
    }
`

 

 

 

 

 

2. 새로고침할 떄마다 CSS 풀리는 현상

SSR - Styled Component가 안먹힘  (next에서 styled component plugin을 설정 안해놓음 ) 

styled-component는 react가 만든 것이 아닌 다른 사람이 든거라 -> 설정 해야 함 

 

pages>_document.jsx 에 아래 내용 입력 

import Document from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      // sheet을 사용해 정의된 모든 스타일을 수집
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        });

      // Documents의 initial props
      const initialProps = await Document.getInitialProps(ctx);

      // props와 styles를 반환
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }
}

 

 

front > .babelrc 파일 생성 

{
    "presets":["next/babel"],
    "plugins":[
        ["styled-components", {"ssr":true}]
    ]


}

 

아래 package 다운 

npm i -D babel-plugin-styled-components

 

 

3. NavToggle.jsx

                <span></span>
                <span></span>
            </label>
            <div>
                asdf
            </div>
        </Toggle>

위치만 확인해보기 

StyledComponent 만들어서 적용하기 

const Accordion = Styled.div`
    position:absolute;
    width:100%;
    left:0px;
    top:10vh;
    z-index:5;
    background: #fff;

`



const NavToggle = () => {
    return (
        <Toggle>

            <input
                type="checkbox"
                id="nav-toggle"
                className="nav-toggle"
            />
            <label htmlFor="nav-toggle">
                <span></span>
                <span></span>
                <span></span>
            </label>
            <Accordion>
                <ul>
                    <li>대분류 메뉴1</li>
                    <li>대분류 메뉴2</li>
                    <li>대분류 메뉴3</li>
                    <li>대분류 메뉴4</li>
                    <li>대분류 메뉴5</li>
                </ul>
            </Accordion>
        </Toggle>
    )
}

위의 메뉴들 예쁘게 만들기

const Accordion = Styled.div`
    position:absolute;
    width:100%;
    left:0px;
    top:10vh;
    z-index:5;
    background: #fff;
    padding:7vh 0;

    & > ul{
        margin-top:5vh;
        display:flex;
        flex-direction:column;
    }

    &>ul>li{
        margin-top:20px;
        text-align:center;
    }

`

 

 

Styeld component의 장점 : 

props 값을 던질 수 있고 css에서 받을 수 있다. -> display none or block 할 지 정할 수 있다. 

 

4.

NavToggle.jsx 

useState 가져오기 

import Styled from 'styled-components'
import {useState} from 'react'

Label에 onClick 하면 click 두번 발생됨 

input에다가 onclick event 주기 

const NavToggle = () => {

    const [visible, setVisible] = useState(false)
    const handleToggle = ()=>{
        setVisible(!visible)
    }
    
    return (
        <Toggle>
            <input
                type="checkbox"
                id="nav-toggle"
                className="nav-toggle"
                onClick={handleToggle}
            />
            <label htmlFor="nav-toggle">
                <span></span>
                <span></span>
                <span></span>

Accordion css compo에게 props 주기 

            <Accordion flag={visible}>
                <ul>
                    <li>대분류 메뉴1</li>
                    <li>대분류 메뉴2</li>
                    <li>대분류 메뉴3</li>
                    <li>대분류 메뉴4</li>
                    <li>대분류 메뉴5</li>
                </ul>
            </Accordion>
        </Toggle>

 

이제 Accodion css compo에서 props 사용하기

${} - JS 영역이라고 알려주는 것 

 

import Styled from 'styled-components'
import {useState} from 'react'

const Toggle = Styled.div`
background:transparent;
border-color:transparent;

& > .nav-toggle {
    display:none;
}

& > .nav-toggle + label{
    display:block;
    width:2.5rem;
    height:2rem;
    position:relative;      
    cursor:pointer;
}

& > .nav-toggle + label > span {
    display:block;
    position:absolute;
    width:100%;
    height:5px;
    border-radius:30px;
    background:#000;
    transition:all .35s
}

& > .nav-toggle + label > span:nth-child(1){ top: 0 }
& > .nav-toggle + label > span:nth-child(2){ 
    top:50%;
    transform:translateY(-50%)
}
& > .nav-toggle + label > span:nth-child(3){ bottom: 0 }


& > .nav-toggle:checked + label > span:nth-child(1){ 
    top:50%;
    transform:translateY(-50%) rotate(45deg);
}
& > .nav-toggle:checked + label > span:nth-child(2){ 
    opacity:0;
}
& > .nav-toggle:checked + label > span:nth-child(3){ 
    bottom: 50%;
    transform:translateY(50%) rotate(-45deg);
}
`

const Accordion = Styled.div`
    position:absolute;
    width:100%;
    left:0px;
    top:10vh;
    z-index:5;
    background: #fff;
    padding:7vh 0;

    // ()=>{} 근데 {} 요 괄호를 생략해줌
    // props 가 가르키는것 : Accodion 이 가진 모든props를 말함 
    display:${(props)=> (props.flag) ? 'block':'none'};



    & > ul{
        margin-top:5vh;
        display:flex;
        flex-direction:column;
    }

    &>ul>li{
        margin-top:20px;
        text-align:center;
    }

`

const NavToggle = () => {

    const [visible, setVisible] = useState(false)
    const handleToggle = ()=>{
        setVisible(!visible)
    }
    
    return (
        <Toggle>
            <input
                type="checkbox"
                id="nav-toggle"
                className="nav-toggle"
                onClick={handleToggle}
            />
            <label htmlFor="nav-toggle">
                <span></span>
                <span></span>
                <span></span>
            </label>
            <Accordion flag={visible}>
                <ul>
                    <li>대분류 메뉴1</li>
                    <li>대분류 메뉴2</li>
                    <li>대분류 메뉴3</li>
                    <li>대분류 메뉴4</li>
                    <li>대분류 메뉴5</li>
                </ul>
            </Accordion>
        </Toggle>
    )
}

export default NavToggle

클릭하면 껏다가 켜짐 

 

 

5. 

formLayout.jsx

import Styled 

import Router from 'next/router'
//import styled from './FormLayout.module.css' //요 안의 모든 css를 객체로 변환 
import Styled from 'styled-components'

-

import Router from 'next/router'
//import styled from './FormLayout.module.css' //요 안의 모든 css를 객체로 변환 
import Styled from 'styled-components'


const FormLayout = ({ children }) => {
    return (
        <>
            <div>
                <div>
                    <button onClick={() => Router.back()}>뒤로가기</button>
                    {children}
                    {/* Footer html  // 나중에 footer부분 / header도 따로 compo로 나누기
                    <div className={styled.footer}>
                        copyright &copy; all reserved
                    </div> */}
                </div>
            </div>
        </>
    )
}

export default FormLayout

-

import Router from 'next/router'
//import styled from './FormLayout.module.css' //요 안의 모든 css를 객체로 변환 
import Styled from 'styled-components'

const Background = Styled.div`
    width:100vw;
    height:100vh;
    background:#eee;
    display:flex;
    align-items:center;
    justify-content:center;

    &>div{
        width:300px;
        height:400px;
        background:#fff;
        padding:20px;
    }
`

const FormLayout = ({ children }) => {
    return (
        <>
            <Background>
                <div>
                    <p onClick={() => Router.back()}>뒤로가기</p>
                    {children}
                    {/* Footer html  // 나중에 footer부분 / header도 따로 compo로 나누기
                    <div className={styled.footer}>
                        copyright &copy; all reserved
                    </div> */}
                </div>
            </Background>
        </>
    )
}

export default FormLayout

 

 

 

6. 

login.jsx 를 form으로 만들어주기 

import FormLayout from "../components/FormLayout"
import Head from 'next/head'


const Login = () => {
    return (
        <>
            <Head>
                <title>Blog | 로그인</title>
            </Head>
            <FormLayout>
                로그인 페이지입니다. 
                <h2>로그인</h2>
                <form>
                    <input type="text" placeholder="아이디를 입력해주세욥" />
                    <input type="password" placeholder="패스워드를 입력해주세욮" />
                    <button type="submit">로그인</button>
                </form>
            </FormLayout>
        </>
    )
}

export default Login

우리가 많이 써봤던 방법 ↓↓↓

import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import {useState} from 'react'

const Login = () => {
    const [userid, setUserid] = useState('')
    const [userpw, setUserpw] = useState('')

    const ChangeUserid =e=>{
        setUserid(e.target.value)
    }

    const ChangeUserpw =e=>{
        setUserpw(e.target.value)
    }

    const handleSubmit =e=>{
        e.preventDefualt()
        console.log(serid,userpw)
    }

    return (
        <>
            <Head>
                <title>Blog | 로그인</title>
            </Head>
            <FormLayout>
                로그인 페이지입니다. 
                <h2>로그인</h2>
                <form onSubmit={handleSubmit}>
                    <input type="text" onChange={ChangeUserid} placeholder="아이디를 입력해주세욥" />
                    <input type="password"  onChange={ChangeUserpw} placeholder="패스워드를 입력해주세욮" />
                    <button type="submit">로그인</button>
                </form>
            </FormLayout>
        </>
    )
}

export default Login

 

 

 

위의 코드를 custom hook 으로 짧게 만들어보기 

import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import {useState} from 'react'

const userInput  =(defaultValue)=>{
    const [value,setValue] = useState(defaultValue)
    const onChange =e=>{
        setValue(e.target.value)
    }

    return{  //useInput에 들어가 있는 상태 value값 은 value에 onChange함수는 onChange함수에 넣기 
        value,onChange
    }
}

const Login = () => {

    const userid = userInput('')
    const userpw = userInput('')

    const handleSubmit=(e)=>{
        e.preventDefault()
        console.log(userid,userpw)
    }
    
    return (
        <>
            <Head>
                <title>Blog | 로그인</title>
            </Head>
            <FormLayout>
                로그인 페이지입니다. 
                <h2>로그인</h2>
                <form onSubmit ={handleSubmit}>
                    <input type="text" {...userid} placeholder="아이디를 입력해주세욥" />
                    <input type="password" {...userpw} placeholder="패스워드를 입력해주세욮" />
                    <button type="submit">로그인</button>
                </form>
            </FormLayout>
        </>
    )
}

export default Login

 

 

* 아래 그림 잘 분석 ! 

두 코드는 같다.

value="ok"   == {...{"value":"ok"}}

import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import {useState} from 'react'

const userInput  =(defaultValue)=>{
    const [value,setValue] = useState(defaultValue)
    const onChange =e=>{
        setValue(e.target.value)
    }
//useInput에 들어가 있는 상태 value값 은 value에 onChange함수는 onChange함수에 넣기 
    return{  
        value,onChange
    }
}

const Login = () => {

    const userid = userInput('')  // object
    const userpw = userInput('')  // object

    const handleSubmit=(e)=>{
        e.preventDefault()
        console.log(userid.value,userpw.value)

        userid.value==="asdf" && userpw.value==="asdf" ? alert('뒤로가기 or main으로 가기') : alert('id 또는 pw가 다릅니다.')
    }

    return (
        <>
            <Head>
                <title>Blog | 로그인</title>
            </Head>
            <FormLayout>
                로그인 페이지입니다. 
                <h2>로그인</h2>
                <form onSubmit ={handleSubmit}>
                    {/* html 형태로 보이게끔 한 JavaScript이다! (bable로 가능케함) */}
                    {/* babel은 type="text" 를 객체로 바꿔줌 type="text" => "type":"text" */}
                    {/* {} 대괄호를 쓰면 JS 구문을 쓸 수 있게 해주겠다.  */}
                    <input type="text" {...userid} placeholder="아이디를 입력해주세욥" />
                    <input type="password" {...userpw} placeholder="패스워드를 입력해주세욮" />
                    <button type="submit">로그인</button>
                </form>
            </FormLayout>
        </>
    )
}

export default Login

 

뒤로 보내는 것보다 메인으로 보내주거나 or url 내가 설정해주는게 좋음 

 

import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import {useState} from 'react'
import Router from 'next/router'
.
.
.
    const handleSubmit=(e)=>{
        e.preventDefault()
        console.log(userid.value,userpw.value)

        userid.value==="asdf" && userpw.value==="asdf" ? Router.push('/') : alert('id 또는 pw가 다릅니다.')
    }

 

 

7. 로그인 후 메인으로 보내졌을 때 메뉴 바꿔주기 

Login되었다는 상태 저장할 공간 필요 

context & useReducer 사용해보기 

 

* Context 놓을 곳 찾기 

 

 

App == _app.jsx 

 

8. context 사용하기 

blogLayout.jsx

 

다른 곳에서 Store context를 사용해야하는데 사용하기 편하기 위해 context를 따로 뻄 

pages> store>context.jsx

import {createContext} from 'react'

export const initialState = {
    IsLogin: false,
}
const Store = createContext(initialState)

export default Store

_app

import '../index.css'
import Head from 'next/head'
import Store,{ initialState } from './store/context'


const App = ({ Component }) => {
    return (
        <>
            <Head>
                <link rel="preconnect" href="https://fonts.googleapis.com" />
                <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
                <link href="https://fonts.googleapis.com/css2?family=Black+Han+Sans&display=swap" rel="stylesheet" />
            </Head>
            <Store.Provider value={initialState}>
                <Component /> {/* 우리가 만든 파일(index,login..)들이 여기에 위치 */}
            </Store.Provider>
        </>
    )
}

export default App

 

 

Header에서 사용하기

import Link from 'next/link'
import NavToggle from '../NavToggle'
import Styled from 'styled-components'
import {useContext} from 'react'
import Store from '../../pages/store/context'


const Header = () => {

    const globalStore = useContext(Store)
    const { IsLogin } = globalStore

    return (
        <>
            <HeaderContainer>
                {/* 로고 & 메뉴  */}
                <h1>로고</h1>
                <Gnb>
                    <li>
                        <Link href="/">
                            <a>Home</a>
                        </Link>
                    </li>
                    <li>
                        <Link href="/posts/post">
                            <a>글쓰기</a>
                        </Link>
                    </li>
                    {
                        IsLogin === false ?
                            <>
                                <li>
                                    <Link href="/login">
                                        <a>login</a>
                                    </Link>
                                </li>
                                <li>
                                    <Link href="/join">
                                        <a>join</a>
                                    </Link>
                                </li>
                            </>
                            :
                            <>
                                <li>
                                    <Link href="/login">
                                        <a>logout</a>
                                    </Link>
                                </li>
                                <li>
                                    <Link href="/join">
                                        <a>info</a>
                                    </Link>
                                </li>
                            </>
                    }

                </Gnb>
                <NavToggle />
            </HeaderContainer>
        </>
    )
}

export default Header

로긴 여부에 따라 false, true 만들기 

리액트는 상태 (state)가 바뀌어야 rerender를 함 

useReducer와 useContext와 같이 쓰는 이유 

Context로 변수 값이 바뀌어도 rerender가 이루어지지않음 

 

 

 

 

9. useReducer

_app.jsx

import '../index.css'
import Head from 'next/head'
import Store,{ initialState } from './store/context'
import {useReducer, useContext} from 'react'


const App = ({ Component }) => {

    const globalContext = useContext(Store) // context에 있는 값을 그대로 빼와서 담음 

    const [state,dispatch] = useReducer(reducer,globalContext)
    
    return (
        <>
            <Head>
                <link rel="preconnect" href="https://fonts.googleapis.com" />
                <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
                <link href="https://fonts.googleapis.com/css2?family=Black+Han+Sans&display=swap" rel="stylesheet" />
            </Head>
            <Store.Provider value={{state,dispatch}}>
                <Component /> {/* 우리가 만든 파일(index,login..)들이 여기에 위치 */}
            </Store.Provider>
        </>
    )
}

export default App

*** value={{state,dispatch}} 넘겨주는거 첫 번째 {} 는 JS / 두 번째 {} 는 객체  벌써 요 부분 두 번 틀ㄹ렷다

reducer 함수 따로 빼서 만들기

Store> reducer.jsx

export const reducer =(state,action)=>{
    switch(action.type){
        case 'login':
            return{
                ...state,
            }
        case 'logout':
            return{
                ...state,
            }
    }
}

_app.jsx에서 reducer 가져오기 

 

 

useContext 로 받는 값이 다름 

			요게 바뀌어서 
            <Store.Provider value={{state,dispatch}}>

-> Header.jsx 수정

const Header = () => {

    const globalStore = useContext(Store)
    const { IsLogin } = globalStore.state

    return (
        <>
            <HeaderContainer>
                {/* 로고 & 메뉴  */}
                <h1>로고</h1>

 

context -> IsLogin true로 만들기 (로그아웃 쳌 하기 위해) 

 

reducer.jsx

export const reducer =(state,action)=>{
    switch(action.type){
        case 'login':
            return{
                ...state,
            }
        case 'logout':
            return{
                ...state,
                IsLogin:false,
            }
    }
}

 

로그아웃 만들기 

pages> logout.jsx

import Store from './store/context'
import { useContext, useEffect } from 'react'
import Router from 'next/router'

const logout=()=>{

    const {dispatch} = useContext(Store)
    useEffect(()=>{
        dispatch({type:'logout'})

        setTimeout(()=>{
            Router.back()
        },1000)
    },[]) // 빈배열일 경우render 완료 되었을 때 딱 한번 실행되는 코드가 됨 

    return(
        <>
            logout 
        </> 
    )
}
export default logout

 

 

 

반응형