본문 바로가기

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

[86일차]20210713 리액트 useReducer Context styled-component Ref Css TicTacToe 만들기

반응형

useReducer

useState와 비슷한 상태관리 담당,   useReducer의 장점은 상태 변경하는 로직을 다른 곳에서 처리할 수 있음. 즉, 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다. 

 

 

 

 

먼저 카운터 만들고 useReduce 를 사용해보고 비교해보기 ! 

 

1. 어제 만든 webpack 폴더에 - components 폴더 - counter 폴더 - Counter.jsx 파일 생성 및 아래 코드 작성

Counter.jsx

import React from 'react';

const Counter = () =>{
    return(
        <>
            hi
        </>
    )
}

export default Counter

 

App.jsx

import React from 'react';
import Memo from './memo/memo';
import Counter from './components/counter/Counter'

const App = () => {
    return(
        <>
            {/* <Memo /> */}
            <Counter/>
        </>
    )
}

export default App

 

2. +1 -1 버튼 카운팅 만들기 

import React from 'react';

const Counter = () =>{
    
    const [number, setNumber] = React.useState(0)

    const onUp = ()=>{
        setNumber((prevNumber)=>prevNumber+1)   // prevNumber 라는 변수명 아무거나 가능 ! 
    }

    const onDown = () =>{
        setNumber((prevNumber)=>prevNumber+1)
    }

    return(
        <>
            <div>
                <h2>{number}</h2>
                <button onClick={onUp}> +1 </button>
                <button onClick={onDown}> -1 </button>
            </div>
        </>
    )
}

export default Counter

위의 코드는 비교용으로 남겨두기 ! -> 주석처리 후 아래에 똑같이 하나 만들기 

 

 

 

 

 

3. 위의 코드를 상태값을 다르게 useReducer 사용해서 작성해보기 

import React from 'react';

// const Counter = () =>{
    
//     const [number, setNumber] = React.useState(0)

//     const onUp = ()=>{
//         setNumber((prevNumber)=>prevNumber+1)   // prevNumber 라는 변수명 아무거나 가능 ! 
//     }

//     const onDown = () =>{
//         setNumber((prevNumber)=>prevNumber+1)
//     }

//     return(
        // <>
        //     <div>
        //         <h2>{number}</h2>
        //         <button onClick={onUp}> +1 </button>
        //         <button onClick={onDown}> -1 </button>
        //     </div>
        // </>
//     )
// }

const reducer = (number, action) =>{
    // reducer는 총 두 가지 값을 받을 수 있음 
    // 첫 번째 number (상태값), 두 번째 type
    // type에 맞게끔 상태값을 정하는 것 
    switch(action.type){
        case 'UP':
            return number +1;
        case 'DOWN':
            return number -1;
        default:
            return number;
    }
}


const Counter =()=>{

    // useReducer 기본 구문 ↓↓↓ useReducer 도 React 안에 있는 객체 
    // useRecucer의 인자값은 두가지. 
    // 첫번째 인자값 : 함수 (함수명 내가 임의로 정의할 수 있음 ex. reducer ~ ) 
    // 두번째 인자값 : 기본 초기값 (useState랑 똑같) = > number 변수에 0 을 담음 
    // dispatch 라는 함수를 사용해서 내용을 바꾸게 되어있음
    // useState 똑같아보이는데 장점은 ? -> 로직 분리 
    // useReduce 의 함수는 Component 밖에 있어도 상관없다. 
    const [number, dispatch] = React.useReducer(reducer,0)

    const onUp = () =>{
        //setState({}) 와 비슷하게 바꿀 내용 객체값으로 넣기 
        dispatch({ type: 'UP' })  // -> 요건 reducer 함수의 두 번째 인자값 
        // dispatch를 호출하면 reducer 함수를 호출하게 됨 
    }

    const onDown =() =>{
        // type 이 DOWN 이라는 걸로 할 꺼야 ( ? 이게 무슨 ?  )
        dispatch({ type: 'DOWN'})
    }
    
    return(
        <>
            <div>
                <h2>{number}</h2>
                <button onClick={onUp}> +1 </button>
                <button onClick={onDown}> -1 </button>
            </div>
        </>
    )
}




