반응형

React

렌더링 방식

  1. 내부 상태값(state)이나 중앙 상태값(redux store 등)이 변경되는 경우
  2. 부모 컴포넌트가 재렌더링되는 경우, 자식 컴포넌트도 재렌더링.

React 컴포넌트에서 return nul*을 사용하면 해당 컴포넌트는 아무것도 렌더링되지 않습니다.
이 방식을 사용하면 조건에 따라 특정 컴포넌트를 출력하지 않도록 만들 수 있습니다.

useEffect(() => {
        // 데이터를 비동기적으로 가져옵니다.
        fetchDataFromAPI().then(responseData => {
            setData(responseData);
        });
    }, []);

    // 데이터가 없을 경우 아무것도 렌더링하지 않습니다.
    if (!data) {
        return null;
    }

 

렌더링 순서

  1. 컴포넌트 함수 호출
  2. 컴포넌트 본문 호출
  3. useEffect

→ 컴포넌트 함수가 가장 먼저 호출되므로 if(!data) return loading 같은 걸로 처리

if (!data) {
    return <div>Loading...</div>;
}

 

useState

상태를 업데이트하는 함수(set)는 상태값을 변경하고 컴포넌트를 재렌더링합니다.

useEffect 첫째, 컴포넌트가 마운트된 직후에 코드를 실행하고, 둘째, 특정 상태가 변경될 때 코드를 실행합니다.

useState → 컴포넌트 렌더링 → useEffect

 

 


 

useEffect

 

  • 의존성 배열이 없는 경우 (useEffect에 두 번째 매개 변수가 전혀 주어지지 않은 경우),
    useEffect 내의 코드는 컴포넌트가 렌더링 될 때마다 실행됩니다. (무조건 실행)
  • 빈 의존성 배열이 있는 경우 (useEffect(fn, [])), useEffect 내의 코드는 컴포넌트가 처음 마운트될 때
    한 번만 실행되고, 그 후에는 실행되지 않습니다. (딱 한번)
  • 의존성 배열이 있는 경우 (useEffect(fn, [dep1, dep2, ...])), useEffect 내의 코드는 컴포넌트가
    처음 마운트될 때와 의존성 배열의 어떤 값이 변경될 때마다 실행됩니다. (감지 대상)
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;

    // Cleanup function
    return () => {
      document.title = 'React App';
    };
  }, [count]); // 의존성 배열에 "count"를 추가합니다.

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

사용자 액션 → count useState변경 → 컴포넌트 리로딩

 

→ count 때문에 리로딩되어서 useEffect 동작

의존성 배열에 추가된 state에 의해 리로딩 된게 아니라면 useEffect가 동작하지 않는다.
의존성 배열이 없다면 첫 마운트(사이트 진입) 때에만 useEffect가 동작한다.

 

예를 들어, 서버에서 데이터를 비동기적으로 가져오는 경우에는 useEffect를 사용하여 데이터를 가져올 수 있습니다. 이 데이터를 컴포넌트의 상태에 저장하려면 useState를 사용해야 합니다.
그러면 상태가 업데이트되고 컴포넌트가 재렌더링되므로, 사용자는 최신의 데이터를 볼 수 있게 됩니다.

 

 

useEffect는 다음과 같은 다양한 경우에 사용될 수 있습니다:

  1. 비동기 작업: 서버로부터 데이터를 가져오는 등의 비동기 작업을 수행할 때 useEffect를 사용할 수 있습니다.
  2. 이벤트 리스너 설정: window의 resize 이벤트나 키보드 keydown 이벤트 등을 감지하려면 이벤트 리스너를 설정해야 하는데, 이런 작업도 useEffect에서 수행합니다.
  3. DOM 조작: React는 일반적으로 DOM을 직접 조작하지 않고, 상태 업데이트를 통해 간접적으로 DOM을 업데이트하는 것을 선호합니다. 그러나 특정 라이브러리를 사용하거나, 특정 DOM API를 직접적으로 사용해야 하는 경우에는 useEffect를 사용하여 이를 수행할 수 있습니다.
  4. 정리(cleanup) 작업: 컴포넌트가 언마운트되거나, 의존성이 변경될 때 정리 작업을 수행해야 하는 경우에도 useEffect를 사용할 수 있습니다. 예를 들어, 컴포넌트가 언마운트되면 이벤트 리스너를 제거해야 하는데, 이런 작업은 useEffect의 반환 함수에서 수행합니다.

따라서 useEffect는 비동기 작업뿐만 아니라 여러 가지 다른 사이드 이펙트를 처리하는 데 사용됩니다. 비동기 작업은 useEffect를 사용하는 주요한 경우 중 하나일 뿐입니다.

사이드 이펙트(side effect)는 프로그래밍 용어로, 함수나 컴포넌트의 주요 기능 외에 발생하는 부수적인 효과를 의미합니다. 이는 전역 상태를 변경하거나, 외부 네트워크와 통신하거나, DOM을 직접 조작하는 것과 같은 작업을 포함할 수 있습니다.

