{"id":27741704,"url":"https://github.com/sangbooom/tirrilee","last_synced_at":"2026-04-29T17:02:26.462Z","repository":{"id":123098643,"uuid":"353278563","full_name":"sangbooom/tirrilee","owner":"sangbooom","description":"과제테스트","archived":false,"fork":false,"pushed_at":"2021-04-22T12:12:22.000Z","size":22939,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-28T16:55:32.025Z","etag":null,"topics":["emotion","firebase-auth","firebase-realtime-database","react-nativation","react-native","redux-toolkit","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sangbooom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-03-31T08:13:55.000Z","updated_at":"2023-03-11T13:54:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"d411ce58-5804-4ba7-a7fc-9d29997a393c","html_url":"https://github.com/sangbooom/tirrilee","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sangbooom/tirrilee","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangbooom%2Ftirrilee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangbooom%2Ftirrilee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangbooom%2Ftirrilee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangbooom%2Ftirrilee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sangbooom","download_url":"https://codeload.github.com/sangbooom/tirrilee/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangbooom%2Ftirrilee/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32435122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T13:34:34.882Z","status":"ssl_error","status_checked_at":"2026-04-29T13:34:29.830Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emotion","firebase-auth","firebase-realtime-database","react-nativation","react-native","redux-toolkit","typescript"],"created_at":"2025-04-28T16:26:14.128Z","updated_at":"2026-04-29T17:02:26.455Z","avatar_url":"https://github.com/sangbooom.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🧾 Assignment Test\n\u003e 3/31, 4/2, 4/3 진행\n\n## 📚 사용 기술 스택\n- react-native (typescript, 함수형 컴포넌트), react-navigation\n- redux-toolkit, emotion.js\n- firbase (auth, realtime database)\n\n\n## 💭 처음 제플린을 보고 생각한 구조\n\n### ui를 어떻게 구성해야 할까\n\n로그인, 회원가입은 스택네비게이션, 홈, 상품목록, 등록페이지, 검색, 마이페이지는 바텀탭 네비게이션, 그 안에 페이지들은 중첩된 스택네비게이션으로 처리하려고 했다.\n\n### 데이터 처리는 어떻게 할까\n\n로그인, 회원가입, 자동로그인 등 인증에 관련된 것은 간단하게 파이어베이스를 사용해서 해결하고, 상품에 대한 데이터는 파이어베이스의 리얼타임 데이터베이스를 사용하거나, 컴포넌트 state에 목업데이터를 넣고 사용하면 되겠다.\n\n### api 사용 여부\n\nredux-toolkit과 Async-Storage or keyChain이 필요하다 생각했다. rtk는 상태관리 위해서 storage는 사용자 토큰 저장을 위해서 (로그인후 통신할때 사용자인지 확인하기 위해, 자동로그인, 로그아웃, 회원탈퇴 용도)\n\napi는 만들 필요 없고 사용자의 이름, 이메일, 사진, 등록상품, 찜 목록 그리고 전체 등록상품를 저장 할 저장소만 있으면 될 것 같았다. 스토리지에 넣을지 데이터베이스에 저장할지 고민한 결과 파이어베이스 리얼타임 데이터베이스에 넣을 결정을 했다. 안전하고 인증 부분은 편하기 때문이다.   \n\n# 📅 3/31\n\n### 진행 상황\n- 서비스 이해하기, 간단한 구조 설계,  프로젝트에 타입스크립트 적용\n\n- 라이브러리 (react-navigation, redux-toolkit, emotion, react-native-firebase) 설치\n\n- 구글 파이어베이스와 프로젝트 연동하기 ,네비게이션 적용\n\ncss는 빠르고 편하게 개발을 하기 위해 짧은건 inline-style로 적용시켰음.    \nreact에서는 emotion이 nested selector도 되고 굉장히 생산성에 도움 됐는데 react native에서는 nested selector가 적용이 안된다고 한다..   \n[https://github.com/emotion-js/emotion/issues/1862](https://github.com/emotion-js/emotion/issues/1862)    \n\n마진과 패딩은 아래적용으로 통일시켰다.  \n\n사실 css 재사용 컴포넌트를 위해 나누려고 시도를 많이 했다가 nested selector가 적용이 안되는 것을 알고나서는 공통 컴포넌트의 css만 재사용했다.  \n\nimage를 불러오기 위해 \n\n```jsx\n\u003cImage source={require('../../assets/image/header-logo@2x.png')}/\u003e\n\n// error: Error: Unable to resolve module ...\n```\n\n위와 같이 작성하면 에러가 난다.그 이유는 이미지이름에 @가 들어가 있어서였다.\n\n이미지 이름을 header-logo-2x로 바꾸면 정상작동한다. \n\n디자인이 web디자인이여서 RN에 css를 적용하면 react-native에 적용되지않는 css때문에 시간이 오래걸렸다. font-stretch: normal, box-shadow, letter-spacing: normal 등 RN에 맞게 고쳐야하는데 일단은 다 주석 처리했다.\n\n# 📅 4/2\n\n### firebase auth 적용\n\nfirebase를 사용하여 이메일 비밀번호를 가지고서 빠르게 로그인, 자동로그인, 회원가입을 할 수 있어서 도입했다. 회원 이름이나 프로필 url, 상품 목록은 firebase realtime database에 넣으려고 한다.\n\n### redux toolkit 도입\n\n사실 상태관리를 안해도 될 정도의 규모일 것 같아서 처음에는 적용 시키지 않았다가 조금씩 커지는 규모에 redux를 사용해서 상품정도는 redux로 관리하자고 생각했다. (결과적으론 redux를 적극 활용하지 않았음)\n\nredux보다는 redux-toolkit을 사용하여 더 직관적이고 짧은 코드로 유지보수 용이하게 했다. redux-toolkit에 익숙하지않아 시간이 오래 걸렸다. 이전 프로젝트들은 ducks패턴에 컨테이너컴포넌트, 프레젠테이셔널 컴포넌트를 나눠 관심사를 분리시키고 props로 사용할 인자들을 일일히 다 넘겼었는데 redux-toolkit을 도입해보니까 훨씬 코드가 짧아지고 사용하기 편해졌다.\n\n### 특이사항\n\n```jsx\n\u003cImage source={require('../../assets/image/invalid-name.png')} /\u003e\n\u003cImage source={require('../../assets/image/id-pw.png')} /\u003e\n\u003cImage source={require('../../assets/image/oo.png')} /\u003e\n```\n\n제플린에 회원가입과 ID/PW 찾기가 이미지로 되어있길래 이미지로 하는 줄 알고 적용했다가 다시 고쳤다.\n\nbox-shadow를 적용시키기 위해\n\n```jsx\nstyle={{\n  shadowColor: '#red',\n  shadowOffset: {\n    width: 0,\n    height: 2,\n  },\n  shadowOpacity: 0.25,\n  shadowRadius: 3.84,\n  elevation: 5,\n}}\n```\n\n등 많은 방법을 적용시켜봤지만 잘 되지 않아 넘어갔다.\n\n# 📅 4/3\n\n### 구현 시나리오\n파이어베이스 리얼타임 데이터베이스에 등록한 상품을 저장시킨다. 저장시킨 순간 상품목록을 업데이트 시키고 사용자들은 실시간으로 등록된 상품을 볼 수 있게 한다.   \n\n상품 목록은 데이터베이스에서 상품데이터를 배열로 가져와 map으로 보여주고, flatList의 스크롤이 바닥에 닿을때 쯤 적당한 갯수의 상품 데이터를 불러오게한다.\n\n\n### 특이사항\n\n```jsx\n\u003cTab.Navigator\n...\nscreenOptions={({route}) =\u003e ({\n  tabBarIcon: ({focused}) =\u003e {\n    let imageName: any;\n    if (route.name === '홈') {\n      imageName = focused ? 'home-focused-3x' : 'home-3x';\n    } else if (route.name === '상품목록') {\n      imageName = focused ? 'product-focused-3x' : 'product-3x';\n    } else if (route.name === '등록하기') {\n      imageName = focused ? 'enroll-3x' : 'enroll-3x';\n    } else if (route.name === '검색') {\n      imageName = focused ? 'search-3x' : 'search-3x';\n    } else if (route.name === '마이페이지') {\n      imageName = focused ? 'mypage-focused-3x' : 'mypage-3x';\n    }\n    return (\n      \u003cImage\n        source={require(`../../assets/image/${imageName}.png`)}\n        style={{width: 32, height: 32}}\n      /\u003e\n    );\n  },\n})}\n```\n\n메인페이지에서 탭바 아이콘을 포커스했을때와 하지않았을 때의 이미지를 다르게 하기 위해 중복되는 코드를 막으려 했으나 이미지에 동적 경로가 설정이 되지않아 그냥 컴포넌트마다 다 따로 붙여줬다\n\n[https://stackoverflow.com/questions/48713580/set-image-name-from-obj-to-source-react-native](https://stackoverflow.com/questions/48713580/set-image-name-from-obj-to-source-react-native)\n\n```jsx\n\u003cTab.Navigator\n...\n\u003cTab.Screen\n name=\"홈\"\n options={{\n  tabBarIcon: ({focused}) =\u003e\n    focused ? (\n      \u003cImage\n        source={require('../../assets/image/home-focused-3x.png')}\n        style={{width: 32, height: 32}}\n      /\u003e\n    ) : (\n      \u003cImage\n        source={require('../../assets/image/home-3x.png')}\n        style={{width: 32, height: 32}}\n      /\u003e\n    ),\n}}\u003e\n```\n\n\u003cbr /\u003e\n\n# 👩🏻‍💻 과제테스트를 하고 느낀점\n\n일단 디자인을 보고 개발을 하니 혼자 해커톤 하는 것 같았다. 스스로 해결해보려고 많이 하다보니 사고의 폭이 넓어진 것 같다.\n\n테스트 디바이스가 삼성 갤럭시 6 (360x640) 이다보니까 화면 크기가 맞지않아 css적으로 그에 맞게 고치다가 시간을 많이썼다. (에뮬레이터를 돌리면 성능이 좋지않아 디바이스로 테스트했음) \n\n개인적으로 css가 가장 어려웠다. 다른 기종에 맞게 반응형 고려, css 네이밍, 규칙 통일하는 연습부터 해야겠다. \n\n설계와 디자인에 대한 고민을 많이하다보니 개발속도가 매우 느렸다. 그래서 많은 기능들을 구현하지 못했다.\n\n\u003cbr /\u003e\n\n# 🏃🏻‍♂️ 이어서 개발하기\n\n## 💭 text가 있는 TextInput 구현\n\n![image](https://user-images.githubusercontent.com/43921054/113576906-59efa500-965b-11eb-84b3-9844d173a3db.png)\n\n위와같은 인풋창 안에 텍스트를 추가하기 위해선 단순하게 '원'이라는 텍스트를 `position:absolute`를 통해 원하는 위치에 두는 방법밖에 생각나지 않았다. \n\n시간을 두고 더 생각해본 결과, 단순히 View 안에 TextInput과 Text가 있는 구조라는 것을 깨달았다..\n\n```jsx\n\u003cView\n  style={css`\n    width: 334px;\n    height: 48px;\n    flex-direction: row;\n    padding: 14px 0 14px 12px;\n    margin-bottom: 24px;\n    border-radius: 8px;\n    border: solid 1px #eaeaea;\n  `}\u003e\n  \u003cTextInput\n    textAlignVertical=\"top\"\n    style={css`\n      width: 288px;\n      height: 20px;\n      padding: 0;\n    `}\n    placeholder=\"가격 입력\"\n    placeholderTextColor=\"#bfbfbf\"\n  /\u003e\n  \u003cText\n    style={css`\n      font-family: 'NotoSansKR-Regular';\n      font-size: 14px;\n      line-height: 20px;\n      text-align: left;\n      color: #000000;\n    `}\u003e\n    원\n  \u003c/Text\u003e\n\u003c/View\u003e\n```\n\n## 💭 Infinity Scroll 구현방법\n\n\u003e 데이터베이스에 20000만개의 상품이 등록 되어있다고 가정한다. flatList에서 1000개씩의 데이터를 꺼내오는 상황이다.\n\n```jsx\nuseEffect(() =\u003e {\n  setLoading(true);\n  database()\n    .ref('상품목록')\n    .on('value', snapshot =\u003e {\n      setProductList(snapshot.val().slice(beginIndex, endIndex));\n      setBeginIndex(prev =\u003e prev + 1000);\n      setEndIndex(prev =\u003e prev + 1000);\n      setLoading(false);\n    });\n}, []);\n```\n\n처음에 `beginIndex`를 시작할 인덱스만큼, `endIndex`는 꺼내올 데이터의 갯수만큼 초기화를 시켜준다.\n\n그 후에 `endIndex - beginIndex + 1`만큼 꺼내온 후에 다음 가져올 데이터의 수만큼 인덱스를 더해준다.\n\n\n```jsx\n\u003cFlatList\n  ...\n  onEndReached={onEndReachedHandler}\n  onEndReachedThreshold={0}\n  ...\n  data={productList}\n  ...\n```\n\n그 후에 스크롤이 임계값 0(스크롤의 끝)에 도달하면 `onEndReachedHandler()` 함수가 트리거 된다. \n\n```jsx\nconst onEndReachedHandler = () =\u003e {\n  setLoading(true);\n  database()\n    .ref('상품목록')\n    .on('value', snapshot =\u003e {\n      if (productList.length \u003c beginIndex) {\n        setLoading(false);\n        return;\n      }\n      setProductList(\n        productList.concat(snapshot.val().slice(beginIndex, endIndex)),\n      );\n      setBeginIndex(prev =\u003e prev + 1000);\n      setEndIndex(prev =\u003e prev + 1000);\n      setLoading(false);\n    });\n};\n```\n\n기존에 state가 가지고있던 데이터에 보여줄 데이터를 더해줘서 다시 화면에 렌더링하게 된다.\n\n![ezgif com-gif-maker (1)](https://user-images.githubusercontent.com/43921054/114666969-d9efcc00-9d39-11eb-829d-f17115c48688.gif)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsangbooom%2Ftirrilee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsangbooom%2Ftirrilee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsangbooom%2Ftirrilee/lists"}