-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[11팀 박근백] [Chapter 1-3] React, Beyond the Basics #6
base: main
Are you sure you want to change the base?
Changes from all commits
faf1325
c5d403e
02baec7
e8a248f
75c781b
4761392
6f3cf34
2a341a5
fdf79bd
e0d0740
c17538f
7bc7ebf
048e25e
9af873a
a5af0f0
d59adc0
f8a7d79
d7a7a49
2366a47
0b59347
6954f60
6d0319f
dc9e3ba
847b898
75c48c6
735bf34
43c5590
e8b9ff2
25c11fe
5fedabf
cbaa7e9
372d821
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,3 +85,4 @@ jobs: | |
run: | | ||
npm install | ||
npm run test:advanced | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: External Test | ||
|
||
on: | ||
pull_request: | ||
types: | ||
- synchronize | ||
- opened | ||
- reopened | ||
|
||
workflow_dispatch: | ||
|
||
jobs: | ||
external: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
fetch-depth: 0 | ||
ref: ${{ github.event.pull_request.head.sha }} | ||
- name: external-test | ||
run: | | ||
npm install | ||
npm run test:external | ||
enhanced: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
fetch-depth: 0 | ||
ref: ${{ github.event.pull_request.head.sha }} | ||
- name: enhanced-test | ||
run: | | ||
npm install | ||
npm run test:enhanced |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,33 @@ | ||
export function deepEquals<T>(objA: T, objB: T): boolean { | ||
return objA === objB; | ||
// 원시타입의 값이 동일하거나 기존 객체의 주소값이 바뀌지 않은 경우 | ||
if (objA === objB) { | ||
return true; | ||
} | ||
|
||
// null인 경우 | ||
if (objA === null || objB === null) { | ||
return objA === objB; | ||
} | ||
|
||
// 나머지 원시값들 처리 | ||
if (typeof objA !== "object" || typeof objB !== "object") { | ||
return objA === objB; | ||
} | ||
|
||
const objAKeys = Object.keys(objA); | ||
const objBKeys = Object.keys(objB); | ||
|
||
if (objAKeys.length !== objBKeys.length) { | ||
return false; | ||
} | ||
|
||
for (const key of objAKeys) { | ||
const valueA = Reflect.get(objA, key); | ||
const valueB = Reflect.get(objB, key); | ||
if (!deepEquals(valueA, valueB)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,33 @@ | ||
export function shallowEquals<T>(objA: T, objB: T): boolean { | ||
return objA === objB; | ||
// 원시타입의 값이 동일하거나 기존 객체의 주소값이 바뀌지 않은 경우 | ||
if (objA === objB) { | ||
return true; | ||
} | ||
|
||
// null인 경우 | ||
if (objA === null || objB === null) { | ||
return objA === objB; | ||
} | ||
|
||
// 나머지 원시값들 처리 | ||
if (typeof objA !== "object" || typeof objB !== "object") { | ||
return objA === objB; | ||
} | ||
|
||
const objAKeys = Object.keys(objA); | ||
const objBKeys = Object.keys(objB); | ||
|
||
if (objAKeys.length !== objBKeys.length) { | ||
return false; | ||
} | ||
|
||
for (const key of objAKeys) { | ||
const valueA = Reflect.get(objA, key); | ||
const valueB = Reflect.get(objB, key); | ||
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 Reflect가 Proxy 객체를 함께 사용할 때 객체의 특정 키에 접근할 때 의도하는 로직을 실행할때 사용하는 것으로만 알다가 실제 사용은 근백님 덕분에 알아갑니다 bb 다양하게 시도해서 코드를 간결하게 구현하신게 너무 좋습니다 bb |
||
if (valueA !== valueB) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,17 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { shallowEquals } from "../equalities"; | ||
import { ComponentType } from "react"; | ||
import React, { ComponentType, ReactNode } from "react"; | ||
|
||
export function memo<P extends object>( | ||
Component: ComponentType<P>, | ||
_equals = shallowEquals, | ||
) { | ||
return Component; | ||
let memoizedComponent: ReactNode | null = null; | ||
let memoizedProps: P | null = null; | ||
return (props: P) => { | ||
if (!_equals(memoizedProps, props)) { | ||
memoizedProps = props; | ||
memoizedComponent = React.createElement(Component, props); | ||
} | ||
return memoizedComponent; | ||
}; | ||
} | ||
Comment on lines
+8
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 크.. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런데 멘토링때 들은 바로는 위와같이 클로저를 이용하여 구현한 경우에는 메모리 누수가 발생할 수 있고 그것을 개발자가 따로 신경써 처리해야하는데 useRef를 이용해 구현하면 메모리 관리를 react 측으로 넘길 수 있다는 장점이 있다고 하셨습니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-unsafe-function-type */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-function-type */ | ||
import { DependencyList } from "react"; | ||
import { useMemo } from "./useMemo"; | ||
|
||
export function useCallback<T extends Function>( | ||
factory: T, | ||
_deps: DependencyList, | ||
) { | ||
// 직접 작성한 useMemo를 통해서 만들어보세요. | ||
return factory as T; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
const memoizedCallback = useMemo(() => factory, _deps); | ||
return memoizedCallback; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,19 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { DependencyList } from "react"; | ||
import { shallowEquals } from "../equalities"; | ||
import { useRef } from "./useRef"; | ||
|
||
export function useMemo<T>( | ||
factory: () => T, | ||
_deps: DependencyList, | ||
_equals = shallowEquals, | ||
): T { | ||
// 직접 작성한 useRef를 통해서 만들어보세요. | ||
return factory(); | ||
const valueRef = useRef<T | null>(null); | ||
const depsRef = useRef(_deps); | ||
|
||
if (valueRef.current === null || !_equals(depsRef.current, _deps)) { | ||
valueRef.current = factory(); | ||
depsRef.current = _deps; | ||
} | ||
|
||
return valueRef.current; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
import { useState } from "react"; | ||
|
||
export function useRef<T>(initialValue: T): { current: T } { | ||
// React의 useState를 이용해서 만들어보세요. | ||
return { current: initialValue }; | ||
const [ref] = useState(() => ({ current: initialValue })); | ||
return ref; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { useRef } from "./useRef"; | ||
import { useSyncExternalStore } from "react"; | ||
import { shallowEquals } from "../equalities"; | ||
import { Store } from "../../storeUtils"; | ||
|
||
export const useStore = <T, S>(store: Store<T>, selector: (store: T) => S) => { | ||
const prevRef = useRef<S | null>(null); | ||
|
||
return useSyncExternalStore(store.subscribe, () => { | ||
const next = selector(store.getState()); | ||
if (prevRef.current === null || !shallowEquals(prevRef.current!, next)) { | ||
prevRef.current = next; | ||
} | ||
return prevRef.current; | ||
}); | ||
}; | ||
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리뷰하러 왔다가 배우고만 가는 것 같네요.. useSyncExternalStore를 이렇게 활용하는구나를 배우고 갑니다..! 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 한가지 궁금한게 있어서 여쭙습니다. useStore에서 전역 공간 확보를 위해서 useSyncExternalStore를 쓰신 것 같은데 혹시 제가 이해한게 맞을까요...? 생소한 훅이라서 정말 궁금하여 여쭙습니다...! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오... useSyncExternalStore은 뭔가요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 useSyncExternalStore가 뭔지 궁금하네요,, 주로 전역으로 관리하기 위해 사용되는 건가요??? 어떨때 주로 사용되나용? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
useSyncExternalStore는 외부 데이터 저장소와 React 컴포넌트를 동기화하는데 사용하는 훅입니다.
context api 와 함께 이용한 이유는 context api 를 사용해 지역적인 상태들을 관리하곤 하는데 이와 전역상태와 구분하기 위해 사용합니다. ( 해당 context 내부에서만 해당 상태를 사용할 수 있도록 ) 저는 zustand를 자주 사용하고 있어서 진짜 전역적인 변경이 있는 경우 zustand 를 그 외에는 context api + zustand를 사용합니다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reflect라는게 있는걸 이번에 처음알았네요..! 정말 몰라서 여줍는건데, 혹시 이렇게 하는 것과, objA[Key]와는 어떤 차이가 있는지 여쭤봐도 괜찮을까요...?
정말 몰라서 신기해서 여쭙습니다..