코드

import './App.css';
import {useCallback, useState} from "react";
import Child from "./Child";

function App() {
  const map = [
      'https://www.daum.net/',
      'https://www.naver.com/',
      'https://www.nate.com/',
  ]
  const [urls, setUrls] = useState([...map]);
  const [count, setCount] = useState(0);
  const [_, forceRender] = useState(0);

  const longTimeWork = async () => {
    console.log('[call longTimeWork]');
    for (let j = 0; j < urls.length; j++) {
        console.log(urls);
        await fetch(urls[j], {mode: 'no-cors'});
    }
    setCount(prev => prev + 1);
  }

  const useLongTimeWork = useCallback(longTimeWork, [urls]);

  return (
    <div className="App">
      <button onClick={() => forceRender(prev => prev + 1)}>부모 그리기</button>
      <button onClick={() => setUrls([...urls, map[count % 3]])}>URL 추가</button>
      <Child count={count} longTimeWork={longTimeWork} />
    </div>
  );
}

export default App;
import {useEffect} from "react";

function Child({count, longTimeWork}) {
  useEffect(() => {
      longTimeWork();
  }, [longTimeWork])

  return (
    <div>
      <button>
          {'child'}
          {' '}
          {count}
      </button>
    </div>
  );
}

export default Child;

 

React에서 보통 상위 컴포넌트가 하위 컴포넌트에게 함수를 전해줄때가 많다.
위의 예에서는 longTimeWork가 이에 해당 한다. longTimeWork함수는 네이버, 다음, 네이트 사이트를 차례대로 하나씩 방문하는 네트워크 레이턴시가 아주 많은(시간이 아주 많이 걸리는) 함수다.
하위 컴포넌트는 useEffect를 사용해서 함수를 받자마자 실행 시킨다고 가정하자. 하위 컴포넌트가 받는 함수가 빠르고, 가벼운 함수라면 부모가 render할 때마다 호출해도 크게 상관없다.
하지만 함수 이름에서 보다 싶이 longTimeWork!
이때는 부모가 render할 때마다 함수를 실행시키기 부담스럽다. 어떻게 해야할까? 이때 바로 useCallback을 쓰면된다.

 

문제 이해하기

문제점을 파악해보자.
상위 컴포넌트에서 <Child longTimeWork={longTimeWork} />를 선언했다.
이때 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. 그러면 어때 longTimeWork 함수도 다시 만들어진다. 이말은 즉 longTimeWork 함수는 새로운 함수가 된다는 말이다.
하위 컴포넌트(Child) 입장에서는 longTimeWork 함수가 새로 만들어졌으니 useEffect에서 다시 호출한다.
longTimeWork는 내부적으로 setCount(prev + 1)을 실행하니 무한대로 longTimeWork 함수를 호출한다.
나는 그냥 하위 컴포넌트에서 longTimeWork가 새로 생길 때만 호출하고 싶었는데......

 

문제 해결

const useLongTimeWork = useCallback(longTimeWork, [urls]);

<Child count={count} longTimeWork={useLongTimeWork} />

함수를 선언하고, Child에 전달하자.


useCallback 두번째 파라미터는 의존성 배열이다. 그러니깐 urls가 변경 될때만 해당 함수를 다시 만들겠다는 말이다.
다시 한번 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. urls가 변경되지 않았기 때문에 Child에서 함수를 호출하지 않는다.
그리고 URL 추가 버튼을 누르면 urls가 변경 되기 때문에 longTimeWork가 재 정의되고 Child 컴포넌트에서 해당 함수를 실행하게 된다.
내가 원하는 결론이다. 아주 뿌듯하다.

 

결론

useCallback 함수는 의존성 배열에 정의된 변수가 변경 될때만 함수를 다시 만든다. 그러므로 성능 향상에 도움이 된다.
하지만 일단 useCallback없이 코딩하고 느려지면 그때 생각하자.

