Upgrade to Pro — share decks privately, control downloads, hide ads and more …

선언형 UI를 학습할 때 알아둬야하는 키ᄋ...

HyunWoo Lee
September 28, 2024

선언형 UI를 학습할 때 알아둬야하는 키워드들

Devfest Android in Korea 2024에서 진행한 선언형 UI를 학습할 때 알아둬야하는 키워드들의 Speaker Deck입니다.

HyunWoo Lee

September 28, 2024
Tweet

More Decks by HyunWoo Lee

Other Decks in Programming

Transcript

  1. Essesntial concepts to know when learning Declarative UI Korea Android

    HyunWoo Lee Android/React Native Developer, Viva Republica(Toss) Organizer, Kotlin User Groups Seoul/GDG Songdo
  2. ࢶ঱ഋ UIܳ ೟णೡ ٸ ঌইىঠೞח ఃਕٜ٘ Korea Android ੉അ਋ Android/React

    Native Developer, Viva Republica(Toss) Organizer, Kotlin User Groups Seoul/GDG Songdo
  3. ੉ߣ ߊ಴ח DroidKaigi 2024ীࢲ ૓೯ೠ ߊ಴ܳ ೠҴয۽ ׮द Recapೠ Ѫ੐ਸ

    ঌ۰٘݀פ׮ https://www.youtube.com/watch?v=txaf9F0d-aE
  4. fun sum(): Int { var result = 0 for(i in

    1 . . 10) { sum += i } return result }
  5. ੹୓ח ࠗ࠙੄ ೤੉׮ • ೞա੄ ചݶ, ೞա੄ ஹನք౟ח ஹನ ք౟੄

    ઑ೤੉׮. • Custom View ٜ݅ӝ ਤ೧ ࢚ࣘ ೞ؍ җѢ੄ दрٜ਷ ੉ઁ Ӓ݅
  6. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    Column() { Row() { Tags(tags = tags) Like(isLike = isLike) } SectionTitle(title = title) } }
  7. struct TimeTableItem: View { var tags: [String] var title: String

    var isLike: Bool var body: some View { VStack { HStack { Tags(tags: tags) Like(isLike: isLike) } SectionTitle(title: title) } } }
  8. function TimeTableItem({ tags, title, isLike }: Props) { return (

    <View> <View style= { { flexDirection: ‘row’ } } > <Tags tags={tags} / > <Like isLike={isLike} / > < / View> <SectionTitle title=title / > < / View> ) }
  9. ೒ۖಬ ߹ ର੉੼ • ೣࣻ ஹನք౟ vs ௿ېझ ஹನք౟ •

    ೣࣻ ஹನք౟ח ಁ۞޷ఠ ഑਷ ೣࣻ ղ ߸ࣻ۽ UI ҙܻ • Jetpack Compose, React • ௿ېझ ஹನք౟ח ࢤࢿ੗, ݯߡ ߸ࣻ۽ UI ҙܻ • SwiftUI, Flutter, (җѢ) React
  10. Flex(FlexBox) • CSSীࢲ ࢎਊೞח ۨ੉ইਓ ߓ஖ ӝߨ ઺ ೞա •

    ࢶ঱ഋ UI ೒ۖಬ ؀׮ࣻо ੉ܳ ଻ఖೣ • ConstraintLayoutҗח ׮ܲ ߑधਵ۽ UIܳ ੘ࢿ೧ঠ ೞפ CSS Flex ѐ֛ਸ ޷ܻ ೠߣঀ ঌҊ оݶ ઑӘ ؊ औѱ UI ੘ࢿਸ ೡ ࣻ ੓ णפ׮.
  11. State৬ UI • Stateܳ ഝਊೞৈ UIܳ ߸҃೧ࠁ੗ • Stateח ചݶਸ

    ߸҃दఆ ࣻ ੓ח ؘ੉ఠ੉׮. • Stateо ߸҃੉ غݶ ചݶ਷ ߸҃ػ׮. • ੉ ߸҃ਸ ೒ۖಬ݃׮ ࢚੉ೞѱ ࠗܰ૑݅ ؀ѐ ܻ۪؊݂(re- rendering)੉ۄ ೠ׮.
  12. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    Column() { Row() { Tags(tags = tags) Like(isLike = isLike) } SectionTitle(title = title) } }
  13. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    var isLike by remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickable { isLike = !isLike } ) } SectionTitle(title = title) } }
  14. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    var (isLike, setIsLike) = remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickable { setIsLike(!isLike) } ) } SectionTitle(title = title) } }
  15. function TimeTableItem({ tags, title, isLike }: Props) { const [isLike,

    setIsLike] = useState(false); return ( <View> <View style= { { flexDirection: ‘row’ } } > <Tags tags={tags} / > <Like isLike={isLike} onLikeChange={() = > setIsLike(!isLike)} / > < / View> <SectionTitle title=title / > < / View> ) }
  16. struct TimeTableItem: View { var tags: [String] var title: String

    @State var isLike: Bool var body: some View { VStack { HStack { Tags(tags: tags) Like(isLike: isLike) } SectionTitle(title: title) } } } struct Like: View { @Binding var isLike: Bool var body: some View { if isLike { Image(.icFavoriteFill) } else { Image( .icFavoriteOutline ) } } }
  17. Re-rendering • ࢶ঱ഋ UI ೐ۨ੐ਕ௼ীࢲח ࢚కܳ ߄Բח ઱୓о ѐߊ੗ীࢲ दझ

    మ ೒ۖಬী ҳগ߉૑ ঋҊ ࠺तೠ ߑधਸ ഝਊ • п ೒ۖಬ݃׮ ܻ۪؊݂ ݫழפ્੉ ࢓૟ ׮ܴ • উ٘۽੉٘: (Smart) Recomposition • React(React Native): Re-rendering/Virtual • SwiftUI: ViewGroup੄ idী ੄ೠ সؘ੉౟
  18. UI সؘ੉౟ ੉৻੄ ӝמٜ਷? • ૑Әө૑ ׮ܘ Ѫٜ਷ UI ۪؊݂ী

    ҙ۲ػ ӝמٜ੉঻਺ • ݅ডী ചݶী ૓ੑೞ੗݃੗ ࢲߡాनਸ ೧ঠೠ׮Ѣա ӂೠ ਃ୒ਸ ೧ঠೠ׮ݶ?
  19. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    var isLike by remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickable { isLike = !isLike } ) } SectionTitle(title = title) } }
  20. fun TimeTableItem( tags: List<String>, title: String, isLike: Boolean ) {

    var isLike by remember { mutableStateOf(false) } LaunchedEffect(isLike) { toast(if (isLike) “੷੢೮णפ׮!” else “׮द ੷੢ೞ۰ݶ ੷੢ ߡౡਸ ׂ۞઱ࣁਃ!”) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickable { isLike = !isLike } ) } SectionTitle(title = title)
  21. function TimeTableItem({ tags, title, isLike }: Props) { const [isLike,

    setIsLike] = useState(false); return ( <View> <View style= { { flexDirection: ‘row’ } } > <Tags tags={tags} / > <Like isLike={isLike} onLikeChange={() = > setIsLike(!isLike)} / > < / View> <SectionTitle title=title / > < / View> ) }
  22. function TimeTableItem({ tags, title, isLike }: Props) { const [isLike,

    setIsLike] = useState(false); useEffect(() = > { alert(`${isLike ? ‘੷੢೮णפ׮’ : ‘׮द ੷੢ೞ۰ݶ ੷੢ ߡౡਸ ׂ۞઱ࣁਃ!’) }, [isLike]) return ( <View> <View style= { { flexDirection: ‘row’ } } > <Tags tags={tags} / > <Like isLike={isLike} onLikeChange={() = > setIsLike(!isLike)} / > < / View> <SectionTitle title=title / > < / View>
  23. function TimeTableItem({ tags, title, isLike }: Props) { useEffect( ()

    = > { / / ਗೞח Side Effect ୊ܻ }, [isLike] ) } Dependency Array • ࠼ ߓৌ: ചݶী ૓ੑೠ ੉റ ࢎ੉٘ ੉ಖ౟ ୊ܻ • ࠺য੓૑ ঋ਷ ߓৌ(ਃࣗ A, B, etc): Aա B੄ ч੉ ߸҃غח ҃਋ী Side E ff ect ୊ܻ • ߓৌਸ ૘য֍૑ ঋ਷ ҃਋: ݒ Re-rendering݃׮ ࢎ੉٘ ੉ಖ౟ ୊ܻ
  24. function TimeTableItem({ tags, title, isLike }: Props) { useEffect( ()

    = > { () = > { / * Clean Up Function * / } }, [isLike] ) } Clean Up Function • ചݶ੉ ӝӝীࢲ ߩযզ ٸ ೧׼ ௒ߔ উী ٜয੓ח ۽૒੉ प೯ؽ
  25. LaunchedEffect(key1, key2) { / / Side Effect } LaunchedE ff

    ect • (key1, key2, …etc):useE ff ect੄ Dependency Array৬ э਷ ৉ೡ • ૊ key1, key2о ߸҃غݶ ೧׼ Side E ff ect प೯ • ݅ড ചݶী ૓ੑೞ੗݃੗ Side E ff ectܳ प ೯ೞҊ र׮ݶ? • keyী ࠛ߸чਸ ֍ח׮ • true, false, Unit, etc • trailing lambdaח suspend function
  26. DisposableEffect(key1, key2) { / / Side Effect onDispose { }

    } DisposableE ff ect • onDispose: useE ff ect੄ Clean Up Functionҗ زੌೠ ৉ೡ • trailing lambdaо non-suspend function
  27. SideEffect { / / Side Effect } SideE ff ect

    • SideE ff ectо ݒ Recomposition݃׮ प೯ؽ
  28. VStack { Text(text).padding() .onAppear { } .onDisappear { } .onChange(of:

    text) { newValue in print("Text changed to: \(newValue)”) } Button("Change Text") { text = "Text changed \(count + 1) times" } }
  29. ࢚క ҕਬ ߑߨۿ • ੗ध ҅க੉ Өয૕ࣻ۾ ࠗݽ੄ ࢚కܳ ੗ध

    ҅கীѱ ੹׳ೞҊ ҕ ਬೞӝо ҭ੢൤ য۰ਕ૗ • ੗ध ஹನք౟ٜীѱ ؊ ੗ਬ܂ѱ ࢚కܳ ҕਬೡ ࣻ ੓ח ߑध਷ হ ਸө?
  30. Props Drilling • ੗ध ஹನք౟ীѱ ࢚కܳ ੹׳ೞӝ ਤ೧ ࢤࢿ੗ա ೣࣻ

    ಁ۞޷ఠ ܳ ഝਊೞৈ ੹׳ೞח Ѫ • Composeীࢲ ӂ੢ೞח ߑध • https://developer.android.com/develop/ui/compose/ state-hoisting
  31. Local Scoped Data • ࠗݽ ஹನք౟ীࢲ ੗ध҅கীѱ ࢚కܳ ੹׳ೡ ࣻ

    ੓ח Provider ܳ ࢸ੿ೞҊ ੗ध ஹನք౟ীࢲח ੉ܳ ࢎਊೡ ࣻ ੓ب۾ ೞח ߑߨ • Reactীࢲ ઱۽ ࢎਊೞח ߑߨۿ • https://react.dev/learn/passing-data-deeply-with- context#replace-prop-drilling-with-context
  32. Local Scoped Data • Android - CompositionLocal • iOS -

    EnvironmentValues • React(React Native) - Context
  33. / / Contextܳ ੿੄ೠ׮. const ThemeContext = createContext() / /

    Context Providerܳ ੿੄ೠ׮. const ThemeProvider = ({ children }) = > { const [theme, setTheme] = useState(‘light’) return ( <ThemeContext.Provider value= { { theme } } > {children} < / ThemeContext.Provider> ) }
  34. / / Contextܳ ࢎਊೞח ஹನք౟ীࢲ useContext ೣࣻܳ ഝਊೠ׮. function Component()

    { const { theme } = useContext() return <Text>{`Current Theme is ${theme}`} < / Text> } / / Contextܳ ࢎਊೞҊ੗ ೞח ஹನք౟ܳ Provider۽ хऴ׮. function Component() { return <ThemeProvider><Component / > < / ThemeProvider> }
  35. / / CompositionLocal ੿੄ val LocalColor = staticCompositionLocalOf<Colors> { Error(“No

    Colors”) } / / LocalColorܳ ઁҕೞח CompositionLocalProviderܳ ࢸ੿ೠ׮. CompositionLocalProvider( LocalColor provides appColors() ) { / / The components within this block can use LocalColors }
  36. CompositionLocalProvider( LocalColor provides appColors() ) { DroidKnightsRow() } fun DroidKnightsRow()

    { val colors = LocalColors.current Row(modifier = Modifier.background(color.background)) { } }
  37. CompositionLocal • staticCompositionLocalOf • CompositionLocal۽ ઁҕೞח ч੉ ࠛ߸ೠ׮Ҋ о੿ೞҊ CompositionLocal

    чਸ ઁҕೞח җ੿ਸ ୭੸ച • ч੉ ߸҃੉ ػ׮ݶ ੹୓ ചݶী recomposition ੌযթ • compositionLocalOf • CompositionLocal੉ ߸҃غݶ ੉ чਸ ੍ח ஹನք౟݅ Recomposition੉ ੌযթ
  38. / / EnvironmentValues۽ ղ۰ࠁյ ч੄ keyܳ ੿੄ struct ThemeKey: EnvironmentKey

    { static let isDarkMode: Bool = false } / / EnvironmentValues੄ ഛ੢ೣࣻܳ ੿੄೧ࢲ / / ਗೞח EnvironmentValuesܳ ࡳইৢ ࣻ ੓ѱ ࢸ੿(setter, getter) extension EnvironmentValues { var isDarkMode: Bool { get { self[ThemeKey.self] } set { self[ThemeKey.self] = newValue } } }
  39. / / EnvironmentValuesܳ .environment۽ ઁҕ struct ContentView: View { var

    body: some View { VStack { Child() .environment(\.isDarkMode, true) } } } / / EnvironmentValuesܳ ࢎਊೞח ੗ध ஹನք౟ח ݯߡ߸ࣻ۽ ੿੄ struct Child: View { @Environment(\.isDarkMode) var isDarkMode }
  40. Single Store ӝ߈ Architecture੄ ਬ೯ • Android, iOS ন ೒ۖಬ

    ݽف അ੤ Single Store ӝ߈ Architecureо ൜೯ ೞҊ ੓਺ • Androidীࢲח MVI • iOSীࢲח TCA(The Composable Architecture) • ੉۠ ইఃఫ୊о ਬ೯੉ ػѱ ਋োੌөਃ?
  41. Reduxח ৵ ۨѢद۽ ੋधೞҊ ੓חо? • ੺؀੸ੋ दр੉ ݆੉ ૑լ਺

    (Initiial Commit 2015֙) • Boilerplate ௏٘੄ ন੉ ࢚׼൤ ݆਺ • API ਃ୒ ਽׹чী ؀ೠ ࢚క ੿੄о ࠗ੤ -> ӏѺചػ ߑध੉ হ਺ • ࠂ੟ೠ ਬ੷ दաܻয়ী ؀਽ೞӝ ਤ೧ࢲ ݆ࣻ਷ Ҋ޹җ ௏٘ ੘ࢿ
  42. ࠺زӝ ࢚క ҙܻ੄ ੿੄ - SWR • stale-while-revalidate • ߔӒۄ਍٘ীࢲ

    ࢜۽਍ ؘ੉ఠܳ ߉ইয়ח زউ நदػ ؘ੉ఠܳ ࢎਊೡ ࣻ ੓ח ૑੿ػ ӝрਸ ੄޷ • HTTP ೻؊ীࢲ ੉ ೐۽ಌ౭о ੿੄غয੓૑݅ ؀ѐ Cached ؘ ੉ఠ৬ ࢜۽਍ ؘ੉ఠ ਃ୒ਸ ҙܻೞח ߑߨۿਸ ੄޷ೣ
  43. ࠺زӝ ࢚క ҙܻ • ࠺زӝ ࢚కҙܻח ࢲߡীࢲ ߉ইয়ח “࠺زӝ” ؘ੉ఠܳ

    ҙܻೞח ߑߨ ۿ • ݒߣ ചݶী ૓ੑೡ ٸ݃׮ ࢜۽਍ ؘ੉ఠܳ ߉ই৬ঠೡө? • ੌ੿ ӝр ੉ղী ചݶী ੤૓ੑೠ׮ݶ ӝઓ ؘ੉ఠܳ ੤ഝਊ೧ب ௾ ର੉о হ਺ • நदػ ؘ੉ఠܳ ࢎਊೞח ҃਋ ҭ੢൤ ࡅܰѱ ചݶਸ ҳࢿೡ ࣻ ੓਺
  44. ࠺زӝ ࢚క ҙܻ • ৘द: Optimistic Update • ࢲߡо ࢿҕ੸ਵ۽

    ਽׹ೡ Ѫ੉ۄ о੿ೞҊ UIܳ ޷ܻ ߸҃ೞҊ पಁೞݶ ܀ߔਸ ೞח ӝߨ
  45. React Query • SWR ੿଼ਸ ӝ߈ਵ۽ ࠺زӝ ࢚కܳ ҙܻೡ ࣻ

    ੓ח ۄ੉࠳۞ܻ • ࠺زӝ ࢚క ҙܻܳ ਊ੉ೞѱ ೞח ӝמٜ੉ ઁҕؽ • ۽٬/ࢿҕ/पಁ ١ ࠺زӝ ࢚క੄ ӏѺ ಴ળച • ࠺زӝ ؘ੉ఠ நय • stale(ؘ੉ఠо য়ېؼ ٸ) ੗زਵ۽ நद ؘ੉ఠ Ү୓ • ъઁ நद ޖബച
  46. ௏٘ ৘द function Example() { const {data, isLoading, isError, refetch}

    = useQuery(‘index’, fetchData, { staleTime: 5000 }); if (isLoading) return <Text>Loading… < / Text> if (isError) return <View><ErrorIamge / > < / View> const invalidate = () = > { queryClient.invalidateQueries(‘index’) } return <View>{data}<View onClick={invalidate}>Invalidate < / View> < / View> }
  47. ௏٘ ৘द function Example() { const {data, isLoading, isError, refetch}

    = useQuery(‘index’, fetchData, { staleTime: 5000 }); if (isLoading) return <Text>Loading… < / Text> if (isError) return <View><ErrorIamge / > < / View> const invalidate = () = > { queryClient.invalidateQueries(‘index’) } return <View>{data}<View onClick={invalidate}>Invalidate < / View> < / View> }
  48. ௏٘ ৘द function Example() { const {data, isLoading, isError, refetch}

    = useQuery(‘index’, fetchData, { staleTime: 5000 }); if (isLoading) return <Text>Loading… < / Text> if (isError) return <View><ErrorIamge / > < / View> const invalidate = () = > { queryClient.invalidateQueries(‘index’) } return <View>{data}<View onClick={invalidate}>Invalidate < / View> < / View> }
  49. ௏٘ ৘द function Example() { const {data, isLoading, isError, refetch}

    = useQuery(‘index’, fetchData, { staleTime: 5000 }); if (isLoading) return <Text>Loading… < / Text> if (isError) return <View><ErrorIamge / > < / View> const invalidate = () = > { queryClient.invalidateQueries(‘index’) } return <View>{data}<View onClick={invalidate}>Invalidate < / View> < / View> }