728x90

⚛️ 기본 환경: IDE: VS code, Language: React

 

 

useEffect()

: 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook

 

useRef()

: React 컴포넌트에서 변수를 저장하고 관리하는 데 사용하는 도구

: 컴포넌트의 렌더링과 관련된 값의 변경에 영향을 받지 않음

 

 

useEffect() 내부에서 useState를 활용한 setState를 할 경우,

1. mount될 때 1번

2. state변경될 때 1번

총 2번(mount 1번 + 렌더링 1번)의 useEffect가 호출됨

 

이 때, Ref를 선언하고 isEffectedRef.current를 활용하여 변수의 현재 값을 가져와 false일 때만 useEffect가 실행되도록 제어할 수 있음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const isEffectedRef = useRef(false);
 
useEffect(() => {
    const effedtedFunc = () => {
    try {
        setData(data);
      } catch (error) {
        console.error(error);
      }
    };
 
    if (!isEffectedRef.current) {
      effedtedFunc();
      isEffectedRef.current = true;
      // setData의 영향을 받지 않아 1번만 effedtedFunc가 실행될 수 있도록 함
      // 이후에는 데이터가 변경되었으므로 더이상 setData에 따른 rendering이 일어나지 않음
 
    }
  }, []);
 
 
 

 

728x90
728x90

이 글은 [[한글자막] React 완벽 가이드 with Redux, Next.js, TypeScript]를 수강하며 정리한 글입니다.

 

 

⚛️ 기본 환경: IDE: VS code, Language: React

 

 

useEffect()

: 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  useEffect(() => {
    fetchMoviesHandler();
  }, [fetchMoviesHandler]);
 
  async function fetchMoviesHandler() {
    setIsLoading(true);
    setError(null);
 
    try{
      const response = await fetch("https://swapi.dev/api/films/");
      
      if(!response.ok){
        throw new Error('Something went wrong!');
      }
 
      const data = await response.json();
  
      const transformedMovies = data.results.map(movieData => { // 필요한 데이터만 가져와서 key값을 변환
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_date,
        };
      });
  
      setMovies(transformedMovies);
    } catch(error) {
      setError(error.message);
    }
    setIsLoading(false);
  };
 
 
 

🚨 상기 코드를 수행할 경우, useEffect 함수에서 fetchMoviesHandler 함수를 호출하면 컴포넌트가 렌더링될 때마다 fetchMoviesHandler 함수가 호출되어 무한 로딩이 발생

* fetchMoviesHandler: fetch()로 외부 데이터를 가져오며, 이 때, re-rendering됨

 

⭐ 대안: useCallBack() 사용

useCallBack()

:  첫 번째 인자로 넘어온 함수를, 두 번째 인자로 넘어온 배열 형태의 함수 실행 조건의 값이 변경될 때까지 저장해놓고 재사용할 수 있게하는 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  const fetchMoviesHandler = useCallback(async () => {
    setIsLoading(true);
    setError(null);
 
    try{
      const response = await fetch("https://swapi.dev/api/films/");
      
      if(!response.ok){
        throw new Error('Something went wrong!');
      }
 
      const data = await response.json();
  
      const transformedMovies = data.results.map(movieData => { // 필요한 데이터만 가져와서 key값을 변환
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_date,
        };
      });
  
      setMovies(transformedMovies);
    } catch(error) {
      setError(error.message);
    }
    setIsLoading(false);
  }, []);
 
 
 

 

 

참고 자료

 

[React] 리액트 Hooks : useCallback() 함수 사용법

🚀 useCallback() useCallback은 useMemo와 비슷한 Hook입니다. useMemo는 특정 결괏값을 재사용할 때 사용하는 반면, useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하는 함수입니다. useC

cocoon1787.tistory.com

 

728x90
728x90

이 글은 코딩알려주는누나의 [리액트 초보자 입문강의 3탄]를 수강하며 정리한 글입니다.

 

 

⚛️ 기본 환경: IDE: replit, Language: React

* replit: Browser 기반 IDE

 

 

🚨 화면단에 보여지는 요소들을 매번 업데이트할 경우, 비용과 시간이 많이 발생하는 문제를 해결하기 위해 variable 대신 state 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, {useState} from 'react';
 
import './App.css'
 
export default function App() {
  let count1 = 0;
  const [count2, setCount2] = useState(0);
  const increase = () => {
   count1 += 1;
    setCount2(count2 + 1);
    console.log('count1: ' + count1 + ', count2: ' + count2);
  }
  
  return (
    <main>
      <div>variable: {count1}</div>
      <div>state: {count2}</div>
      <button onClick={increase}>증가</button>
    </main>
  )
}
 
 
 