'React' 카테고리의 다른 글

React(SPA) Auth 로그인 흐름  (0) 2020.01.09
React Redux 기본  (0) 2019.12.19
React Form input  (0) 2019.12.16

전통적인 웹사이트(멀티 페이지) 로그인 방법은 Session을 이용하는 것이다.

로그인을 하면 서버와 브라우저 간에 세션이 생성되고 그 세션을 기반으로 Request가 안전 한지 판단한다.

 

React- The Complete Guide

React와 같은 SPA(싱글페이지)의 로그인 방법은 Session을 가지지 않는다. 

그대신 Token을 이용하는데 Restful 백엔드 서버에서 생성된 토큰을 로그인하면 받게 되고 이 토큰을 localStorage에 저장한다.

잘 저장해 뒀다가 서버로 데이터 요청 즉 Reuqest를 할 때 아까 발급 받은 Token을 같이 보낸다.

'React' 카테고리의 다른 글

React useCallback 이해하기  (0) 2022.01.13
React Redux 기본  (0) 2019.12.19
React Form input  (0) 2019.12.16

 

React - The Complete Guide (incl Hooks, React Router, Redux)

간략 절차

1. Store에서 모든 State를 관리한다.

2. 그런데 Store의 State를 변경하려면 Reducer라는 놈을 통해서 와라

3. Reducer라는 놈을 통해서 오기전에 Action이라는 놈을 통해서 와라

 

사전지식

 

1. Reducer is a pure function which receives the action and the old state as input and spit out an updated state

 - Reducer는 그냥 js Function이다. 이 Function은 action과 old State를 파라미터로 받고 자기가 Type에 따라서 어떤 로직을 수행하고 state를 update한 뒤 return 해준다. 그런데 이 로직은 반드시 동기(Synchronous)여야 한다.

 

2. Action은 아무것도 하지 않는다, 그냥 Reducer로 가기 위한 메신저다.

 

말로 풀어서

React Component에서 Action으로 Dispatch(보내다) 한다. 그럼 Action에서 "아! 이 Component에서 보낸 Type은 xxx이구나!", "action(정확히는 action Type)과 old State를 파라미터로 가지는 Reducer야 여기 action과 old state를 줄테니 new State를 반환해다오" 

 

그러면 Reducer가 update를 하고 "야 방금 나한테 요청한 Component 누구냐?? update 했으니깐 받아가라~ triger"

 

'React' 카테고리의 다른 글

React useCallback 이해하기  (0) 2022.01.13
React(SPA) Auth 로그인 흐름  (0) 2020.01.09
React Form input  (0) 2019.12.16
Container State
    state = {
        orderForm: {
            name: {
              elementType: 'input',
              elementConfig: {
                  type: 'text',
                  placeholder: 'Your name',
              },
              value: ''
            },
            street: {
                elementType: 'input',
                elementConfig: {
                    type: 'text',
                    placeholder: 'Your street',
                },
                value: ''
            },
            zipCode: {
                elementType: 'input',
                elementConfig: {
                    type: 'text',
                    placeholder: 'Your zipCode',
                },
                value: ''
            },
            country: {
                elementType: 'input',
                elementConfig: {
                    type: 'text',
                    placeholder: 'Your country',
                },
                value: ''
            },
            email: {
                elementType: 'input',
                elementConfig: {
                    type: 'email',
                    placeholder: 'Your email',
                },
                value: ''
            },
            deliveryMethod: {
                elementType: 'select',
                elementConfig: {
                    option: [
                        {value: 'Fastest', displayValue: 'Fastest'},
                        {value: 'cheapest', displayValue: 'Cheapest'},
                    ]
                },
                value: ''
            },
        }
    }

위와 같은 state가 있다.  orderForm안에 들어갈 input 들은 name, street, zipCode 등이 있다.

 

Input Component
import React from 'react'
import classes from './Input.css'

