- src
- assets: ์์ด์ฝ ๋ฐ ์ ์ ํ์ผ ์ ์ฅ์
- icons: svg ์ ์ฅ์
- components: ์ฌ์ฌ์ฉ ์ปดํฌ๋ํธ ๋ฐ ์คํฌ๋ฆฐ์์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ
- common: ์ฑ ์ ์ญ์์ ์ฌ์ฉํ๋ ๊ณตํต ์ปดํฌ๋ํธ
- pages: ์คํฌ๋ฆฐ์์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ ๋ชจ์
- context: ์ ์ญ ์ํ ๊ด๋ฆฌ
- hooks: ์ปค์คํ ํ ๋ฐ ๋ทฐ์์ ์ฌ์ฉํ๋ ๋ก์ง ์ ์ฅ์
- routes: ๋ผ์ฐํฐ ๊ตฌํ
- screens: ์ฑ ์คํฌ๋ฆฐ
- styles: ์ฑ ์คํ์ผ๋ง ๋ฐ ํ ๋ง
- types : ํ์ ์ ์
- util : ์ ํธ๋ฆฌํฐ ํจ์ ๋ฐ ๊ณตํต ๋ก์ง
- assets: ์์ด์ฝ ๋ฐ ์ ์ ํ์ผ ์ ์ฅ์
- Bottom Up Slide Animation
KeyboardAvoidingView๋ฅผ ์ฌ์ฉํ๋ฉด keyboardVerticalOffset ๊ฐ์ ํ๋์ฝ๋ฉ์ผ๋ก ๋ฃ์ด ๋ง์ถฐ์ผ ํด์ ๋ค์ํ ๊ธฐ๊ธฐ์์ ์ ํํ ๊ฐ์ ๊ตฌํํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค.
React-native ๋ด์ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Keyboard๋ฅผ ์ฌ์ฉํ์ฌ ๋์ด๋ฅผ ๊ตฌํด ๊ฐ์ ๊ตฌํ๋ฉด ํค๋ณด๋ ํฌ๊ธฐ์ ๋ง์ถ์ด ์ธํ์ ์ฌ๋ฆด ์ ์์์ผ๋ ์ฌ๋ผ๊ฐ๋ ์ ๋๋ฉ์ด์
์ด ๋ถ์์ฐ์ค๋ฌ์ ์ต๋๋ค.
๊ทธ ๋น์ ์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค.
const BottomUpSlideComponent = () => {
const {width} = useWindowDimensions();
const {theme} = useTheme();
const [keyboardHeight, setKeyboardHeight] = React.useState(0);
useEffect(() => {
Keyboard.addListener('keyboardDidShow', e => {
setKeyboardHeight(e.endCoordinates.height);
});
Keyboard.addListener('keyboardDidHide', () => {
setKeyboardHeight(0);
});
return () => {
Keyboard.removeAllListeners('keyboardDidShow');
Keyboard.removeAllListeners('keyboardDidHide');
};
}, []);
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={[
styles.container,
{height: keyboardHeight + 12 + 40},
{backgroundColor: theme.backgroundColor, width: width},
]}>
<TextInput style={styles.input} />
</KeyboardAvoidingView>
);
};
export default BottomUpSlideComponent;์์ฐ์์์ ์์ธํ ๋ณด๋ height๊ฐ์ ํตํด ์๋๋ฉ์ด์
์ ๊ตฌํํ๊ณ ์๋ ๊ฒ ๊ฐ์์ต๋๋ค.
๊ธฐ์กด ๋ก์ง์ ๋ณ๊ฒฝํ๊ณ react-native-reanimated๋ฅผ ์ฌ์ฉํ์๊ณ ,
ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ฐ์ด๋ ๊ณณ์ด ๋ง์ ๊ฒ ๊ฐ์ ์ปค์คํ
ํ
์ผ๋ก ๊ตฌํํ์์ต๋๋ค.
useKeybardHeight
import {Keyboard} from 'react-native';
import {useEffect, useState} from 'react';
import {
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
interface Props {
additionalHeight: number;
}
const useKeyboardHeight = ({additionalHeight}: Props) => {
const [keyboardHeight, setKeyboardHeight] = useState(0);
const height = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
height: height.value,
};
});
useEffect(() => {
height.value = withTiming(keyboardHeight + additionalHeight, {
duration: 100,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [keyboardHeight]);
useEffect(() => {
Keyboard.addListener('keyboardDidShow', e => {
setKeyboardHeight(e.endCoordinates.height);
});
Keyboard.addListener('keyboardDidHide', () => {
setKeyboardHeight(0);
});
return () => {
Keyboard.removeAllListeners('keyboardDidShow');
Keyboard.removeAllListeners('keyboardDidHide');
};
}, []);
return {
keyboardHeight,
height: height.value,
animatedStyle,
};
};
export default useKeyboardHeight;BottomUpSlideComponent
import React, {forwardRef} from 'react';
import {StyleSheet, TextInput} from 'react-native';
import useKeyboardHeight from '../../hooks/useKeyboardHeight';
import Animated from 'react-native-reanimated';
const BottomUpSlideComponent = forwardRef<TextInput, {}>((_, ref) => {
const {animatedStyle} = useKeyboardHeight({
additionalHeight: 64,
});
return (
<Animated.View style={[styles.container, animatedStyle]}>
<TextInput ref={ref} style={styles.input} />
</Animated.View>
);
});
export default BottomUpSlideComponent;์ค์ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ์ฌ๋ฌ๊ณณ์์ ์ฌ์ฉํ ๋, ์ด๋ฒคํธ ๋ฆฌ์ค๋ ํด์ ๊ฐ ๋์ง ์์ ์ ์์ ์ผ๋ก Input์ด ๋๋๋ง ๋์ง ์๋ ํ์์ด ๋ฐ์๋์์ต๋๋ค.
Error: Attempted to remove more RCtKeyboardObserver listteneners than added
์ด๋ฒคํธ๋ฅผ ๋ณ์์ ํ ๋นํ์ฌ ComponentDidUnmount์ ์ด๋ฐดํธ๋ฅผ ์ง์ฐ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ์์ต๋๋ค.
useEffect(() => {
// ๋ณ์ ํ ๋น
const keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', e => {
setKeyboardHeight(e.endCoordinates.height);
});
const keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => {
setKeyboardHeight(0);
});
return () => {
// component did unmount์ผ ๋ ์ญ์
keyboardWillShowListener.remove();
keyboardWillHideListener.remove();
};
}, []);๋ํ BottomUpSlideInput์ ์ฌ๋ฌ๊ณณ์์ ์ฌ์ฉํ๋ค๋ณด๋ TextInput์ ํ ๋นํ useRef์ ์ค๋ณต์ผ๋ก ์ธํ ๋๋๋ง ์ด์๊ฐ ๋ฐ์ํ์ต๋๋ค.
forwardRef์ useImperativeHandle์ ์ฌ์ฉํ์ฌ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์์ ์ปดํฌ๋ํธ๋ฅผ ์ ์ดํ๋ ๊ฒฝ์ฐ,
๊ฐ ์์ ์ปดํฌ๋ํธ์ ๋ํด ๋ณ๋์ ref๋ฅผ ์์ฑํด์ผ ํ๋๋ฐ, ํ๊ฐ์ ์ธํ์ ์ฌ๋ฌ ๋ถ๋ชจ์ปดํฌ๋ํธ๊ฐ ref ์ด์ด ๊ณต์ ํ๋ค๋ณด๋,
์ค์ ์ฌ์ฉํ ๋์๋ ์ํ๋ ์ปดํฌ๋ํธ๊ฐ ์๋ ์คํฌ๋ฆฐ ์ต์์ ์ปดํฌ๋ํธ์ ์ ์ธ๋ BottomUpSlideInput ์ปดํฌ๋ํธ๊ฐ ๋๋๋ง ๋๋ ํ์์ด ๋ฐ์๋์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ์ผ๋ก๋ BottomUpSlideInput ์ปดํฌ๋ํธ ๋ด์์ ์ฌ์ฉ์ฒ์ ๋ง๊ฒ ref์ input์ ์์ฑํ๊ณ ์ธํ์ ๋ฌ์์ฃผ๋ ๋ฐฉ๋ฒ์ด ์์์ผ๋
์ฝ๋์ ์๋๋ฅผ ์ฝ๊ฒ ํ์
ํ ์ ์๊ณ , ์ถ๊ฐ ์ฌ์ฉ์ฒ๊ฐ ์๊ธธ๋๋ง๋ค ์ธํ๊ณผ ref๋ฅผ ํด๋น ์ปดํฌ๋ํธ์ ์ถ๊ฐ ํด์ฃผ์ด์ผํ๊ณ ,
input์ธ ๋ค๋ฅธ ์์๋ค์ด ๋ค์ด์ฌ ์ ์์ด ์ฌ์ฌ์ฉ์ฑ์ด ๋จ์ด์ง๋ ์ด์ ๋ก ์ปดํฌ๋ํธ ๋ฆฌํฉํฐ๋ง์ ์งํํ๊ฒ ๋์์ต๋๋ค.
BottomUpSlideInput ์ปดํฌ๋ํธ๋ฅผ BottomUpSliderComponent๋ก ์ด๋ฆ์ ๋ฐ๊ฟ์ฃผ๊ณ
Input์ children์ผ๋ก ์ ๋ฌํ ๋ค editMode State๋ฅผ ํตํด Input์ ์ปจํธ๋กค ํ์์ต๋๋ค.
๋ณ๊ฒฝํ ์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค.
const BottomUpSlideComponent = ({
onSubmit,
children,
}: PropsWithChildren<Props>) => {
const {width} = useWindowDimensions();
const {theme} = useTheme();
const {delay} = useDelay(); // timeout custom hook
const {keyboardHeight, animatedStyle} =
useKeyboardHeight({ additionalHeight: 64, }); // animation custom hook
const [fakeLoading, setFakeLoading] = useState(false); // fake loading state
const onPressSubmit = () => {
onSubmit();
setFakeLoading(true);
delay(() => {
setFakeLoading(false);
}, 500);
};
return (
<Animated.View
style={[
styles.container,
animatedStyle,
{
width,
bottom: keyboardHeight ? 0 : -100,
backgroundColor: theme.backgroundColor,
},
]}>
{children}
<TouchableOpacity onPress={onPressSubmit} style={styles.submitButton}>
{fakeLoading ? (
<ActivityIndicator color={theme.grey} size="small" />
) : (
<Upload width={32} height={32} fill={theme.accent} />
)}
</TouchableOpacity>
</Animated.View>
);
};
export default BottomUpSlideComponent;BottomUpSlideComponent๋ฅผ ์ฌ์ฉํ ์์์
๋๋ค.
{
editMode === 'create' &&
<BottomUpSlideComponent onSubmit={createChecklist}>
{/* Children */}
<TextInput
ref={createChecklistRef}
style={[
styles.input,
{borderColor: theme.lightGrey, fontSize: theme.textXS},
]}
value={newChecklistContent + ''}
placeholder='์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์'
onChangeText={onChangeNewChecklistContent}
selectionColor={theme.accent}
keyboardType='default'
/>
</BottomUpSlideComponent>
}
{
editMode === 'update' &&
<BottomUpSlideComponent onSubmit={onSubmitUpdateContent}>
{/* Children */}
<TextInput
ref={updateChecklistInputRef}
style={[
styles.input,
{borderColor: theme.accent, fontSize: theme.textXS},
]}
value={editText + ''}
placeholder='์์ ํ ๋ด์ฉ์ ์
๋ ฅํด์ฃผ์ธ์'
onChangeText={onChangeEditText}
selectionColor={theme.accent}
keyboardType='default'
/>
</BottomUpSlideComponent>
}Dry ์์น๋ ์ค์ํ๋ ํ ๋์ ์๋๋ฅผ ์์ ๋ณผ ์ ์๋ ์ ์ด ๋ ํด๋ฆฐํ๋ค๊ณ ์๊ฐํ์ฌ ๊ณ ๋ฏผํ ๋์ ์์ฒ๋ผ ์ฌ์ฉํ๊ฒ ๋์์ต๋๋ค.
-
Checklists์ ์ํ๋ฅผ non-persistent local state๋ก ์ ์งํ๊ธฐ ์ํด ๋ ธ๋ ฅํ์์ต๋๋ค.
์ ์ญ์ํ ๋๋ Context Api๋ฅผ ํตํด ๊ด๋ฆฌํ๋ ๊ฒ์ด ์๋๋ผ useChecklists ์ปค์คํ ํ ์ ์์ฑํ์ฌ ๋ด๋ถ์ ์ํ๋ฅผ ์ ์ฅํ๊ณ ์กฐ์ํ๋ ๋ก์ง์ ๋๊ณ ,
์ฌ์ฉํ๋ ์ปดํฌ๋ํธ์ ๋ด๋ ค์ฃผ๋ ๋ฐฉ์์ผ๋ก local state๋ฅผ ์งํค๋ ค๊ณ ๋ ธ๋ ฅํ์์ต๋๋ค. -
๊ทธ ์ธ ์ฑ ์ ์ญ์ผ๋ก ํ์ํ ์ํ๋ค์ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ท๋ชจ์ ์ ํฉํ Context Api๋ฅผ ์ ํํ์ฌ ๊ด๋ฆฌํ์์ต๋๋ค.
ํ๋ก์ ํธ์ ๊ท๋ชจ๋ฅผ ๋ณด๋ฉด Context Api๋ง์ผ๋ก๋ ์ถฉ๋ถํ ๊ด๋ฆฌํ ์ ์๋ค๊ณ ์๊ฐํ์๊ณ , useContext ํ
์ ํตํด ์ฌ์ฉ์ ๋จ์ํ ํ ์ ์์์ต๋๋ค.
๋ํ Context Api๋ React์ ๋ด์ฅ๋์ด ์์ด ๋ค๋ฅธ Hook๋ค๊ณผ ํจ๊ป ์ ์๋ํ๋ฏ๋ก useState, useEffect์ ๊ฐ์ ํ
๊ณผ ํจ๊ป ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์๋ค๋ ์ฅ์ ๋ ์์ต๋๋ค.
Context Api๋ฅผ ์ฌ์ฉํ ๋ ๊ตฌ๋
ํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋๋๋ง ๋๋ ๋ถ๋ถ์ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ฌ ๋ด๋ ค์ฃผ๋ ๊ฐ๊ณผ ํจ์๋ค์ useMemo์ useCallback์ ์ฌ์ฉํ์ฌ ๋ฆฌ๋๋๋ง์ ์ต์ํ ํ์์ต๋๋ค.
- ์ฒดํฌ๋ฆฌ์คํธ์ ์์ดํ
์ ๋ง์ด ๋ฑ๋กํ์ฌ๋ ๋๋๋ง์ ๋ฌธ์ ๊ฐ ์๊ฒ๋ ์ฒดํฌ๋ฆฌ์คํธ ์ปดํฌ๋ํธ๋ฅผ Flatlist๋ก ์์ฑํ์์ต๋๋ค.
<FlatList
style={[styles.container,{ width }]}
data={checklists}
keyExtractor={item => item.id}
ListEmptyComponent={EmptyChecklistComponent}
// ์๋ ์ฝ๋๋ ์ธ๋ผ์ธ ํจ์๋ก ๋ฆฌ๋๋๋ง์ ์ด๋ํฉ๋๋ค. 3๋ฒ์ ํด๊ฒฐ ๊ณผ์ ์ ์ ์ด๋์์ต๋๋ค.
ListHeaderComponent={
() => checklists.length > 0 &&
<ProgressBar completedCount={completedChecklistCount} inCompletedCount={checklists.length} />
}
renderItem={({ item: checklist }) => (
<ChecklistItem
checklist={checklist}
onFocusInput={onFocusInput}
deleteChecklist={deleteChecklist}
onChangeCompleted={onChangeCompleted}
/>
)}
/>- ์ฌ์ฉํ๋ ์์ด์ฝ์ react-native-svg ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด svg๋ก ๊ตฌํํ์์ต๋๋ค.
import Checked from '../../../assets/icons/Checked.svg';
// ...
<IconButton
Icon={<Checked/>}
onPress={() => onChangeCompleted(checklist)}
/>
// ...- ํ๋กญ์ค๋ก ์ ๋ฌํ๋ ์ธ๋ผ์ธ ํจ์์ ๋ฆฌ๋๋๋ง ๋ฌธ์
๋ฆฌ์กํธ ์ปดํฌ๋ํธ์ ์ต์ ํ ๊ด๋ จํด์ ์กฐ์ฌํ์ง ๋ชปํ ๋ถ๋ถ์ด ์์์ต๋๋ค.
ํ๋กญ์ค๋ก ์ธ๋ผ์ธ ํจ์๋ฅผ ์ ๋ฌํ ๋ ๋ฐ์ํ ์ ์๋ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ๋ฌธ์ ์๋๋ฐ์,
๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค.
<FlatList
// ...
ListHeaderComponent={
() => checklists.length > 0 &&
<ProgressBar completedCount={completedChecklistCount} inCompletedCount={checklists.length} />
}
// ...
/>์ ์ฝ๋์์ ๋ฌธ์ ๋ FlatList ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋ ๋๋ง๋ค ListHeaderComponent ํ๋กญ์ค๋ก ์ ๋ฌ๋ ์ธ๋ผ์ธ ํจ์๊ฐ ํญ์ ์๋ก ์์ฑ๋๋ค๋ ๊ฒ์
๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก FlatList๊ฐ ๋ฆฌ๋ ๋๋ง๋ ๋๋ง๋ค ListHeaderComponent๋ ์๋ก์ด ๊ฐ์ผ๋ก ๊ฐฑ์ ๋์ด ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ์๊ณ ,
ํด๋น ํ๋ก๊ทธ๋์ค๋ ์ง์ฒ๋๋ฅผ ์๋ฏธํ๋ ์๋๋ฉ์ด์
์ ๊ณ์ 0๋ถํฐ ์คํํ๋ ์ด์๊ฐ ๋ฐ์ํ์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์ธ๋ผ์ธ ํจ์๋ฅผ ์ปดํฌ๋ํธ ๋ฐ๊นฅ์ผ๋ก ๋นผ๋ด์ด ๋ณ์์ ํ ๋นํ๋ ๋ฐฉ๋ฒ์ด ์์ง๋ง,
useCallback ํ
์ ํ์ฉํ์ฌ ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ ๊ฒ์ด ์ข๋ค๊ณ ์๊ฐํ๊ณ ๋ฆฌํํฐ๋ง์ ์งํํ์ต๋๋ค.
์๋๋ ์ด๋ฅผ ์ ์ฉํ ์์ ๋ ์ฝ๋์ ๋๋ค.
const ProgressBarComponent = useCallback(({ completedCount, inCompletedCount }: {
completedCount: number,
inCompletedCount: number,
}) => {
return <ProgressBar completedCount={completedCount} inCompletedCount={inCompletedCount} />
},[checklists])
const listHeader = checklists?.length > 0
? <ProgressBarComponent completedCount={completedChecklistCount} inCompletedCount={checklists.length} />
: null;
return (
<FlatList
style={[styles.container,{ width }]}
data={checklists}
keyExtractor={item => item.id}
ListEmptyComponent={EmptyChecklistComponent}
ListHeaderComponent={listHeader}
renderItem={({ item: checklist }) => (
<ChecklistItem
checklist={checklist}
onFocusInput={onFocusInput}
deleteChecklist={deleteChecklist}
onChangeCompleted={onChangeCompleted}
/>
)}
/>
);- ์ฒดํฌ๋ฆฌ์คํธ์ CRUD
์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์
๋ฐ์ดํธํ๋ ๊ณผ์ ์์ ์ธ ๊ฐ์ง ๋ก์ง์ ๊ณ ๋ฏผํ์์ต๋๋ค.
๊ฐ๊ฐ์ ๋ก์ง์ ๋ถ๋ณ์ฑ, ์ฝ๋ ๊ฐ๋ ์ฑ ์ธก๋ฉด์์ ์ฅ๋จ์ ์ ๊ฐ์ง๊ณ ์์ด์ ์ ํํ๊ธฐ ์ด๋ ค์ ๊ณ , ์ด๋ค ๊ฒ์ด ๋ ์ข์ ์ฝ๋์ธ์ง ๊ณ ์ฌํ์์ต๋๋ค.
- ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ. ๋ถ๋ณ์ฑ ๊ทธ๋ฆฌ๊ณ ๋ณต์ก์ฑ
์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ useMemo์ ํจ๊ป ๊น์ ๋ณต์ฌ๋ฅผ ํตํด ๋ถ๋ณ์ฑ์ ํ๋ณดํ๊ณ ์ ํ์ต๋๋ค.
ํ์ฌ ์ฒดํฌ๋ฆฌ์คํธ๋ 2๋์ค์ ๊ฐ์ฒด์ด์ง๋ง ์ถ๊ฐ์ ์ธ ํ๋กํผํฐ๋ ์๊ตฌ์ฌํญ์ด ๊ณ ๋ํ ๋ ๋์ ํ์ฅ์ฑ์ ์ด์ ์ ๋ง์ถ์์ต๋๋ค.
๋ค๋ง ๊น์ ๋ณต์ฌ ๋ก์ง์ด ์ถ๊ฐ๋๊ณ ,useMemo๋ฅผ ์ฌ์ฉํจ์ผ๋ก์ ๊ด๋ฆฌํด์ผํ๋ ์์กด์ฑ ๋ฐฐ์ด์ด ๋์ด๋๋ค๋ ๋จ์ ์ด ์์์ต๋๋ค.
๋ํ Memoization์ ์ฑ๋ฅ์ ์ด์ ์ ๊ฐ์ ธ์ฌ ์ ์์ง๋ง, ์ํ ์ ๋ฐ์ดํธ๋ง๋ค ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ถ๋ถ์์ ์ฑ๋ฅ์ ๋จ์ด๋จ๋ฆด ์ ์์ ๊ฒ์ด๋ผ๋ ๊ณ ๋ฏผ์ด ์์์ต๋๋ค.
const mutableCopyChecklist = useMemo(
() => deepCopy(allChecklists),
[allChecklists],
);
const updateChecklistChanges = useCallback(
(newChecklist: NewChecklist) => {
if (!mutableCopyChecklist) {
return;
}
const weekNumber = newChecklist.weekNumber;
const index = mutableCopyChecklist[weekNumber].findIndex(
checklist => checklist.id === newChecklist.id,
);
if (index < 0) {
return;
}
mutableCopyChecklist[weekNumber][index] = newChecklist;
setAllChecklists(mutableCopyChecklist);
},
[mutableCopyChecklist],
);- ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ. ๊ฐ๊ฒฐํจ๊ณผ ํ์ฅ์ฑ
๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์์ ๋ณต์ฌ๋ฅผ ์ค์ฒฉ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๋ ๊ฐ๋จํ๊ฒ ๋ง๋ค๊ณ ์ ํ์ต๋๋ค.
ํ์ฌ์ ์ฒดํฌ๋ฆฌ์คํธ ๊ตฌ์กฐ์์๋ 2๋จ๊ณ ์ ๋์ ๊น์ด์ด๊ธฐ ๋๋ฌธ์ ์์๋ณต์ฌ ๋ํ ์ ์ ํ ๊ฒ์ผ๋ก ํ๋จํ์ต๋๋ค.
ํ์ง๋ง ์ถ๊ฐ์ ์ธ ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๊น์ด๊ฐ ๊น์ด์ง๋ ์๊ตฌ์ฌํญ์ ์ ์ ํ๊ฒ ๋์ฒํ๊ธฐ ์ด๋ ค์ธ ์ ์๊ณ ,
์์ฑ, ์ญ์ , ์์ ๋ฑ ๋ฐ์ดํฐ๋ฅผ ์กฐ์ํ๋ ๋ก์ง์ ์ค๋ณต๋ ๋ด๋ถ ๋ก์ง์ด ์ฆ๊ฐํ๋ ๋จ์ ์ด ์์์ต๋๋ค.
const updateChecklistChanges = useCallback((newChecklist: NewChecklist) => {
const weekNumber = newChecklist.weekNumber;
setAllChecklists(prev => {
if (!prev) {
return {};
}
const mutable = {
...prev,
[weekNumber]: [...prev[weekNumber]],
};
const index = prev[weekNumber].findIndex(
checklist => checklist.id === newChecklist.id,
);
if (index < 0) {
return prev;
}
mutable[weekNumber][index] = newChecklist;
return mutable;
});
}, []);- ์ธ ๋ฒ์งธ ๋ฐฉ๋ฒ. ๊ฐ๊ฒฐํจ๊ณผ ํ์ฅ์ฑ
2๋ฒ์งธ์๋ ๋ฌ๋ฆฌ ์ฝ๋ ์์ด ์ค์ด๋ค์๊ณ , ๋ถ๋ณ์ฑ์ ์ ์งํ๋ฉด์๋ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์ ์งํ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ณด์์ต๋๋ค.
ํ์ง๋ง ์ด ์ญ์ 2๋ฒ์งธ ๋ฐฉ๋ฒ์ด ๊ฐ๋ ํ์ฅ์ฑ ๋ถ์กฑ๊ณผ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉ ํ ๋ ์ค๋ณต ๋ก์ง์ด๋ผ๋ ๋จ์ ์ด ์์์ผ๋ฉฐ,
์ฝ๋์ ์์ ์ค์ด๋ค์์ง๋ง ์ฃผ๊ด์ ๊ฐ๋ ์ฑ์ด ์๋์ ์ผ๋ก ๋ฎ์์ก์ต๋๋ค.
ํนํ, ์ ๋ฐ์ดํธ๋ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ฐพ์๋ด๋ ๋ก์ง์ด ํ ์ค์ ๋ชจ๋ ํํ๋์ด ์์ด ์ฝ๋๋ฅผ ์ ์ง๋ณด์ ํ ๋ ๋ํดํ ์ ์๋ค๊ณ ์๊ฐํ์ต๋๋ค.
const updateChecklistChanges = useCallback((newChecklist: NewChecklist) => {
setAllChecklists(prev => {
if (!prev) {
return {};
}
const weekNumber = newChecklist.weekNumber;
const mutableChecklists = {
...prev,
[weekNumber]: prev[weekNumber]?.map(item =>
item.id === newChecklist.id ? newChecklist : item
),
};
return mutableChecklists;
});
}, []);์ ๋ ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ ํํ์์ต๋๋ค.
๊ทธ ์ด์ ๋ ๋ถ๋ณ์ฑ ์ ์ง์ ํ์ฅ์ฑ ์ธก๋ฉด์์์ ์์ ์ฑ์ ์ค์ํ๊ธฐ ๋๋ฌธ์
๋๋ค.
-
๋ถ๋ณ์ฑ์ ํ๋ณด
useMemo์ ํจ๊ป ๊น์ ๋ณต์ฌ๋ฅผ ํตํด ๋ถ๋ณ์ฑ์ ํ๋ณดํ๋๋ฐ ์ฃผ๋ ฅํ์ต๋๋ค. -
ํ์ฅ์ฑ์ ๋ํ ๊ณ ๋ ค
ํ์ฌ ์ฒดํฌ๋ฆฌ์คํธ๋ 2๋จ๊ณ์ ๊ฐ์ฒด์ด์ง๋ง, ์ถ๊ฐ์ ์ธ ํ๋กํผํฐ๋ ์๊ตฌ์ฌํญ์ด ๊ณ ๋ํ๋ ๋์ ํ์ฅ์ฑ์ ์ด์ ์ ๋ง์ถ์์ต๋๋ค. -
Memoization์ ํตํ ์ฑ๋ฅ ์ต์ ํ
useMemo๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ์ ํ์ต๋๋ค.
์ด๋ ๋ถ๋ณ์ฑ์ ์ ์งํ๋ฉด์๋, ์ฑ๋ฅ์ ์ธ ์ด์ ์ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก ํ๋จํ์ต๋๋ค. -
์ฝ๋ ๊ฐ๋ ์ฑ์ ์ ์ง
์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ๋ถ๋ณ์ฑ์ ํ์คํ ์ ์งํ๋ฉด์๋ ๊ฐ๋ ์ฑ์ ์ต๋ํ ์ ์งํ๋ ค๊ณ ๋ ธ๋ ฅํ์ต๋๋ค.
๊น์ ๋ณต์ฌ ๋ก์ง์ด ์ถ๊ฐ๋์ด ์ฝ๋๊ฐ ๋ณต์กํด์ง๋ ๋จ์ ์ด ์์ง๋ง, ์ฝ๋๋ฅผ ์ดํดํ๋๋ฐ ์์ด์ ๋ช ํ์ฑ์ ์ ์งํ๋ ๊ฒ์ด ์ค์ํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
์ฌ์ฉ์๋ค์ ๋ค์ํ ๊ธฐ๊ธฐ์์ ์ผ๊ด๋ ๋ฐ ํฅ์๋ ์ฑ ๊ฒฝํ์ ๋๋ฆด ์ ์๋๋ก ๊ณ ๋ฏผํ์ต๋๋ค.
๋ค์ํ iPhone ๋๋ฐ์ด์ค ํฌ๊ธฐ๋ฅผ ๊ณ ๋ คํ์ฌ ์ฒดํฌ๋ฆฌ์คํธ ํญ ํฌ๊ธฐ๋ฅผ ์ก์์ต๋๋ค.
๊ฐ๋จํ ์์ฐ ์์์ ์ฒจ๋ถํฉ๋๋ค.
์ฌ์ฉํ 3rd ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- react-native-reanimated@3
- react-native-svg, react-native-svg-transformer
- react-navigation@6