⭐ state는 변수와 달리 변경 시, state에 값을 직접 대입하는 것이 아닌 set 함수를 활용

React에서는 variable과 state를 구분하여 state가 업데이트 될 때만 UI(User Inferface, 사용자가 제품/서비스를 사용할 때, 마주하게 되는 면)를 업데이트

 

해당 동작에 대한 console.log를 살펴보면 다음과 같음

🚨variable-count1은 value가 1로 고정되며 state-count2는 UI보다 value가 1이 작음

useState() 사용 시, state값이 변경될 경우 해당 컴포넌트(App function)를 새로고침하여 변수값이 0으로 초기화되어 버튼 클릭시 초기값 0에 1을 더해주게 됨 → 변수의 값이 기억되지 않으므로 임시 저장용으로만 사용

useState()에서 set 메서드는 비동기적으로 작동 = UI를 업데이트하는 set 함수의 모든 동작을 모아서 한 번에 처리

console에 반환되는 값은 업데이트가 진행되기 전(set 함수가 동작하기 )의 값으로 반환이 되고, UI는 모든 set 함수가 한 번에 처리되어 업데이트 된 state 값을 전달받게 됨

1
2
3
4
5
6
  const increase = () => {
    count += 1;
    setCount2(count2 + 1);
    console.log('count1: ' + count1 + ', count2: ' + count2);
  }
 
 
 

increase 메서드 호출 시,

1. count1 업데이트

2. setCount2 대기

3. console.log 실행

4. 해당 컴포넌트 내 모든 set 함수 실행

5. UI 업데이트

 

728x90
728x90

이 글은 [[한글자막] React 완벽 가이드 with Redux, Next.js, TypeScript]를 수강하며 정리한 글입니다.

 

 

⚛️ 기본 환경: IDE: VS code, Language: React

 

 

Redux 사용법

1. createSlice 선언

: initialState, reducer 함수의 객체, slice의 이름을 받아서 reducer와 state에 해당하는 action 생성자와 action type을 자동으로 생성하는 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createSlice } from "@reduxjs/toolkit";
 
const uiSlice = createSlice({
    name'ui',
    initialState: {cartIsVisible: false},
    reducers: {
        toggle(state){
            state.cartIsVisible = !state.cartIsVisible;
        }
    },
});
 
export const uiActions = uiSlice.actions;
 
export default uiSlice;
 
 
 

 

2. consigureStore에 reducer 전달

: reducer 매개변수를 통해 루트 리듀서를 지정할 수 있으며, 여러 개의 리듀서를 조합하여 사용할 수 있음

* reducer: 이전 상태와 액션을 받아와서 새로운 상태를 반환, Redux 애플리케이션에서 상태의 변화는 액션을 디스패치하면 리듀서를 통해 이루어짐

1
2
3
4
5
6
7
8
9
import { configureStore } from '@reduxjs/toolkit';
import uiSlice from './ui-slice';
 
const store = configureStore({
    reducer: {ui: uiSlice.reducer}
});
 
export default store;
 
 
 

 

3. 전체 Application에 redux store 공유

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
 
import './index.css';
import App from './App';
import store from './store';
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>);
 
 
 

 - Provider: 하위 컴포넌트인 App 컴포넌트와 그 아래에 있는 모든 컴포넌트에 Redux 스토어를 제공

 - store: Redux 스토어 객체, Provider 컴포넌트의 store prop으로 전달되어 모든 컴포넌트에 공유됨

 

4. useDispatch를 통한 action 전달

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import classes from './CartButton.module.css';
 
import {uiActions} from '../../store/ui-slice'
import { useDispatch } from 'react-redux';
 
const CartButton = (props) => {
  const dispatch = useDispatch();
  
  const toggleCartHandler = () => {
    dispatch(uiActions.toggle());
  };
  
  return (
    <button className={classes.button} onClick={toggleCartHandler}>
      <span>My Cart</span>
      <span className={classes.badge}>1</span>
    </button>
  );
};
 
export default CartButton;
 
 
 

 - click 이벤트에 toggle 핸들러를 추가

 - useDispatch: 생성한 action을 작동시킴

 - uiActions.toggle(): Redux 액션을 반환하는 액션 생성자 함수

[정리]

버튼 클릭

 → dispatch(uiActions.toggle()) 호출

 → Redux 스토어에 uiActions.toggle()이 반환한 액션 객체를 디스패치

 → Redux 스토어는 전달받은 액션 객체의 type 속성*을 기반으로 어떤 리듀서를 호출할지 결정하여 리듀서에게 액션 객체와 현재의 상태를 전달