export default Counter

 음..... 저 안에 교수님의 설명을 다 적었는데 이해가 어렵,,,,,,,,,,,,,,,

복습하면서 뜯어봐야징 

 

 

 

 

4. Component 밖에 두었던 reducer 함수를 아예 다른 파일로 빼기 

counter 폴더 안 reducer.js 파일 생성  - reducer 함수 복붙 + 'export' 코드 추가 

reducer.js  (교수님은 jsx 로 해야 오류가 안났다 -> 왜징 ???  -> webpack 환경설정 차이  !) 

export const reducer = (number, action) =>{
    // reducer는 총 두 가지 값을 받을 수 있음 
    // 첫 번째 number (상태값), 두 번째 type
    // type에 맞게끔 상태값을 정하는 것 
    switch(action.type){
        case 'UP':
            return number +1;
        case 'DOWN':
            return number -1;
        default:
            return number;
    }
}

Counter.jsx 에 reducer.js 불러오기 

import React from 'react';
import {reducer} from './reducer'

 

 

reducer 장점 : 상태를 관리하는 함수를 따로 파일로 빼서 관리할 수 있는 장점 ! 특히 Context와 함께 같이 쓴다. 

 

 

 

5. string 도 변수로 빼기

Counter.jsx

import React from 'react';
import {reducer, UP, DOWN} from './reducer'

const Counter =()=>{

    const [number, dispatch] = React.useReducer(reducer,0)

    const onUp = () =>{
        //setState({}) 와 비슷하게 바꿀 내용 객체값으로 넣기 
        dispatch({ type:UP })  // -> 요건 reducer 함수의 두 번째 인자값 
        // dispatch를 호출하면 reducer 함수를 호출하게 됨 
    }

    const onDown =() =>{
        // type 이 DOWN 이라는 걸로 할 꺼야 ( ? 이게 무슨 ?  )
        dispatch({ type: DOWN})
    }
    
    return(
        <>
            <div>
                <h2>{number}</h2>
                <button onClick={onUp}> +1 </button>
                <button onClick={onDown}> -1 </button>
            </div>
        </>
    )
}




export default Counter

reducer.js

export const UP = 'UP'
export const DOWN = 'DOWN'
export const reducer = (number, action) =>{
    // reducer는 총 두 가지 값을 받을 수 있음 
    // 첫 번째 number (상태값), 두 번째 type
    // type에 맞게끔 상태값을 정하는 것 
    switch(action.type){
        case 'UP':
            return number +1;
        case 'DOWN':
            return number -1;
        default:
            return number;
    }
}

 

 

export default = 1개만 보내기 

or 아래처럼 여러개 보내기 

 

 

 

 


 

 

 

 

Context

Props 하위 컴포넌트에 보내는 것을 편리하게 만들어주는 Context ! 

 

<- App에서의 내용을 가장 아래 컴포넌트에서 바로 사용 가능하도록 ! 

 

 

 

 

 

 

 

 

 

 

 

함수형 Context (클래스 Context는 7월 9일 수업에 함 !) 

 

1. 위의 그림대로 Components 만들기

Component 파일에 - context 폴더 - Layout.jsx 생성  / 코드 입력 

import React from 'react';


const Layout = () =>{
    return(
        <>
            <LoginBox/>
        </>
    )
}

const LoginBox =()=>{
    return(
        <>
            <Login/>
        </>
    )
}

const Login = () =>{
    return(
        <>
            <Button/>
        </>
    )
}

const Button = () =>{
    return(
        <>
            <buuton>HI CONTEXT</buuton>
        </>
    )
}

export default Layout

 

 

 

 

 

2. context 파일 안에 LayoutContext.js 파일 생성 / 아래 코드 작성

//4개의 컴포를 담을 box를 만들기 위해 요 파일을 만듬.
// 1.createContext() 현실적으로 4개 컴포를 감싸는 주체가 되는 컴포넌트
// 가장 최상위 컴포넌트 - LayoutContext가 되어야함.  <- 얘한테 직접적으로 요청함 by 하위 Components
// LayoutStore는 껍데기일 뿐 ! 
// props.children 써서 사용하고 싶어서 ! 
// LayoutContext.Provide value 에 실질적으로 데이터를 넣는 공간. 


