Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy to GitHub Pages

on:
push:
branches:
- main

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 리액트 성능 최적화
Promise.all의 await을 제거하고 컴포넌트를 쪼개고 Context를 작게 가져가고 메모이제이션을 해 렌더링 최적화를 진행했다.
렌더링시마다 실행되는 무거운 연산도 메모이제이션하고 중복 호출 제거, api 호출 캐싱 처리 등.
드래그&드롭 역시 최적화를 진행했다.
3 changes: 3 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
packages:
- '.'

onlyBuiltDependencies:
- '@swc/core'
- msw
5 changes: 1 addition & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { ChakraProvider } from "@chakra-ui/react";
import { ScheduleProvider } from "./ScheduleContext.tsx";
import { ScheduleTables } from "./ScheduleTables.tsx";
import ScheduleDndProvider from "./ScheduleDndProvider.tsx";

function App() {

return (
<ChakraProvider>
<ScheduleProvider>
<ScheduleDndProvider>
<ScheduleTables/>
</ScheduleDndProvider>
<ScheduleTables/>
</ScheduleProvider>
</ChakraProvider>
);
Expand Down
7 changes: 5 additions & 2 deletions src/ScheduleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { createContext, PropsWithChildren, useContext, useState } from "react";
/* eslint-disable react-refresh/only-export-components */
import React, { createContext, PropsWithChildren, useContext, useMemo, useState } from "react";
import { Schedule } from "./types.ts";
import dummyScheduleMap from "./dummyScheduleMap.ts";

Expand All @@ -20,8 +21,10 @@ export const useScheduleContext = () => {
export const ScheduleProvider = ({ children }: PropsWithChildren) => {
const [schedulesMap, setSchedulesMap] = useState<Record<string, Schedule[]>>(dummyScheduleMap);

const value = useMemo(() => ({ schedulesMap, setSchedulesMap }), [schedulesMap]);

return (
<ScheduleContext.Provider value={{ schedulesMap, setSchedulesMap }}>
<ScheduleContext.Provider value={value}>
{children}
</ScheduleContext.Provider>
);
Expand Down
93 changes: 61 additions & 32 deletions src/ScheduleDndProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { DndContext, Modifier, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { PropsWithChildren } from "react";
import {
DndContext,
Modifier,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { PropsWithChildren, useCallback } from "react";
import { CellSize, DAY_LABELS } from "./constants.ts";
import { useScheduleContext } from "./ScheduleContext.tsx";

Expand All @@ -17,19 +23,30 @@ function createSnapModifier(): Modifier {
const maxX = containerRight - right;
const maxY = containerBottom - bottom;


return ({
return {
...transform,
x: Math.min(Math.max(Math.round(transform.x / CellSize.WIDTH) * CellSize.WIDTH, minX), maxX),
y: Math.min(Math.max(Math.round(transform.y / CellSize.HEIGHT) * CellSize.HEIGHT, minY), maxY),
})
x: Math.min(
Math.max(
Math.round(transform.x / CellSize.WIDTH) * CellSize.WIDTH,
minX
),
maxX
),
y: Math.min(
Math.max(
Math.round(transform.y / CellSize.HEIGHT) * CellSize.HEIGHT,
minY
),
maxY
),
};
};
}

const modifiers = [createSnapModifier()]
const modifiers = [createSnapModifier()];

export default function ScheduleDndProvider({ children }: PropsWithChildren) {
const { schedulesMap, setSchedulesMap } = useScheduleContext();
const { setSchedulesMap } = useScheduleContext();
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
Expand All @@ -38,33 +55,45 @@ export default function ScheduleDndProvider({ children }: PropsWithChildren) {
})
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleDragEnd = (event: any) => {
const { active, delta } = event;
const { x, y } = delta;
const [tableId, index] = active.id.split(':');
const schedule = schedulesMap[tableId][index];
const nowDayIndex = DAY_LABELS.indexOf(schedule.day as typeof DAY_LABELS[number])
const moveDayIndex = Math.floor(x / 80);
const moveTimeIndex = Math.floor(y / 30);
const handleDragEnd = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(event: any) => {
const { active, delta } = event;
const { x, y } = delta;
const [tableId, index] = active.id.split(":");
const moveDayIndex = Math.floor(x / 80);
const moveTimeIndex = Math.floor(y / 30);

setSchedulesMap((prev) => {
const schedule = prev[tableId][index];
const nowDayIndex = DAY_LABELS.indexOf(
schedule.day as (typeof DAY_LABELS)[number]
);

setSchedulesMap({
...schedulesMap,
[tableId]: schedulesMap[tableId].map((targetSchedule, targetIndex) => {
if (targetIndex !== Number(index)) {
return { ...targetSchedule }
}
return {
...targetSchedule,
day: DAY_LABELS[nowDayIndex + moveDayIndex],
range: targetSchedule.range.map(time => time + moveTimeIndex),
}
})
})
};
...prev,
[tableId]: prev[tableId].map((targetSchedule, targetIndex) => {
if (targetIndex !== Number(index)) {
return targetSchedule;
}
return {
...targetSchedule,
day: DAY_LABELS[nowDayIndex + moveDayIndex],
range: targetSchedule.range.map((time) => time + moveTimeIndex),
};
}),
};
});
},
[setSchedulesMap]
);

return (
<DndContext sensors={sensors} onDragEnd={handleDragEnd} modifiers={modifiers}>
<DndContext
sensors={sensors}
onDragEnd={handleDragEnd}
modifiers={modifiers}
>
{children}
</DndContext>
);
Expand Down
Loading