const input = (props) => {
    let inputElement = null;

    switch (props.elementType) {
        case ('input'):
            inputElement=<input
                className={classes.InputElement}
                {...props.elementConfig}
                onChange={props.changed}
                value={props.value} />
            break;
        case ('textarea'):
            inputElement=<textarea
                className={classes.InputElement}
                {...props.elementConfig}
                onChange={props.changed}
                value={props.value} />
            break;
        case ('select'):
            inputElement=(
                <select
                    className={classes.InputElement}
                    onChange={props.changed}
                    value={props.value}>
                    {props.elementConfig.option.map(option => (
                        <option key={option.value} value={option.value}>
                            {option.displayValue}
                        </option>
                    ))}
                </select>
            )
            break;
        default:
            inputElement=<input
                className={classes.InputElement}
                {...props.elementConfig}
                value={props.value} />
    }

    return (
        <div className={classes.Input}>
            <label className={classes.Label}>{props.label}</label>
            {inputElement}
        </div>
    )
}
export default input

props의 elementType에 따라 그냥 input이되기도, textarea, select가 되기도 한다.

inputElement 변수에 input값을 담는다.

 

Container render
    render() {
        const formElementsArray = []
        for (let key in this.state.orderForm) {
            formElementsArray.push({
                id: key,
                config: this.state.orderForm[key]
            })
        }

        let form = (
            <form onSubmit={this.orderHandler}>
                {formElementsArray.map(formElement => {
                    return (<Input
                        key={formElement.id}
                        elementType={formElement.config.elementType}
                        elementConfig={formElement.config.elementConfig}
                        value={formElement.config.value}
                        changed={(event) =>this.inputChangedHandler(event, formElement.id)}
                    />)
                })}
                <Button btnType="Success" clicked={this.orderHandler}>Order</Button>
            </form>
        )
        if (this.state.loading) {
            form = <Spinner />
        }
        return (
            <div className={classes.ContactData}>
                <h4>Enter your Contact Data</h4>
                {form}
            </div>

        );
    }

1. formElementsArray 변수에 {id, config}의 형태로 push

2. map으로 for문을 돌면서 Input 값을 생성(return) 해준다.

2-1. changed 매개변수로 사용자의 키보드 입력값을 받을 함수를 넘겨준다.

 

inputChangedHandler 함수
    inputChangedHandler = (event, inputIdentifier) => {
        const updatedOrderForm = {
            ...this.state.orderForm
        }
        const updatedFormElement = {
            ...updatedOrderForm[inputIdentifier]
        }
        updatedFormElement.value = event.target.value
        updatedOrderForm[inputIdentifier] = updatedFormElement
        this.setState({orderForm: updatedOrderForm})
    }

Input Component의 onChange event가 발생하면 매개변수로 이벤트 발생 input값과 그 input 값을 찾기 위한 inputIdentifier을 받는다.

 

immutable하게 state를 업데이트 해준다.

 

Form Submit
    orderHandler = (event) => {
        event.preventDefault()
        this.setState({loading: true})
        const formData = {}
        for (let formElementIdentifier in this.state.orderForm) {
            formData[formElementIdentifier] = this.state.orderForm[formElementIdentifier].value
        }
        const order = {
            ingredients: this.props.ingredients,
            price: this.props.price,
            orderData: formData
        }
        axios.post('/orders.json', order)
            .then(res=> {
                this.setState({loading: false})
                this.props.history.push('/')
            })
            .catch(error => {
                this.setState({loading: false})
            })
    }

1. render 함수의 form Tag에서 onSubmit으로 orderHandler 함수 정의

2. state의 orderForm을 immutable하게 formData 변수로 생성

3. axios post Call 실행

'React' 카테고리의 다른 글

React useCallback 이해하기  (0) 2022.01.13
React(SPA) Auth 로그인 흐름  (0) 2020.01.09
React Redux 기본  (0) 2019.12.19

+ Recent posts