import React from 'react';

export const LayoutContext  =  React.createContext() // context 생성하기=하나의 컴포넌트 생성했따는 것 

const LayoutStore =(props)=>{

    const user={
        userid:'asdf',
        username:'addd',
        job:'dddd'
    }

    return(// //LayoutContext도 컴포넌트라 jsx 형태로 이렇게 쓰기 가능 ! 
        // 기본값으로 무엇을 보낼지 value 
        
        <LayoutContext.Provider value={user}>  
            {props.children}
        </LayoutContext.Provider>
    )
}

export default LayoutStore

context로 감싸라 ~ 라는 느낌 

 

 

 

Layout.jsx

import React from 'react';
import LayoutStore, {LayoutContext} from './LayoutContext'; //추가 

const Layout = () => {
    return (
        <>
            <LayoutStore>
                <LoginBox />
            </LayoutStore>
        </>
    )
}

const LoginBox = () => {
    return (
        <>
            <Login />
        </>
    )
}

const Login = () => {
    return (
        <>
            <Button />
        </>
    )
}

const Button = () => {
    // 인자값 : LayoutContext를 넣어주기  -> 
    const context = React.useContext(LayoutContext)
    return (
        <>
            <button>HI CONTEXT</button>
            <ul>
                <li>{context.userid}</li>
                <li>{context.username}</li>
                <li>{context.job}</li>
            </ul>
        </>
    )
}

export default Layout

App.jsx

import React from 'react';
import Memo from './memo/memo';
import Counter from './components/counter/Counter'
import Layout from './components/context/Layout'

const App = () => {
    return(
        <>
            {/* <Memo /> */}
            {/* <Counter/> */}
            <Layout />
        </>
    )
}

export default App

 

 

context 만으로 가능 

 

context에게 요청을 바로 해서 context 의 값을 가져오는 방식 

 

 

 

 

 

 

 

 

Styled Component

 

CSS 에 처리에 대한 내용

 

 

* 일반 CSS 넣기 

 

 

1. npm 설치 

$ npm i styled-components

2. components 폴더 안 styleComponent 파일 > index.jsx 생성 / 코드 작성

import React from 'react'

const Index = () =>{
    return(
        <>
            hi sytled
        </>
    )
}


export default Index

 

3. App Component에 연결 

import React from 'react';
import Memo from './memo/memo';
import Counter from './components/counter/Counter'
import Layout from './components/context/Layout'
import Index from './components/styledComponent/index'

 

 

4. index.jsx 

button에 직접 style 넣기 (styled-component 사용 X) 

import React from 'react'

const Buttonstyle = {
    "background":"black",
    "border":"none",
    "color":"white",
    "padding":"7px 14px",
}

const Index = () =>{
    return(
        <>
            hi sytled
            <div >
                <input type="text"/>
                <button style={Buttonstyle}>
                    hi
                </button>
            </div>
        </>
    )
}

export default Index

 

 

 

styled-components 사용하기

 

1. styled-components 가져오기 

import React from 'react'
import styled from 'styled-components'

 

index.jsx

import React from 'react'
import styled from 'styled-components'

const Buttonstyle = {
    "background":"black",
    "border":"none",
    "color":"white",
    "padding":"7px 14px",
}

const Button = styled.button`  
    background:#000;
    border:none;
    color:#fff;
    padding:7px 14px;
`

const Index = () =>{
    return(
        <>
            hi sytled
            <div >
                <input type="text"/>
                <button style={Buttonstyle}>
                    hi
                </button>
                <Button>
                    hihi
                </Button>
            </div>
        </>
    )
}

export default Index

 

 

// < = 야는 컴포넌트를 뜻함. styled 안에 있는 button 버튼에 아래와 같은 스타일을 적용한 Button이라는 것을 만듬 

 

application check -> component 안에서만 유효한 css

css 파일과 겹치지 않아서 협업할 때 좋음 

 

 

 

2. hover 해보기 

import React from 'react'
import styled from 'styled-components'