React에서 사이드 이펙트는 아래와 같은 상황들을 가리킵니다:

  1. 네트워크 요청: 서버에 데이터를 요청하거나, 서버로 데이터를 보내는 등의 네트워크 요청이 사이드 이펙트입니다. 이러한 요청은 종종 비동기적으로 수행됩니다.
  2. 구독(subscriptions): 외부 데이터 소스를 구독하는 것도 사이드 이펙트입니다. 예를 들어, WebSocket 연결을 통해 서버에서 실시간 업데이트를 받거나, 이벤트 리스너를 설정하여 사용자 입력을 감지하는 것이 이에 해당합니다.
  3. 타이머: setTimeout이나 setInterval과 같은 타이머를 설정하는 것도 사이드 이펙트입니다.
  4. 직접적인 DOM 조작: React는 일반적으로 DOM을 직접 조작하지 않고 상태 업데이트를 통해 간접적으로 DOM을 업데이트하는 것을 선호합니다. 그러나 특정 라이브러리를 사용하거나, 특정 DOM API를 직접적으로 사용해야 하는 경우에는 DOM을 직접 조작해야 할 수 있습니다.

 


 

REDUX

Store

state → 상태(데이터)

reducer → 액션(Action)을 수신, 상태(State)를 업데이트

getState → state를 가져와서 rendering

action → 상태를 갖고있는 그냥 메시지같은거

dispatch [dispatch(action)] → action으로 state변경, pub 작업을 한다.

subscribe[useSelector] → state가 변경되면, sub되어 state를 사용하는 컴포넌트들도 갱신 (화면)

      → useSelector를 사용하면 자동 subscribe함. 그래서 안씀

state는 reducers를 통해 변하고,

reducers에 action을 전달한다. action은 변화할 값을 갖고 있으며, dispatch가 action을 사용한다.

컴포넌트쪽에서는 노출된 것은 action이며, dispatch로 action을 전달한다. 한쪽에서 dispatch를 사용하면, 해당 state를 useSelector로 구독하는 모든 컴포넌트들은 상태변화를 감지한다.

// init

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. 액션 함수를 만든다. (액션은 update data만 갖고 있음)
  2. dispatch()로 액션 함수를 전달한다.
  3. reducer에서 state, 액션함수를 인자로 받아서 변경하고 return
  4. 화면에 rendering 된다

 

ReduxTookit

reducer, action 함수를 같이 만듬

state도 slice안에서 관리함

들어온 param은 전부 payload라는 명칭을 사용

import { createSlice } from '@reduxjs/toolkit';

const chatSlice = createSlice({
  name: 'chat',
  initialState: { messages: [] },
  reducers: {
    addMessage: (state, action) => {
      state.messages.push(action.payload);
    },
    getMessage: (state, action) => {
      return state.messages.find(msg => msg.id === action.payload);
    },
    updateMessage: (state, action) => {
      const message = state.messages.find(msg => msg.id === action.payload.id);
      if (message) message.text = action.payload.text;
    },
    deleteMessage: (state, action) => {
      const index = state.messages.findIndex(msg => msg.id === action.payload);
      if (index !== -1) state.messages.splice(index, 1);
    },
  },
});

export const { addMessage, getMessage, updateMessage, deleteMessage } = chatSlice.actions;

export default chatSlice.reducer;

 

비동기 thunk

thunk를 만들고, slice → extraReducers에서 사용

pending, fulfilled, rejected 3가지 상태가 있다.

// chatSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 비동기 액션을 위한 Thunk
export const fetchMessages = createAsyncThunk(
  'chat/fetchMessages',
  async () => {
    const response = await axios.get('/api/messages');
    return response.data; // 이 값이 액션의 payload로 들어갑니다.
  }
);

const chatSlice = createSlice({
  name: 'chat',
  initialState: { messages: [] },
  reducers: {
    addMessage: (state, action) => {
      state.messages.push(action.payload);
    },
  },
  extraReducers: (builder) => {
    builder.addCase(fetchMessages.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchMessages.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.messages = action.payload;
      })
      .addCase(fetchMessages.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

export const { addMessage } = chatSlice.actions;

export default chatSlice.reducer;

사용

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchMessages } from './chatSlice';

function Chat() {
  const dispatch = useDispatch();
  const messages = useSelector(state => state.chat.messages);

  useEffect(() => {
    dispatch(fetchMessages());
  }, [dispatch]);

  return (
    <div>
      {messages.map((message, index) => (
        <p key={index}>{message}</p>
      ))}
    </div>
  );
}

export default Chat;

 


설정

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import webSocketSlice from "redux/webSocketSlice";
import chatSlice from "redux/chatSlice";
import chatRoomSlice from "redux/chatRoomSlice";

// rootReducer 생성
const rootReducer = combineReducers({
  webSocket: webSocketSlice.reducer,
  chat: chatSlice.reducer,
  chatRoom: chatRoomSlice.reducer,
});

export const store = configureStore({
  reducer: rootReducer,
});

보통 reducer만 내보낸다고함.

비동기 요청 createAsynThunk는 같은 slice 파일 내에 위치시키고

따로 export해서 사용하는 것 같다.

extraReducers는 사용하지말고, 그냥 createAsyncThunk로 모든것을 처리하자.

반응형

+ Recent posts