* createSlice.actions을 사용하면 type 속성을 unique identifier로 자동 생성

 → 리듀서는 전달받은 액션의 타입을 확인하여 어떤 작업을 수행할지 결정

 

5. useSelector를 통한 Redux 상태 객체에서 특정 속성을 선택하여 반환하여 조건부 화면 반환 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useSelector } from 'react-redux';
import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import Products from './components/Shop/Products';
 
function App() {
  const showCart = useSelector((state) => state.ui.cartIsVisible);
 
  return (
    <Layout>
      {showCart && <Cart />}
      <Products />
    </Layout>
  );
}
 
export default App;
 
 
 

* 여기서는 Redux 저장소에서 ui의 cartIsVisible 값을 선택하여 반환

ui = redux 저장소의 uiSlice.reducer(=새로운 상태를 반환하는 함수)

ui.cartIsVisible = uiSlice.reducer를 통해 cartIsVisible의 기존 state를 받아 신규 state를 반환

 

결과

Redux를 활용한 toggleBtn 동작 구현

 

 

 

참고 자료

 

createSlice | Redux Toolkit

 

redux-toolkit.js.org

 

728x90
728x90

이 글은 [[한글자막] React 완벽 가이드 with Redux, Next.js, TypeScript]를 수강하며 정리한 글입니다.

 

 

⚛️ 기본 환경: IDE: VS code, Language: React

 

 

Redux toolKit 사용하기

1. Redux toolKit 설치

1
2
npm install @reduxjs/toolkit
 
 

 

2. createSlice import 및 선언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import {createSlice} from '@reduxjs/toolkit';
 
const initialState = {counter: 0, showCounter: true};
 
const counterSlice = createSlice({
    name'counter'// slice 이름
    initialState, // initialState: initialState와 동일
    reducers: {
        increment(state) {
            state.counter++;
        }, // 현재 상태를 변경한 것이 아니라 redux toolkit이 자동으로 원래 상태를 복제 후 새로운 상태 객체를 생성하여 반환
        decrement(state) {
            state.counter--;
        },
        increase(state, action) {
            state.counter = state.counter + action.payload;
        }, // action-payload: 추가 데이터
        toggleCounter(state) {
            state.showCounter = !state.showCounter;
        },
    }
});
 
 
 

⭐ redux 사용 시, 기존 state를 직접 변경하지 않도록 유의

⭐ state.counter++: 기존 state를 직접 업데이트한 것이 아니라 redux toolkit에 의해 만들어진 새로운 상태 객체를 업데이트 한 것

 

3. createSlice를 store에 등록

1
2
3
4
const store = configureStore({
    reducer: configureStore.reducer
});
 
 
 

cf. configureStore를 통한 reducer 병합

1
2
3
4
const store = configureStore({reducer: {
    counter: counterSlice.reducer
}}); configureStore-reducer: 모든 reducer를 하나의 큰 reducer로 병합
 
 
 

 

4. reducer 메서드에 접근하기 위한 slice의 actions 선언

: createSlice 함수의 reducer 영역에 있는 reducer method와 이름이 같은 메서드 호출 시 해당 메서드의 action 객체 생성

1
2
3
export const counterActions = counterSlice.actions;
// counterSlice.actions: return {type: 'some auto-generated unique identifier'}
 
 
 

 

5. action이 필요한 Component에서 import 후 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { counterActions } from "../store/index";
 
const Counter = () => {
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);
  const dispatch = useDispatch();
 
  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };
  const increaseHandler = () => {
    dispatch(counterActions.increase(5)); 
  }; // redux/toolkit: {type: SOME_UNIQUE_IDENTIFIER, payload: 5}
  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };
 
  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
 
    return (
      <main className={classes.counter}>
        <h1>Redux Counter</h1>
        {show && (
          <div className={classes.value}>-- COUNTER VALUE: {counter} --</div>
        )}
        <div>
          <button onClick={incrementHandler}>Increment</button>
          <button onClick={increaseHandler}>Increment by 5</button>
          <button onClick={decrementHandler}>Decrement</button>
        </div>
        <button onClick={toggleCounterHandler}>Toggle Counter</button>
      </main>
    );
  };
};
 
export default Counter;
 
 
 
 

redux/toolkit 내부에서 action 객체를 자동으로 생성

→ payload(추가 데이터)값의 key 이름을 payload로 자동으로 설정하였으므로 createSlice-reducer 선언 시 유의

 

728x90