const Buttonstyle = {
    "background": "black",
    "border": "none",
    "color": "white",
    "padding": "7px 14px",
}

const Button = styled.button`  
    background:#000;
    border:none;
    color:#fff;
    padding:7px 14px;
`
const ButtonHover = styled(Button)`
    background:#007bff;
    :hover{
        background:#0069d9;
    }
`

const Index = () => {
    return (
        <>
            hi sytled
            <div >
                <input type="text" />
                <button style={Buttonstyle}>
                    hi
                </button>
                <Button>
                    hihi
                </Button>
                <ButtonHover>
                    hoho
                </ButtonHover>

            </div>
        </>
    )
}

export default Index

 

 

 

 

3. 이제 DOM 조작을 해보기 -> 위의 첫 번째 버튼 클릭하면 input box cursor가 가도록 만들기 

index.jsx

const Index = () => {
    const inputRef = React.useRef()  // element 넣을 준비 되어 있어 ! 

    const handleClick=()=>{
        console.log(inputRef.current)
    }

    return (
        <>
            hi sytled
            <div >
                <input type="text" ref={inputRef}/>
                <button 
                    style={Buttonstyle}
                    onClick={handleClick}    
                >
                    hi
                </button>
                <Button>
                    hihi
                </Button>

-

const Index = () => {
    const inputRef = React.useRef()  // element 넣을 준비 되어 있어 ! 

    const handleClick=()=>{
        //inputRef.current = 해당 element 
        inputRef.current.focus()
        inputRef.current.style.height = '30px'
        // inputRef.current.style.display='none'
        
    }

    return (
        <>
            hi sytled
            <div >
                <input type="text" ref={inputRef}/>
                <button 
                    style={Buttonstyle}
                    onClick={handleClick}

-

const Index = () => {
    const inputRef = React.useRef()  // element 넣을 준비 되어 있어 ! 

    const handleClick=()=>{
        //inputRef.current = 해당 element 
        inputRef.current.focus()
        inputRef.current.style.height = '30px'
        if(inputRef.current.style.display=='block'){
            inputRef.current.style.display = 'none'
        }else{
            inputRef.current.style.display ='block'
        }
        
    }

 

 

두 번째 버튼은 익명함수로 주기

            <div >
                <input type="text" ref={inputRef}/>
                <button 
                    style={Buttonstyle}
                    onClick={handleClick}    
                >
                    hi
                </button>
                <Button onClick={()=>{handleClick()}}>  
                    hihi
                </Button>

 

 

전체 코드 

import React from 'react'
import styled from 'styled-components'

const Buttonstyle = {
    "background": "black",
    "border": "none",
    "color": "white",
    "padding": "7px 14px",
}

const Button = styled.button`  
    background:#000;
    border:none;
    color:#fff;
    padding:7px 14px;
`
const ButtonHover = styled(Button)`
    background:#007bff;
    :hover{
        background:#0069d9;
    }
`

const Index = () => {
    const inputRef = React.useRef()  // element 넣을 준비 되어 있어 ! 

    const handleClick=()=>{
        //inputRef.current = 해당 element 
        inputRef.current.focus()
        inputRef.current.style.height = '30px'
        if(inputRef.current.style.display=='block'){
            inputRef.current.style.display = 'none'
        }else{
            inputRef.current.style.display ='block'
        }
        
    }

    return (
        <>
            hi sytled
            <div >
                <input type="text" ref={inputRef}/>
                <button 
                    style={Buttonstyle}
                    onClick={handleClick}    
                >
                    hi
                </button>
                <Button onClick={()=>{handleClick()}}>  
                    hihi
                </Button>
                <ButtonHover>
                    hoho
                </ButtonHover>

            </div>
        </>
    )
}

export default Index

 

 

 


 

 

 

Tic Tac Toe 게임을 위에서 배운 내용들로 만들어 보기 

 

 

 

 

component >game > 3개 comps 만들기 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. 위의 파일트리 만들기 / 각 내용 만들기 / 코드 작성 및 연결

App > Game > Board > Square 

 

 

2. 화면 부터 만들기 -> React할 때 기본 ! 

Game.jsx

import React from 'react'
import Board from './Board'
import Styled from 'styled-components';


// wrap : 영역에서 넘어가면 떨군다. 
const GameDiv = Styled.div`
    display:flex;
    flex-wrap:wrap; 
    align-item:center;
    justify-content:center;
    width:300px;    
`

const Game = () => {
    return (
        <GameDiv>
            <Board />
        </GameDiv>
    )
}

export default Game

 

 

Board

import React from 'react'
import Square from './Square'

const Board = () =>{
    return(
        <>
            <Square />
            <Square />
            <Square />
            <Square />
            <Square />
            <Square />
            <Square />
            <Square />
            <Square />
        </>
    )
}

export default Board

 

Square

import React from 'react'
import Styled from 'styled-components'

const Button = Styled.button`
    width:33.3333%;
    height:70px;
    border:1px solid #000;
    background:#fff;
    cursor:pointer;
    font-size:30px;
`

const Square = () => {
    return (
        <>
            <Button>
                0
            </Button>

        </>
    )
}

export default Square

 

화면 그리기 끝 

 

 

3. 로직 처리하기 

Game Component에 reducer 사용해보기 

Game.jsx

import React from 'react'
import Board from './Board'
import Styled from 'styled-components';


// wrap : 영역에서 넘어가면 떨군다. 
const GameDiv = Styled.div`
    display:flex;
    flex-wrap:wrap; 
    align-item:center;
    jstify-content:center;
    width:300px;    
`

const reducer = (state, action) => {

}

const defaultState = {
    squares: Array(9).fill(null),
    xIsNext: true,
    winner: null,
}

const Game = () => {
    const [state, dispatch] = React.useReducer(reducer, defaultState)

    return (
        <>
            <GameDiv>
                <Board />
            </GameDiv>
            {state.winner ? `${state.winner}님 승리`:`Next Player is ${state.xIsNext ? 'X' : 'O'}`}
        </>
    )
}

export default Game

이제 winner 값만 채워주면 ~ 가 승리! 를 알게됨 

 

 

div 클릭하면 'O승리' 나오게 만들기 onClick 함수 사용 

const reducer = (state, action) => {
    
    switch(action.type){
        case 'winner':
            return {
                ...state,
                winner:action.ox
            }
    }
}

const defaultState = {
    squares: Array(9).fill(null),
    xIsNext: true,
    winner: null,
}

const Game = () => {
    const [state, dispatch] = React.useReducer(reducer, defaultState)

    const handleClick =() =>{
        dispatch({type:'winner', ox:'O'})
    }

    return (
        <>  
            <GameDiv onClick={()=>handleClick()}>

 

game.jsx 

    return (
        <>  
            <GameDiv > 
                <Board 
                    squares={state.squares}
                />

다시 onClick 이벤트 삭제하고 squares 보내기 

 

board.jsx

const Board = (props) =>{
    return(
        <>

그런데 객체로 그대로 받기

const Board = ({squares}) =>{
    return(

배열 뿌리기

import React from 'react'
import Square from './Square'

const Board = ({ squares }) => {
    const square = squares.map((v, k) => {
        return (
            <Squares
                key={k}
                value={v}
            />
        )
    })
    return (
        <>
            {square}
        </>
    )
}

export default Board

Square.jsx 

import React from 'react'
import Styled from 'styled-components'

const Button = Styled.button`
    width:33.3333%;
    height:70px;
    border:1px solid #000;
    background:#fff;
    cursor:pointer;
    font-size:30px;
`

const Square = ({value}) => {
    return (
        <>
            <Button>
                {value} 
            </Button>
        </>
    )
}

export default Square

 

 

handleClick 내용 완성시키고 Square 쪽까지 전달 시키기 

Game

    const handleClick =(n) =>{ // n = > key값 1~ 9 -> Board 콤프에 있음 
        dispatch({type:'NEXT', index:n})
    }

    return (
        <>  
            <GameDiv > 
                <Board 
                    squares={state.squares}
                    onClick ={handleClick}

 

Baord.jsx

import React from 'react'
import Square from './Square'

const Board = ({ squares, onClick }) => {
    const square = squares.map((v, k) => {
        return (
            <Square
                key={k}
                value={v}
                onClick={()=>onClick(k)}
            />
        )
    })
    return (
        <>
            {square}
        </>
    )
}

export default Board

 

 

Sqaure.jsx

import React from 'react'
import Styled from 'styled-components'

const Button = Styled.button`
    width:33.3333%;
    height:70px;
    border:1px solid #000;
    background:#fff;
    cursor:pointer;
    font-size:30px;
`

const Square = ({value, onClick}) => {
    return (
        <>
            <Button onClick={()=>onClick()}>
                {value} 
            </Button>
        </>
    )
}

export default Square

 

 

클릭하면 잘 찍히는지 확인

const Game = () => {
    const [state, dispatch] = React.useReducer(reducer, defaultState)

    const handleClick =(n) =>{ // n = > key값 1~ 9 -> Board 콤프에 있음 
        // dispatch({type:'NEXT', index:n})
        console.log(n)
    }

 

 

 

이제 O, X 찍히도록 만들기 

 

Game 

const reducer = (state, action) => {
    
    switch(action.type){
        case 'NEXT':
            const {squares} = {...state}
            squares[action.index] = state.xIsNext ? 'X' : 'O'
            return {
                ...state,
                xIsNext:!state.xIsNext,
                squares,
            }
    }
}

 

내가 클릭한 곳이 또 클릭한 경우 || 승자가 결정된 경우, 함수 실행안되도록 만들기

const Game = () => {
    const [state, dispatch] = React.useReducer(reducer, defaultState)

    const handleClick =(n) =>{ // n = > key값 1~ 9 -> Board 콤프에 있음 
        if(state.squares[n]) return;
        if(state.winner !== null) return;
        dispatch({type:'NEXT', index:n})
        console.log(n)
    }

 

 

 

 

승자 결정하기

위너 정하는 Winner함수 

import React from 'react'
import Board from './Board'
import Styled from 'styled-components';


// wrap : 영역에서 넘어가면 떨군다. 
const GameDiv = Styled.div`
    display:flex;
    flex-wrap:wrap; 
    align-item:center;
    jstify-content:center;
    width:300px;    
`

const reducer = (state, action) => {
    
    switch(action.type){
        case 'NEXT':
            const {squares} = {...state}
            squares[action.index] = state.xIsNext ? 'X' : 'O'
            return {
                ...state,
                xIsNext:!state.xIsNext,
                squares,
                
            }
        case 'win':
            return{
                ...state,
                winner:action.winner
            }
    }
}

const defaultState = {
    squares: Array(9).fill(null),
    xIsNext: true,
    winner: null,
}

const Winner =(squares)=>{
    let lines = [
        [0,1,2],
        [3,4,5],
        [6,7,8],
        [0,3,6],
        [1,4,7],
        [2,5,8],
        [0,4,8],
        [2,4,6]
    ]

    for(let i=0; i<lines.length; i++){
        let [a,b,c] = lines[i]
        //squares[a] -> 해당 값이 있는지 확인하는 요소 
        if(squares[a] && squares[a]==squares[b] && squares[a]==squares[c]){
            return squares[a]
        }
    }
    return null
}

const Game = () => {
    const [state, dispatch] = React.useReducer(reducer, defaultState)

    const handleClick =(n) =>{ // n = > key값 1~ 9 -> Board 콤프에 있음 
        if(state.squares[n]) return;
        if(state.winner !== null) return;
        dispatch({type:'NEXT', index:n})
        console.log(n)
    }
    //첫 번째 인자값 : 실행할 함수 두 번째 인자값 : 기준이될 함수 - 바뀌면 실행함
    React.useEffect(()=>{
        console.log('we')
        const win = Winner(state.squares)
        if(!win){
            dispatch({type:'win', winner:win})
        }
    },[state.xIsNext])

    return (
        <>  
            <GameDiv > 
                <Board 
                    squares={state.squares}
                    onClick ={handleClick}
                />
            </GameDiv>
            {state.winner ? `${state.winner}님 승리`:`Next Player is ${state.xIsNext ? 'X' : 'O'}`}
        </>
    )
}

export default Game

 

 

반응형