Search

'모바일'에 해당되는 글 90건

  1. 2021.02.15 겹치는 recyclerview 만들기
  2. 2021.01.26 git 에 올라간 파일 이름 확인하기
  3. 2021.01.15 android - Hilt 사용기
  4. 2021.01.10 nodejs + postgresql
  5. 2021.01.10 cannot find module - heroku
  6. 2021.01.07 nodejs + multer 파일 업로드
  7. 2021.01.07 nodejs + s3 upload/get
  8. 2021.01.07 node-schedule-tz
  9. 2020.12.24 express
  10. 2020.12.24 debugger
  11. 2020.12.22 useContext
  12. 2020.12.22 useReducer
  13. 2020.12.21 useEffect
  14. 2020.12.21 useState
  15. 2020.12.20 connect
  16. 2020.12.17 react router
  17. 2020.12.16 redux
  18. 2020.12.14 error: internal/modules/cjs/loader.js:883
  19. 2020.12.13 visual code definition 찾고 돌아가기
  20. 2020.12.13 scss
  21. 2020.12.13 webpack
  22. 2020.12.11 localStorage
  23. 2020.12.11 Props
  24. 2020.12.11 state
  25. 2020.12.06 item decoration
  26. 2020.12.06 babel
  27. 2020.12.06 arrow function
  28. 2020.12.05 Realm
  29. 2020.12.05 CoreData
  30. 2020.12.05 UserDefaults

겹치는 recyclerview 만들기

모바일/안드로이드 2021. 2. 15. 17:44 Posted by 아는 개발자

서비스 개발 하다 보면 위 그림처럼 recyclerview인데 아이템을 겹치는 형태로 만들어야 할 때가 있다. 먼저 쉽게 생각해 볼 수 있는 방법은 ItemDecoration을 이용해 item1을 제외한 item2, item3의 left 오프셋을 왼쪽으로 당겨주는 방법이 있다.

 

rv.addItemDecoration(object: RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val position = parent.getChildAdapterPosition(view)
        if (position != 0) outRect.left = DimensionUtils.dp2px(requireContext(), 10f).toInt() * -1  
    }
})

 

그런데 이렇게 만들면 예상했던 것과 다르게 뒤에 있는 아이템이 앞에 있던 아이템 위로 올라가게 된다. 뒤에 있는 아이템을 우선순위를 높게 쳐서 발생하는 에러다.

 

 

처음에 계획했던 대로 만들려면 recyclerview 에 약간 트릭을 추가해야한다. 사용한 LinearLayoutManager에서 reverseLayout과 stackFronEnd 속성 값을 true로 설정한다. reverLayout을 true로 두면 아이템을 RTL에 맞춰서 오른쪽으로 쌓는 것이고, stackFronEnd는 recyclerview 영역의 끝부분부터 채우는 것이다. item을 역순으로 출력할 것이므로, 맨 앞에 있는 것은 맨 뒤로 가기 때문에 offset 설정 함수도 끝 부분이 이동하도록 바꿔준다.

 

rv.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false).apply {
    reverseLayout = true
    stackFromEnd = true
}

rv.addItemDecoration(object: RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val position = parent.getChildAdapterPosition(view)
        if (position != (adapter?.itemCount?: 0) - 1) {
            outRect.left = DimensionUtils.dp2px(context, 10f).toInt() * -1
        }
    }
})

 

 

위 코드로 설정하면 아래와 같은 그림이 나온다. 예상했던 그림이긴 한데, item 순서가 역순이다.  해결방법은 간단하다. rv의 adapter에 item을 넣을 때 역순으로 넣으면 된다.

 

adapter?.submitItems(it.reversed())

 

결과 이렇게 겹치는 recyclerview 아이템을 볼 수 있다.

 

728x90

git 에 올라간 파일 이름 확인하기

모바일/삽질 기록 2021. 1. 26. 20:07 Posted by 아는 개발자

이전 포스트에서 맥에서 파일의 대소문자 구분을 하지 않는 특성 때문에 크게 한번 삽질한 적이 있었는데 얼마 지나지 않아서 같은 삽질을 반복하고 말았다... 내 로컬 저장소에서는 아무 문제 없이 돌아가는데 리눅스 기반 heroku에서는 파일을 찾지 못하는 에러를 또 보고 말았다.. 부글부글.

 

이번에는 깃허브에서 관리하는 프로젝트도 아니라서 무슨 파일이 잘못됐는지 찾기도 어려웠는데 다행히 명령어 한줄로 긴 삽질을 막을 수 있었다. 깃에 올라간 파일 이름을 리스트로 출력할 수 있다.

 

user@kwony mytrot-admin % git ls-files
.env.development
.env.production
.gitignore
README.md
package.json
public/favicon.ico
public/index.html
public/logo192.png
public/logo512.png
728x90

android - Hilt 사용기

모바일/안드로이드 2021. 1. 15. 14:29 Posted by 아는 개발자

예전에 쓴 Hilt 포스트에선 기존에 사용중인 프로젝트에 Hilt를 쉽게 적용할 수 없어 아쉽다는 점을 다루었다. 그래서 최근에 소소하게 시작한 사이드프로젝트에선 처음부터 Hilt를 도입해서 사용해봤다. 확실히 Dagger에 비해 자유롭고 사용하기가 간편했다. 이번 포스트에서는 어떤점이 좋았는지를 다뤄보고자 한다. 

 

1. private val 변수 형태로 주입 가능.

 

Dagger로 의존성을 주입할 때는 @Inject 어노테이션과 뒤에 lateinit var 을 붙여줘야했다. 그런데 앞으로 바뀌지 않을 변수에 var 형태로 선언하는게 여간 찝찝한게 아니었다. 다행히 Hilt에서는 이런 찝찝함을 해결했다. 생성자의 인자로 추가해 의존성을 주입할 수 있어 값이 변경되지 않은 val 형태로 주입이 가능하다. 아래 코드는 @ViewModelInject 어노테이션을 이용해 module에서 선언된 객체들에 바로 의존성을 주입하는 코드다. private 변수로도 주입이 가능하다.

 

class AssetEditorViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    application: Application,
    private val assetRepository: AssetRepository,
    private val assetTypeRepository: AssetTypeRepository
): AndroidViewModel(application) {

}

@Module
@InstallIn(ApplicationComponent::class)
class DatabaseModule {
    ...

    @Singleton
    @Provides
    fun provideAssetRepository(appDatabase: AppDatabase) = AssetRepository(appDatabase.assetDao())

    @Singleton
    @Provides
    fun provideAssetTypeRepository(appDatabase: AppDatabase) = AssetTypeRepository(appDatabase.assetTypeDao())
}

 

물론 activity, fragment 처럼 생성자를 customize 할 수 없는 클래스도 있다. 이런 경우 기존과 동일하게 lateinit var를 붙인 채로 주입이 가능하다.

 

@AndroidEntryPoint
class MainFragment : BaseFragment(R.layout.fragment_main) {

    @Inject lateinit var assetRepository: AssetRepository

 

2. ViewModel 의존성 주입이 쉽다

 

Dagger에서는 ViewModel 을 공식적으로 지원해주는게 아니어서 별도의 Factory 클래스를 만들어서 주입을 해줘야 했다. 예로 Fragment를 만들면 이 Fragment Module에선 주입할 ViewModel을 팩토리 형태로 만들어줘야하고 ViewModelMap에 따로 등록도 해줘야하고 결과적으로 코드가 너무 늘어나 관리가 어렵다. Hilt에서는 ViewModel 의존성 주입을 공식적으로 지원해주기 시작했다.

 

ViewModel은 @ViewModelInject 어노테이션을 생성자 앞에 붙이고 ViewModel에서 사용하려는 의존성 주입 클래스를 선언만 하면 된다. Activity, Fragment 단에서는 코틀린 delegate 속성인 by viewModels(), by activityViewModels()를 통해 ViewModel을 받으면 평소와 동일하게 사용할 수 있다.

 

@AndroidEntryPoint
class MainActivity : BaseActivity() {
    private val mainViewModel: MainViewModel by viewModels()
}

@AndroidEntryPoint
class AssetsFragment: Fragment(R.layout.fragment_assets) {
    private val mainViewModel: MainViewModel by activityViewModels()
}

class MainViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    application: Application,
    private val accountRepository: AccountRepository,

 

3. Module 만들고 등록 할 필요가 없다.

 

Dagger에서는 어떤 Module을 만들면 Dagger에 등록해주는 Module에다가 추가해야했다. 그래서 열심히 Module을 만들어도 추가하는 작업을 빼먹어으면 런타임시 에러가 수두룩 뜨곤 했었다. 근데 Hilt에서는 따로 추가하는 작업 없이 @InstallIn 어노테이션만 추가해주면 된다. 귀찮고 빼먹기 쉬운 코드를 확 줄일 수 있었다.

 

@Module
@InstallIn(ApplicationComponent::class)
class DatabaseModule {
    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "database")
            .build()
    }

 

이외에도 편리한 점이 더 많을텐데 사이드 프로젝트 규모가 크지 않아서 아직 다 경험하지 못한 것 같다... 앞으로 쓰다가 괜찮으면 추가로 정리해서 올려야지.

728x90

nodejs + postgresql

모바일/nodejs 2021. 1. 10. 13:12 Posted by 아는 개발자

nodejs로 postgresql 데이터베이스를 사용하는 방법. 엄청 간단하다. 

 

먼저 pg 라이브러리를 npm으로 설치한 후

 

npm install pg // pg library install

 

host 주소랑 포트번호 그리고 유저 정보들을 담은 오브젝트를 만든 후 pg client를 생성해 연결을 시켜준다.

 

const dbconfig = {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PW,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
    ssl: {
        rejectUnauthorized: false
    }
}

const client = new pg.Client(dbconfig)

client.connect(err => {
    if (err) {
        console.log('Failed to connect db ' + err)
    } else {
        console.log('Connect to db done!')
    }
})

 

정상적으로 연결이 완료 되면 선언한 pg client 객체를 이용해 db 쿼리를 날린다. 결과 값은 promise의 형태로도 받을 수 있는데 여기선 비동기 콜백을 피하고자 await로 받았다. 쿼리 결과 값은 리턴 객체의 rows 배열에 있으니 얘를 잘 써먹으면 된다.

 

rows() = () => client.query('select * from tb_table')

router.get('/api/v1/rows', async (req, res) => {
    try {
        const rowQuery = await rows();
        const resp = response.Builder.buildOkResponse({
            row: rowQuery.rows.map()
        })

 

 

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

cannot find module - heroku

모바일/삽질 기록 2021. 1. 10. 11:01 Posted by 아는 개발자

파일 이름을 리팩토링 한 후 새롭게 배포를 했더니 heroku에서 파일을 찾을 수 없다는 에러가 발생하게 됐다. 분명 로컬에서는 아무 문제 없이 제대로 돌아가고 있는데 heroku에 deploy하면 file을 찾을 수 없다는 에러가 발생했다.

 

2021-01-10T01:25:18.946380+00:00 app[web.1]: mytrot development mode
2021-01-10T01:25:19.123710+00:00 app[web.1]: internal/modules/cjs/loader.js:883
2021-01-10T01:25:19.123711+00:00 app[web.1]: throw err;
2021-01-10T01:25:19.123712+00:00 app[web.1]: ^
2021-01-10T01:25:19.123712+00:00 app[web.1]:
2021-01-10T01:25:19.123713+00:00 app[web.1]: Error: Cannot find module '../utils/errors'
2021-01-10T01:25:19.123713+00:00 app[web.1]: Require stack:
2021-01-10T01:25:19.123713+00:00 app[web.1]: - /app/lib/middleware/auth-checker.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: - /app/lib/routers/user.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: - /app/index.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: at Function.Modu

 

하지만 아무리 눈 씻고 찾아봐도 내 프로젝트상의 코드에는 문제가 없다. 파일을 임포트 한 파일 명도 문제 없고 파일도 지정된 경로에 있다.

 

 

이럴때는 heroku app 의 터미널을 열어본다. heroku run bash 명령어로 나의 애플리케이션 프로젝트 폴더의 터미널을 열어 볼 수 있다. 문제가 된 파일이 있는 경로로 이동하니까 파일 이름이 Errors.js 라고 돼있었다. Errors.js는 errors.js 전에 설정한 파일 명이다. 파일 이름 변경사항이 heroku 프로젝트에 반영이 되지 않아서 그렇다.

 

user@kwony ~ % heroku run bash -a mytrot-dev
~ $ cd lib/utils/
~/lib/utils $ ls
Errors.js  jwt-utils.js  trot-response.js

 

heroku의 문제라기 보다는 mac + git의 문제라고 보는게 맞다. 문제가 된 파일(Errors.js, errors.js)은 대소문자가 바뀐 것 빼고는 변경 사항이 없는데 mac의 git에서는 이런 파일 이름의 변경사항을 놓치고 있다.

 

파일 이름을 index.js 에서 Index.js로 바꿨는데 git status에서는 변화 없는 것으로 나온다.

 

이럴때는 파일 이름을 대소문자만 바꾸는게 아니라 다른 변경 사항도 추가한다거나, 아니면 파일 이름을 한단계 더 거쳐서 바꾸거나 아니면 git mv 로 일일이 파일 이름을 바꾸면 된다. 번거롭고 실수하기도 쉬운 부분인데.. 왜 이렇게 만들어놨지

 

mv Errors.js errors-temp.js
mv errors-temp.js errors.js

----------------------------------

git mv -f OldFileNameCase newfilenamecase
728x90

nodejs + multer 파일 업로드

모바일/nodejs 2021. 1. 7. 20:07 Posted by 아는 개발자

multer 라는 npm 라이브러리를 사용하면 nodejs로 쉽게 파일 업로드 api를 구축 할 수 있다. multer 라이브러리를 설치하고 내부 함수인 diskStorage 로 저장 받는 파일의 장소와 저장하게될 파일 이름을 설정하자. destination 필드에 파일 다운 받을 경로를 정하고 filename 필드에 다운 받는 파일 이름을 정한다. 그리고 정해둔 값으로 multer 오브젝트를 생성한다.

 

const multer = require('multer')
const path = require('path')

const storage = multer.diskStorage(
    {
        destination: './uploads',
        filename: function (req, file, cb) {
            cb(null, Date.now() + path.extname(file.originalname))
        }
    }
)

const upload = multer( { storage: storage } )

 

api 에서는 마지막에 생성한 multer 오브젝트를 middleware로 넣어주는데 파일 필드로 받을 key 값을 같이 넣어준다. 아래 api에서는 image라는 필드로 정했다. 이렇게 api를 설정하고 post 명령으로 호출하면 uploads 폴더에 파일이 생긴다.

 

router.post('/media/image', upload.single('image'), async(req, res) => {
    try {
        res.status(200).send()
    } catch (error) {
        console.log(error)
        res.status(400).send(error)
    }
})

 

 

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

nodejs + s3 upload/get

모바일/nodejs 2021. 1. 7. 19:56 Posted by 아는 개발자

nodejs로 AWS S3 스토리지에 업로드를 하기 위해선 먼저 S3 access 권한을 갖고 있는 IAM 사용자가 있어야 한다. 이것까지 설명하면 어려우니 아래 사진과 같은 권한을 가진 사용자가 필요하다는 것을 먼저 알아두자. 

 

 

사용자를 만들면 accessKeyId랑 secretAccessKey를 받는다. 이 string 값을 이용해 nodejs의 AWS S3 오브젝트를 만든다

 

const AWS = require('aws-sdk')

const s3 = new AWS.S3({
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
})

 

만든 객체로 s3 고유 함수를 호출 할 수 있다. 업로드의 인자에는 s3 버킷을 식별 할 수 있는 정보인 버킷 이름(Bucket), 권한 정보(ACL), 컨텐츠 타입 (ContentType), 보낼 파일 스트림 (Body), 파일 저장 경로(Key)를 입력한다. 아래 코드는 sync하게 호출하고자 promise() 함수까지 호출 했는데 원래는 비동기 함수라 .then, catch 함수도 호출이 가능하다.

 

const createUploadParam = (filePath) => {
    return {
        'Bucket': process.env.S3_BUCKET_NAME,
        'ACL': 'public-read',
        'ContentType': 'image/' + path.extname(filePath).substring(1),
        'Body': fs.createReadStream(filePath),
        'Key': moment().format("YYYY-MM-DD") + '/' + path.parse(filePath).base
    }
}

exports.uploadImage = async (filePath) => {
    return await s3.upload(createUploadParam(filePath)).promise()
}

 

파일 가져 올 때도 비슷하게 버킷의 정보가 필요하다. 단 이때는 버킷 이름과, 파일 경로만 있으면 된다. headObject 함수로 파일의 유무를 확인 한 후 있으면 getObject 함수로 파일에 대한 stream을 받을 수 있다

 

exports.createReadParam = (filePath) => {
    return {
        'Bucket': process.env.S3_BUCKET_NAME,
        'Key': filePath
    }
}

s.headObject(createReadParam(filePath)).on('success',() =>{
    s.getObject(param).createReadStream().pipe()
})
728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

node-schedule-tz

모바일/nodejs 2021. 1. 7. 19:28 Posted by 아는 개발자

node-cron 이란 라이브러리를 사용하면 몇 시간이나 몇 일을 주기로 특정 작업을 반복해서 실행 할 수 있지만 내가 원하는 시간대에 실행하는 것은 어렵다. 예로 매일 23시 55분에 실행하는 작업을 만드려면 cron 실행을 23시 55분부터 실행하도록 하거나 아니면 1분 주기로 재실행해서 23시 55분이 지났는지 확인해야하는데 이건 번거롭다.

 

이럴때는 node-schedule이란 라이브러리를 이용하면 된다. 이 라이브러리는 특정 작업을 언제 실행할 것인지 설정 할 수 있다. cron과 거의 비슷한데 내가 작업 시간을 설정 할 수 있다는 점에서 다르다. 

 

아래 코드는 매시 42분 마다 실행되는 코드다. 2시 42분, 3시 42분마다 아래 코드가 실행된다.

 

var schedule = require('node-schedule');
 
var j = schedule.scheduleJob('42 * * * *', function(){
  console.log('The answer to life, the universe, and everything!');
});

 

scheduleJob 첫번째 인자에 들어가는 위치별로 분, 시, 일, 월, 요일을 정해줄 수 있다. 모두 포함하고 싶으면 별표를 넣으면 된다.

 

*    *    *    *    *    *
┬    ┬    ┬    ┬    ┬    ┬
│    │    │    │    │    │
│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)
│    │    │    │    └───── month (1 - 12)
│    │    │    └────────── day of month (1 - 31)
│    │    └─────────────── hour (0 - 23)
│    └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)

 

node-schedule-tz 라이브러리는 time zone까지 설정할 수 있다. 그냥 node-schedule 라이브러리를 쓰면 서버 시간에 맞춰지는데 다른 국가에 서버가 있는 경우에는 맞지 않을 경우가 있다. 이때는 node-schedule-tz 라이브러리로 타임존까지 맞춰주자. 아래 코드는 서울 시간 기준으로 매일 23시 59분에 실행되도록 스케줄링 한 예다.

 

const schedule = require('node-schedule-tz')

const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [0, new schedule.Range(0, 6)];
rule.hour = 23;
rule.minute = 59;
rule.tz = 'Asia/Seoul';

module.exports = schedule.scheduleJob(rule, () => {
    console.log('called every 23:59 pm')
})
728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

express

모바일/nodejs 2020. 12. 24. 21:29 Posted by 아는 개발자

nodejs로 서버를 만들 때 유용한 웹 애플리케이션 프레임 워크. 사실상 nodejs의 표준 서버 프레임워크라 봐도 무방하다. 몇몇 함수에 대해서 알아보자.

 

listen 

 

첫번째 인자로 포트 번호를 받고 두번째는 콜백 함수다. 몇번 포트에 서버를 만들 것인지 정하는 함수다.

 

const express = require("express")

const app = express() 

...

app.listen(3000, () => {
    console.log('Server is up on port 3000')
})

 

get, post

 

외부로부터 http GET, POST 요청을 처리 할 수 있다. 콜백함수에서는 요청 인자와 응답 인자를 받으며 응답 인자를 이용해 값을 전달 할 수 있다. 

 

app.get('', (req, res) => {
    res.send('GET request express')
})

app.post('', function (req, res) {
    res.send('POST request to the homepage');
});

 

route 

 

동일한 get, post 주소를 하나로 묶어서 처리하는 것도 가능하다

 

app.route('')
    .get((req, res) => {
        res.send('Hello express')
    })
    .post((req, res) => {
        res.send('POST request to the homepage'); 
    })

 

response method 

 

응답값은 여러가지가 가능하다. 

 

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

debugger

모바일/nodejs 2020. 12. 24. 20:58 Posted by 아는 개발자

nodejs 도 android studio 나 jetbrain 처럼 강력한 디버깅 툴을 지원한다. 이번 포스트에서는 간단한 사용법을 정리해본다. 

 

1. 코드 내에 중단점 넣기 

 

작업을 중단하고 싶은 특정 위치에 debugger 라는 코드를 끼워 넣는다. nodejs 기본 라이브러리기 때문에 별도의 module 추가는 필요 없다.

 

const fs = require('fs')
const chalk = require('chalk')

const addNote = (title, body) => {
    const notes = loadNotes()
    const duplicateNote = notes.find((note) => note.title === title)

    debugger

    if (!duplicateNote) {
        notes.push({

 

2. 디버깅용 명령어 실행 

 

원래 실행하던 명령어 앞에 inspect를 붙여준다. 디버깅용으로 nodejs가 실행된다. 

 

node inspect app.js add --title="Courses" --body="Note.js"

 

3. chrome://inspect 실행 

 

크롬 브라우저에서 chrome://inspect 주소를 쳐보면 그림 처럼 Remote Target 리스트에 현재 실행하고 있는 node js 프로세스가 보인다. inspect 버튼을 클릭해보자.

 

4. 디버깅 시작 

 

Sources 탭에 들어가면 현재 실행하고 있는 코드가 라인별로 나온다. 오른쪽 상단의 more 버튼을 클릭하면 command 창을 열 수 있고 이거로 각 인자의 값을 볼 수도 있으니 조사식으로 적극적으로 이용하자. 또한 콜스택을 통해서 어떤 순서로 작업이 실행되고 있는지 알 수 있다.

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

useContext

모바일/react 2020. 12. 22. 21:09 Posted by 아는 개발자

useContext를 사용하면 하위 컴포넌트에서도 상위 컴포넌트에서 전달하는 값을 공유 받을 수 있다. props를 통해서 전달하지 않고 동일한 Context를 넘겨 받은 인자를 통해서 공유가 가능하다. 이 함수를 이용해 state 값과 이를 업데이트 하는 dispatch 함수를 컴포넌트끼리 공유 할 수 있다.

 

1. Context 만들기 

 

하위 컴포넌트에서 공통적으로 공유할 수 있는 React Context를 만든다.

 

import React from 'react';

const NotesContext = React.createContext()

export { NotesContext as default }

 

2. Context 공유하기 

 

공유를 시작하려는 가장 상위 컴포넌트에서 태그를 씌워준다. value 안에 넣어둔 값들은 하위 컴포넌트에서 접근 할 수 있게 된다.

 

const NoteApp = () => {
  
    const [notes, dispatch] = useReducer(notesReducer, [])
  
    ...
  
    return (
      <NotesContext.Provider value={{ notes, dispatch }}>
        <h1>Notes</h1>
        <NoteList />
        <p>Add note</p>
        <AddNoteForm />
      </NotesContext.Provider>

 

3. 공유 데이터 접근 

 

하위 컴포넌트에서는 useContext 함수와 공통적으로 사용하고 있는 Context를 이용해 공유 데이터에 접근 할 수 있다. 아래 코드를 보면 useContext를 이용해 상위에서 공유하는 notes 인자를 접근하고 jsx로 만드는 형태다.

 

import React, { useContext } from 'react';
import Note from './Note';
import NotesContext from '../context/notes-context';

const NoteList = () => {
    const { notes } = useContext(NotesContext)

    return notes.map((note) => (
        <Note key={note.title} note={note} />
      ))
}

export { NoteList as default }
728x90

'모바일 > react' 카테고리의 다른 글

useContext  (0) 2020.12.22
useReducer  (0) 2020.12.22
useEffect  (0) 2020.12.21
useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17

useReducer

모바일/react 2020. 12. 22. 20:59 Posted by 아는 개발자

useReducer는 useState랑 비슷하나 state 업데이트 작업을 담당하는 reducer를 직접 넣어 줄 수 있다는 점이 다르다. 아래 코드에서 주석으로 처리된 useState 코드는 두번째 인자로 state 값을 업데이트 하는 작업을 일괄 담당했는데, useReducer를 사용하면 커스텀 reducer를 추가할 수 있어 action의 타입에 따라서 다른 행동을 취하도록 할 수 있다. 

 

useReducer도 useState처럼 리턴 값은 배열이며 첫번째 인자는 state 값이고 두번째 인자는 reducer에 액션을 보낼 수 있는 dispatch 함수다. 값을 업데이트 할 때는 dispatch 함수에 action 을 설정해서 업데이트 한다.

 

import React, { useEffect, useReducer } from 'react';
import notesReducer from '../reducers/notes';

const NoteApp = () => {
  
    // const [notes, setNotes] = useState([])
    const [notes, dispatch] = useReducer(notesReducer, [])
    
    
// 

const notesReducer = (state, action) => {
    switch (action.type) {
      case 'POPULATE_NOTES':
        return action.notes
      case 'ADD_NOTE':
        return [
          ...state,
          {
            title: action.title, body: action.body
          }
        ]
      case 'REMOVE_NOTE':
        return state.filter((note) => note.title !== action.title)
      default: 
        return state
    }
}

export { notesReducer as default }

 

아래는 Note 를 추가하고 제거하는 작업의 코드다. useState를 사용할 때는 setNote를 이용해서 바로 업데이트 하는 코드를 넣었다면 useReducer에 넣어둔 Reducer를 이용해 action 과 인자를 전달해서 작업을 넘길 수 있다. 액션을 추가하는 코드는 useReducer에서 받아온 dispatch 함수를 사용한다.

 

const addNote = (e) => {
  e.preventDefault()

  // setNotes([
  //   ...notes, 
  //    { title, body }
  // ])
  dispatch({
    type: 'ADD_NOTE',
    title,
    body
  })
}

const removeNote = (title) => {
  dispatch({
    type: 'REMOVE_NOTE',
    title
  })
  // setNotes(notes.filter((note) => note.title !== title))
}

 

useState는 간단한 상태값을 담당할 때 사용한다면 useReducer는 복잡한 상태 값, 중복되는 코드가 많이 생겨 일괄적으로 관리가 필요할 때 사용하면 좋을 것 같다.

728x90

'모바일 > react' 카테고리의 다른 글

useContext  (0) 2020.12.22
useReducer  (0) 2020.12.22
useEffect  (0) 2020.12.21
useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17

useEffect

모바일/react 2020. 12. 21. 20:27 Posted by 아는 개발자

리액트의 useEffect 라이브러리는 컴포넌트나 state에 변화가 생길 때 호출되는 함수다. 두개의 인자를 받는데 첫째 인자는 변경시 호출할 콜백함수고 두번째 인자는 상태를 변경을 감지할 state를 설정한다.  state를 별도로 설정하지 않으면 componentDidUpdate, componentDidMount랑 동일한 역할을 하게 된다. 

 

const NoteApp = () => {
  
  const [notes, setNotes] = useState([])
  const [title, setTitle] = useState('')
  const [body, setBody] = useState('')

  useEffect(() => {
    console.log('load data')
    const notesData = JSON.parse(localStorage.getItem('notes'))
    if (notesData) {
      setNotes(notesData)
    }
  }, [])

  useEffect(() => {
    console.log('update notes')
    const toJson = JSON.stringify(notes)
    localStorage.setItem('notes', toJson)
  }, [notes])

  useEffect(() => {
    console.log('useEffect called')
  })

 

위와 같이 여러개의 useEffect 함수를 둘 수 있다. 첫번째 useEffect에서는 두번째 인자에 빈 배열을 넣었는데 이러면 최초 한번만 호출되게 된다. componentDidMount 콜백과 기능이 유사하다. 두번째 useEffect 함수에서는 notes 상태 값을 인자로 두었다. notes 상태의 값이 변경될 때마다 함수가 호출된다. 세번째 useEffect 함수는 전달인자를 따로 넣지 않아서 내부에 어떤 state가 바뀌더라도 새롭게 호출된다. 

 

 

728x90

'모바일 > react' 카테고리의 다른 글

useContext  (0) 2020.12.22
useReducer  (0) 2020.12.22
useEffect  (0) 2020.12.21
useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17

useState

모바일/react 2020. 12. 21. 20:14 Posted by 아는 개발자

useState는 react 에서 비교적 최근에 나온 state 관리 라이브러리다. 기존에는 컴포넌트 생성할 때 state로 두고 싶은 변수들을 하나의 오브젝트로 관리했다면 useState 라이브러리를 사용해서 변수 별로 나눠서 선언할 수 있다. 

 

const NoteApp = () => {
  
  const [notes, setNotes] = useState([])
  const [title, setTitle] = useState('')
  const [body, setBody] = useState('')

 

useState 인자로 object를 받는데 이 값은 state의 초기 값이다. 배열, 정수형 인자, 문자열 모두 가능하다. 리턴 값으로는 길이가 2인 배열을 내놓는데 첫번째는 state 변수 값이고 두번째 값은 state 값을 변형 시킬 수 있는 setter 함수다. 

 

 const addNote = (e) => {
    e.preventDefault()
    setNotes([
      ...notes, 
       { title, body }
    ])
    setTitle('')
    setBody('')
  }

  const removeNote = (title) => {
    setNotes(notes.filter((note) => note.title !== title))
  }

 

기존에는 setState를 이용해서 모든 오브젝트를 다시 초기화해야 했다면 useState에서 넘어온 setter 함수를 이용해 내가 업데이트 하고 싶은 state만 명시적인 함수로 호출이 가능하기 때문에 관리가 한결 수월해진 측면이 있다. 한 컴포넌트 내에서 관리할 state 인자가 많아질수록 유용해질 기능인 것 같다.

 

728x90

'모바일 > react' 카테고리의 다른 글

useReducer  (0) 2020.12.22
useEffect  (0) 2020.12.21
useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17
redux  (0) 2020.12.16

connect

모바일/react 2020. 12. 20. 11:08 Posted by 아는 개발자

connect 함수는 리액트 앱의 하위 컴포넌트에서 redux store를 접근하는 것을 가능하게 해주는 역할을 한다. 이 함수를 이용해서 컴포넌트 단에서 redux store에 접근하고 액션을 호출 할 수 있게 된다. 이번 포스트에서는 간단한 예제로 connect 함수를 통해 redux store를 사용하는 방법을 다뤄보려고 한다.

 

0. 준비작업

 

connect 함수 소개를 위해 예제와 텍스트와 숫자를 담당하는 redux를 만들었다.

 

BlogStore.js

import { createStore, combineReducers } from 'redux';

const textReducerState = {
    text: '',
    name: 'textReducer'
};

const textReducer = (state = textReducerState, action) => {
    switch (action.type) {
        case 'SET_TEXT':
            return {
                ...state,
                text: action.text
            };
        default: 
            return state;
    }
}

const numberReducerState = {
    numberState: 30,
    name: 'numberReducer'
};

const numberReducer = (state = numberReducerState, action) => {
    switch (action.type) {
        case 'SET_NUMBER':
            return {
                ...state,
                number: action.number
            };
        default: 
            return state;
    }
};

export const configureStore = () => {
    const store = createStore(
        combineReducers({
            text: textReducer,
            number: numberReducer
        })
    );
    return store;
};

 

BlogActions.js

 

export const setText = (
    text = ''
) => ({
    type: 'SET_TEXT',
    text
});

export const setNumber = (
    number = 0
) => ({
    type: 'SET_NUMBER',
    number
});

 

 

1. Provider 

 

configureStore() 함수를 통해 store를 생성하고 Provider 태그에 store를 속성값으로 넣는다. 이러면 하위에 추가되는 component에서 redux store를 바라볼 수 있는 창구가 만들어진다.

 

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { configureStore } from './BlogStore';
import BlogMain from './BlogMain';

const store = configureStore();


const jsx = (
    <Provider store={store}>
        <BlogMain />
    </Provider>
);


ReactDOM.render(jsx, document.getElementById('app'));

 

2. connect 

 

하위 컴포넌트 단에서는 Provider에서 제공하는 store 함수를 connect 함수를 통해서 받아온다. 함수 형식이든 클래스 형식이든 받는 방식은 동일하다. 

 

 

2.1 클래스형식

 

import React from 'react';
import { connect } from 'react-redux';
import BlogDetail from './BlogDetail';


class BlogMain extends React.Component {
    render() {
        console.log(this.props.text)
        console.log(this.props.number)
        return (
            <div>
                <p>BlogMain component</p>
                <BlogDetail />
            </div>
        )
    };
};
const mapStateToProps = (state) => {
    return {
        text: state.text,
        number: state.number
    }
};
export default connect(mapStateToProps)(BlogMain);

 

클래스 형식 컴포넌트를 export 할 때 connect 함수를 사용하고 첫번째 인자에 mapStateToProps 함수를 넣었는데 redux store에 있는 값을 컴포넌트에 어떻게 넘겨줄지 세팅하는 작업이다.  넘겨 받은 값은 component의 props에 들어가서 호출 할 수 있다. 아래 사진은 render() 함수 안에서 console로 찍은 로그다. textReducer와 numberReducer가 출력되는 것을 볼 수 있다.

 

 

2.2 함수 형식 

 

import React from 'react';
import { connect } from 'react-redux';

const BlogDetail = (props) => (
    <div>
        <p>BlogTextDetail</p>
        <p>{props.text.name}</p>
    </div>
);
const mapStateToProps = (state) => {
    return {
        text: state.text
    };
};

export default connect(mapStateToProps)(BlogDetail)

 

함수 형식도 크게 다르지 않다. 컴포넌트 내에서 호출 할 때 this를 부르지 않아도 된다는 점만 다르다. 위 코드로 호출하면 아래 그림처럼 화면 뷰가 그려진다.

 

 

3. Action 

 

connect로 컴포넌트에 전달 할 때 store만 전달 하는것이 아니라 action을 넣을 수 있는 dispatch 함수까지 전달된다. react 디바이스 툴을 사용해보면 component의 props 안에 dispatch가 들어있는 것을 확인 할 수 있다. 

 

 

실제로도 잘 사용할 수 있을 지 테스트 해보자. 방금 전에 사용한 BlogDetail 컴포넌트를 살짝 수정해서 현재 store에 저장된 text를 출력하고 버튼을 추가하고 클릭하면 text를 BlogDetail로 바뀌도록 했다.

 

import React from 'react';
import { connect } from 'react-redux';
import { setText } from './BlogActions';

const BlogDetail = (props) => (
    <div>
        <p>BlogTextDetail</p>
        <p>current store text value: {props.text.text}</p>
        <button onClick={() => {
            props.dispatch(setText('BlogDetail'))
        }}>Change text to BlogDetail</button>
    </div>
);

const mapStateToProps = (state) => {
    return { text: state.text};
};

export default connect(mapStateToProps)(BlogDetail)

 

함수를 실행하고 버튼을 클릭하니 화면이 아래와 같이 store의 text 값이 BlogDetail로 변경됐다.

 

 

4. 총평 

 

코딩을 처음 하는 분이면 이걸 왜 이렇게까지 해야할지 이해가 안될 수 있을 것 같은데 이전에 mvvm, mvc 패턴을 경험해본 개발자들에게는 redux가 크게 어려울 것 같지 않다. 강의 들을 때는 헷갈렸는데 실제로 코드로 짜보니까 어떤 식으로 구조를 잡아야할 지 느낌이 온다. 물론 자바스크립트 언어 특성상 state 세부 이름을 관리할 때 꽤 귀찮음을 겪을 것 같긴 하다.

728x90

'모바일 > react' 카테고리의 다른 글

useEffect  (0) 2020.12.21
useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17
redux  (0) 2020.12.16
scss  (0) 2020.12.13

react router

모바일/react 2020. 12. 17. 19:52 Posted by 아는 개발자

react 에서는 react-router-dom 라이브러리를 통해  들어 오는 주소 별로 별도의 페이지를 보여줄 수 있는 라우팅 기능을 제공한다. 리액트의 특성에 맞게 이 라이브러리는 어떤 페이지로 진입 했을 때 어떤 페이지를 보여 줄 것인지를 컴포넌트 단위로 뽑을 수 있다.

 

1. 라이브러리 임포트 

 

리액트와 필요한 라이브러리를 임포트 한다.

 

import React from 'react';
import { BrowserRouter, Route, Switch, Link, NavLink } from 'react-router-dom';

 

2. Route

 

const AppRouterExample = () => (
    <BrowserRouter>
        <div>
            <Switch>
                <Route path="/" component={() => (<h2>This is DashboardPage</h2>)} exact={true} />
                <Route path="/create" component={() => (<h2>This is Create Page</h2>)} exact={false} />
                <Route path="/edit/:id" 
                    component={ (props) => (<h2>This is Edit Page {props.match.params.id}</h2>)} 
                        exact={true} />
                <Route path="/help" 
                    component={() => (<h2>This is help page</h2>)} 
                    exact={true} />
                <Route component={() => (<h2>This is not 404 page</h2>)} />
            </Switch>
        </div>
    </BrowserRouter>
);

 

<BrowserRouter> 와 <Switch> 태그 내에 위치한 <Route> 태그로 관리하고 싶은 경로를 설정할 수 있다. 이렇게 두면 애플리케이션이 관리하는 경로를 설정 할 수 있게 된다.

 

2.1 path 

 

라우팅할 경로를 정의하는 값이다. 위 페이지에서는 /create, /edit, /help 페이지를 경로로 뒀다. edit 페이지의 경우에는 수정하려는 데이터의 id를 인자로 받을 수 있고 이 값은 component에 전달된다. component 객체에 props로 전달되며 저장되는 필드는 컴포넌트에 있는 값과 같다.

 

2.2 component 

 

해당 경로로 들어올 경우 어떤 component를 보여줄 것인지를 결정하는 곳이다. 직접 컴포넌트를 만들어서 넣을 수 있으며 이 예제에서는 component 필드 내에서 보여줄 수 있는 값을 넣었다. /edit 경로를 보면 props로 Route로부터 인자를 받아오는데 /edit에서 받아오는 id 정보를 확인 할 수 있다.

 

2.3 exact 

 

exact는 이 경로를 명확하게 볼 것인지 말것인지를 설정한다. 평소 익히 쓰던 path와 다른 개념이라 와닿지 않을 것 같은데 exact값이 false면 앞에 부분만 맞아도 해당 페이지로 렌딩이 된다. 예로 /create 는 exact 값이 false이기 때문에 /create/34 로 접근하든, /create/edit 으로 접근하든 모두 create 페이지로 렌딩해준다.

 

3. NavLink 

 

const AppRouterExample = () => (
    <BrowserRouter>
        <div>
            <div>
                <h1>This is Header</h1>
                <NavLink to="/" activeClassName="is-active" exact={true}>Dashboard</NavLink>
                <NavLink to="/create" activeClassName="is-active">Create Expense</NavLink>
                <NavLink to="/edit" activeClassName="is-active">Edit Expense</NavLink>
                <NavLink to="/help" activeClassName="is-active">Help</NavLink>
            </div>

 

NavLink는 하이퍼링크 기능이고 스타일링을 가능하게 한다. 내부에 있는 필드 값을 바꿔서 더 링크를 더 이쁘게 만들 수 있다.

728x90

'모바일 > react' 카테고리의 다른 글

useState  (0) 2020.12.21
connect  (0) 2020.12.20
react router  (0) 2020.12.17
redux  (0) 2020.12.16
scss  (0) 2020.12.13
webpack  (0) 2020.12.13

redux

모바일/react 2020. 12. 16. 20:28 Posted by 아는 개발자

react는 state를 이용해서 컴포넌트의 상태를 관리하는데 state 하나에 들어가는 element가 많아질수록 관리하기가 힘들어지는 문제가 있다. 그래서 react에서는 redux라는 라이브러리를 이용해서 state를 좀더 쉽게 관리할 수 있게 해줬다. 크게 state를 관리하는 store와 store 를 변경하려는 dispatch 그리고 변경 작업을 일괄 관리하는 reducer로 나뉘는데 이번 포스트에서는 각각에 대해서 간단히만 다뤄보려고 한다.

 

1. createStore 

 

redux로 관리할 state 집합을 만드는 작업이다. 함수의 인자로는 reducer를 받는다. 

 

const store = createStore(countReducer);

 

2. action 

 

state를 바꾸기 위해 취하는 액션이다. 단 여기에는 액션이 들어가지 않고 액션에 필요한 데이터만 들어간다. 변수 이름을 자유롭게 입력할 수 있는데 type 필드에 액션의 종류를 정해주는게 일반적인 것 같다. dispatch 함수를 통해 액션을 실행 할 수 있다.

 

const incrementCount = ({incrementBy = 1} = {}) => ({
    type: 'INCREMENT',
    incrementBy
});

store.dispatch(incrementCount())

 

3. reducer 

 

액션을 대신 처리해주는 부분이다. MVC로 치면 controller에 해당하는 부분으로 실제 state 값의 변경을 담당한다. reducer에서 state에 대한 변경을 일괄적으로 담당하기 때문에 관리하기가 한결 쉬워지는 장점이 있다.

 

// Reducer
// 1. Reducers are pure functions
// 2. Never change state or action 

const countReducer = (state = { count: 0 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return {
                count: state.count + action.incrementBy
            };
        default:
            return state;
    }
};

 

4. subscribe 

 

store의 subscribe 함수로 state 변경에 대한 변화를 구독 할 수 있다. reducer에서 업데이트 값들은 모두 여기를 거치게 된다. state 변경 값을 화면에 노출해야하는 경우에는 이 함수를 사용하면 된다.

 

store.subscribe(() => {
    console.log(store.getState());
});
728x90

'모바일 > react' 카테고리의 다른 글

connect  (0) 2020.12.20
react router  (0) 2020.12.17
redux  (0) 2020.12.16
scss  (0) 2020.12.13
webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11

error: internal/modules/cjs/loader.js:883

모바일/삽질 기록 2020. 12. 14. 09:58 Posted by 아는 개발자

리액트 프로젝트를 복사해서 다시 사용하는데 이런 에러가 발생했다면. 

 

internal/modules/cjs/loader.js:883

 

이전에 사용한 프로젝트의 node_modules에서 만든 경로랑 꼬여서 생긴 문제일 확률이 높다. 이럴때는 모듈 별로 수정하는 방법이 있겠지만 간단하게 node_modules 폴더를 모두 날리고 다시 설치하는게 빠르다. 

 

rm -rf node_modules
rm -f package-lock.json
yarn cache clean
yarn install
728x90

visual code definition 찾고 돌아가기

모바일/삽질 기록 2020. 12. 13. 16:59 Posted by 아는 개발자

visual code로 작업을 하다보면 어떤 클래스의 정의를 찾고 나서 이전 페이지로 돌아가고 싶은 경우가 있다. Jetbrain계열 IDE만 사용하다 visual code로 들어와서 헤맸는데 이번 포스트에 간단히 정리한다. 거의 게임체인저 급의 단축키임이 틀림 없다.

 

Mac 기준

 

정의 찾기: command + 마우스 클릭  

 

이전 페이지로 돌아가기: control + -

 

Window 기준

 

정의찾기: control + 마우스 클릭

 

이전 페이지로 돌아가기: Alt + 왼쪽 방향키

728x90

'모바일 > 삽질 기록' 카테고리의 다른 글

cannot find module - heroku  (0) 2021.01.10
error: internal/modules/cjs/loader.js:883  (0) 2020.12.14
visual code definition 찾고 돌아가기  (0) 2020.12.13
RxJava: mapper function returned null 에러  (0) 2020.02.14
addr2line  (0) 2018.12.22
gcc로 pthread API 컴파일하기  (2) 2018.10.30

scss

모바일/react 2020. 12. 13. 15:50 Posted by 아는 개발자

Syntactically Awesome StylesheetS 의 준말로 기존 css보더 더 편하게 스타일링 할 수 있는 언어다. css에 있는 모든 기능을 포함하고 여기에 더해 변수랑 내부에서 제공하는 함수도 사용할 수 있기 때문에 개발자들의 반복적인 작업을 확 줄여준다. 5년 전에 간단히 홈페이지 만들면서 css를 쓰고 불편하다고 느낀후 지금까지 손댄적이 없었는데 지금은 scss 라는 언어로 더 쉽게 스타일링 할 수 있게 된 것 같다. 이번포스트에서는 scss를 react에 적용하는 방법을 간단히 소개한다.

 

1. scss 로더 추가 

 

scss는 확장 기능이므로 새로 css로 transformation 해주는 기능이 필요하다. 별도의 로더를 설치하고 스크립트를 실행할 수 있으나 여기선 webpack에 로더를 추가해서 정리하려고 한다. 먼저 아래 명령어로 필요한 라이브러리를 추가한다.

 

yarn add style-loader css-loader sass-loader node-sass

 

그리고 webpack.config.js에 css, scss 용 로더를 추가한다.

 

module.exports = {
    ...
    module: {
        rules: [{
            loader: 'babel-loader',
            test: /\.js$/,
            exclude: /node_modules/
        }, {
            test: /\.s?css$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]

 

2. scss 파일 추가 

 

scss로 쓸 파일을 추가한다. scss 언어 내에 import 기능이 있어 여러 개의 파일로 의존성이 가능하다. 최상위 파일로 styles.scss 을 만들고 예시로 base 폴더 내에 base.scss 파일을 추가한다.

 

styles.scss

 

@import './base/base';

 

base.scss

 

간단하게 브라우저 전체에 적용될 수 있는 스타일링을 넣었다.

 

body {
    background: $dark-blue;
    font-family: Helvetica, Arial, sans-serif;
    font-size: 2.4rem;
}

 

3. entry 파일에서 임포트 

 

entry에 해당하는 파일에서 아까 추가한 scss 파일을 임포트 한후 간단한 코드를 넣어 봤다.

 

import React from 'react';
import ReactDOM from 'react-dom';
import './styles/styles.scss';

ReactDOM.render(<div><h1>This is selfish-developer blog</h1></div>, document.getElementById('app'))

 

그 결과 아래처럼 스타일링이 된 것을 확인 할 수 있다.

 

728x90

'모바일 > react' 카테고리의 다른 글

react router  (0) 2020.12.17
redux  (0) 2020.12.16
scss  (0) 2020.12.13
webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
TAG CSS, REACT, scss

webpack

모바일/react 2020. 12. 13. 15:10 Posted by 아는 개발자

webpack 은 자바스크립트로 짠 애플리케이션의 모듈 관리용 툴이다. 애플리케이션에서 필요한 모듈의 의존성을 관리하고 프로젝트용 코드를 만들어준다. 예로 리액트 자바스크립트에서 필요한 babel, style-loader, css-loader 같은 스타일용 로더, 애플리케이션의 시작위치를 정하는 entry, 디버깅용 devtool 설정, 서버 관리까지 모두 webpack을 이용해서 관리가 가능하다. 짜치는 일들을 하나로 묶어서 관리해주는 라이브러리니 배워두면 프로젝트 관리할 때 크게 도움이 될 것 같다.

 

1. Configuration

 

webpack.config.js 파일을 통해서 webpack에서 관리하고 싶은 설정을 추가할 수 있다. 아래 코드는 튜토리얼 프로젝트에서 추가한 webpack 설정 코드다. 공식 문서를 살펴보면 더 많은 설정을 추가 할 수 있다. 이번 포스트에서는 주요 설정에 대해서만 정리하려고 한다.

 

module.exports = {
    entry: './src/app.js',
    output: {
        path: path.join(__dirname,'public'),
        filename: 'bundle.js'
    },
    module: {
        rules: [{
            loader: 'babel-loader',
            test: /\.js$/,
            exclude: /node_modules/
        }, {
            test: /\.s?css$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]
        }]
    },
    devtool: 'cheap-module-eval-source-map',
    devServer: {
        contentBase: path.join(__dirname,'public')
    }
};

 

1.1 Entry 

 

webpack이 어디에서 의존성 그래프를 그려갈 지 설정하는 곳이다. 어디서부터 프로그램을 시작할지 설정하는 작업이라고 봐도 좋다. c언어 프로그램으로 치면 main함수를 어디에 둘 지 설정하는 작업이라고 봐도 될 것 같다. 위 소스코드에서는 src 폴더에 있는 app.js를 시작점으로 두었다.

 

1.2 Output 

 

webpack으로 만든 묶음(bundle)을 어디에 생성할 것인지를 설정한다. 일단 c언어라면 컴파일후 바이너리 결과물을 어디에 둘 것인지 설정하는 것과 같다.

 

1.3 Loaders

 

webpack 이 어떤 타입의 파일을 특정한 모듈로 변경하고 의존성 그래프에 추가할지를 설정하는 작업이다. 위 코드의 module 내부를 보면 test 속성과 use 속성이 있는데 test는 어떤 파일을 변형할 것인지를 설정하고 use는 어떤 loader를 사용할 것인지 설정한다. 여기서 test는 파일을 설정하는 작업이라고 했는데 파일 이름을 정규표현식으로 정하고 있다. 위 코드에서는 jsx 컨버팅용 babel-loader와 css 스타일링용 style-loader, css-loader, sass-loader 를 추가했다.

 

1.4 DevServer 

 

webpack-dev-server 라이브러리를 이용해 빠르게 프로그램을 시작 할 수 있는 기능이다. devServer 값을 설정하면 리액트 프로그램을 시작 하는 주소나 포트번호 같은 값을 쉽게 설정 할 수 있다. 위 코드에서는 가장 기본인 시작 위치만 설정 했다.

 

2. 설치 및 사용

 

2.1 설치 

 

npm을 이용해 global 하게 설치하는 방법도 있지만 꼬여버리면 답이 없으므로 프로젝트 단위로 yarn을 이용해서 설치한다. 

 

yarn add webpack webpack-dev-server

 

2.2 스크립트 파일 추가 

 

package.json에 스크립트 코드를 추가한다

 

{
  "name": "knowing-developer",
  ...
  "scripts": {
    "build": "webpack",
    "dev-server": "webpack-dev-server"
  },

 

build 스크립트는 webpack을 이용해 프로젝트 파일을 변경 시켜주는 스크립트다. 로컬에 서버까지 만드려면 dev-server 스크립트까지 추가한다.

 

2.3 실행 

 

yarn run dev-server

 

방금 추가한 스크립트를 실행한다.

728x90

'모바일 > react' 카테고리의 다른 글

redux  (0) 2020.12.16
scss  (0) 2020.12.13
webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11

localStorage

모바일/react 2020. 12. 11. 18:00 Posted by 아는 개발자

 

리액트의 localStorage는  Android의 SharedPreference나 iOS의 UserDefaults 처럼 애플리케이션 단위로 key-value 값을 저장할 수 있는 라이브러리다. 아래 코드를 보면 getItem 함수에 key값을 넣어서 값을 받아오고 setItem 함수에 key 값을 넣어서 값을 업데이트한다. 

 

아래 코드를 보면 JSON 라이브러리를 사용해 변환하는 과정이 있는데 이 이유는 localStorage에서는 string의 형태로만 저장이 가능하기 때문이다. 그래서 일반 텍스트를 사용하는 것이 아니면 모두 JSON을 이용해 값을 변환해서 저장해야한다. 배열 같은 값을 저장한다면 어차피 변환하는 과정이 필요하기 때문에 항상 써두는 것도 나쁘지 않을 것 같다.

 

class App extends React.Component {
    constructor(props) {
        super(props);
        this.handleIncrease = this.handleIncrease.bind(this)
        this.handleDecrease = this.handleDecrease.bind(this)


        this.state = {
            counter: 0
        };
    }

    componentDidMount() {
        const json = localStorage.getItem('counter');
        const counter = JSON.parse(json);
        
        if (counter) {
            this.setState((prevState) => {return {counter: counter}})
        }
    }

    componentDidUpdate(prevProps, prevState) {
        const json = JSON.stringify(this.state.counter);
        localStorage.setItem('counter', json)
    }

 

componentDidMount() 와 componentDidUpdate() 함수는 리액트의 라이프사이클 관리 함수다. componentDidMount()는 리액트 컴포넌트가 처음 생성 될 때 호출되고 componentDidUpdate는 리액트 컴포넌트에 변화가 생겼을 때, 예로 state 값이 변화해서 UI가 바뀌었을 때 호출된다. 위 코드는 각 라이프 사이클에서 필요한 작업을 넣은 것이다. 주로 componentDidMount에서 기존 값을 가져오고(fetch) componentDidUpdate에서 값을 업데이트 한다

 

소스코드

 

https://github.com/kwony/react-study/blob/main/src/playground/blog-localstorage.js

728x90

'모바일 > react' 카테고리의 다른 글

scss  (0) 2020.12.13
webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11
babel  (0) 2020.12.06

Props

모바일/react 2020. 12. 11. 18:00 Posted by 아는 개발자

props는 리액트에서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달 할 때 사용하는 기능이다. 아래 그림처럼 Header가 App 안에 포함되어 있을 때 App에서 Header에게 title과 subtitle을 전달하게 되는데 이를 props를 이용해서 할 수 있다.

 

 

1. 데이터 전달

 

class App extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const title = '아는개발자'
        const subtitle = '리액트를 공부해봅시다'
        return (
            <div>
                <Header title={title} subtitle={subtitle} />
            </div>
        )
    }
}

 

상위 컴포넌트의 render() 함수에서 하위 컴포넌트를 리턴할 때 내부에 key와 value로 전달한다. Header 컴포넌트에 subtitle로 전달하고 있다. key 값은 자유롭게 설정할 수 있고 받는 곳에서 동일한 key 값으로만 받아오면 된다.

 

2. 데이터 받아오기 

 

class Header extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return(
            <div>
                <h1>{this.props.title}</h1>
                <h2>{this.props.subtitle}</h2>
            </div>
        )        
    }
}

 

상위로부터 받아온 props 데이터를 확인해본다. props 데이터는 {this.props} 내에 있고 상위에서 보내준 key 값에 따라서 값을 확인 할 수 있다.

 

3. stateless function 

 

함수의 형태에서도 props로 전달된 값을 받아 올 수 있다. 아래 코드에선 props 변수를 받는 함수를 만들고 return 값으로 props에서 받아온 이름으로 버튼을 만들었다. 상위 컴포넌트인 App에서는 Action 컴포넌트에 buttonName을 props로 전달했다. 

 

const Action = (props) => {
    return (
        <div>
            <button>
                {props.buttonName}
            </button>
        </div>
    )
}

class App extends React.Component {
    ...
    render() {
    ...
                <Action buttonName={'click'} />
            </div>
        )
    }
}

 

4. 함수 전달

 

props로 데이터 뿐만 아니라 함수도 전달 할 수 있다. App 컴포넌트에 알럿 메시지를 띄우는 handleClick이라는 함수를 만들고 생성자에서 바인딩을 한 다음 Action 컴포넌트에 데이터를 전달하는 것과 동일하게 함수를 전달한다. 버튼을 그리는 Action 함수에서는 button 의 버튼 클릭 콜백함수에 받아온 handleClick을 인자로 넣는다. 

 

class App extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this)
    }

    handleClick() {
        alert('button clicked');
    }

    render() {
        const title = '아는개발자'
        const subtitle = '리액트를 공부해봅시다'
        const buttonName = 'click'
        return (
            <div>
                <Header title={title} subtitle={subtitle} />
                <Action buttonName={buttonName} handleClick={this.handleClick} />
            </div>
        )
    }
}

const Action = (props) => {
    return (
        <div>
            <button onClick={props.handleClick}>
                {props.buttonName}
            </button>
        </div>
    )
}

 

테스트 결과 버튼을 클릭하면 App에서 설정한 알럿 메시지가 띄워지는것을 확인 할 수 있다.

 

 

5. 소스코드

 

https://github.com/kwony/react-study/blob/main/indecision-app/src/playground/blog-props.js

728x90

'모바일 > react' 카테고리의 다른 글

webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11
babel  (0) 2020.12.06
arrow function  (0) 2020.12.06
TAG Props, REACT

state

모바일/react 2020. 12. 11. 18:00 Posted by 아는 개발자

state는 리액트 컴포넌트 내에서 사용할 변수를 관리하는 역할을 한다. 예로 간단하게 정수 값을 표시하고 1씩 증가시키고 감소시키는 버튼이 있다고 하자. 아래와 같은 기능을 제공하는 앱이라면 컴포넌트중 누군가는 현재 화면에 표시되는 값을 들고 있어야 한다.

 

1. state 관리

 

이 값은 컴포넌트내의 state 변수에서 관리한다. 아래 코드를 보면 App 컴포넌트의 생성자에서 state를 만들고 그 안에 counter라는 값을 초기화 하는 것을 볼 수 있다. 그리고 render() 함수에서 현재 state 값을 참조해 counter 값을 보여주고 있다.

 

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        };
    }

    render() {
        const title = '아는개발자'
        const subtitle = '이번에는 state를 공부해봅시다'
        const incrButton = '+1'
        const decrButton = '-1'
        return (
            <div>
                <Header title={title} subtitle={subtitle} />
                <Action buttonName={incrButton} />
                <Action buttonName={decrButton} />
                <p>현재 값: {this.state.counter}</p>
            </div>
        )
    }
}

 

2. state 변경 

 

state 값을 업데이트 할 때는 constructor 함수에서 처럼 this.state 값에 직접 업데이트 하는게 아니라 setState 함수를 사용한다. state 값이 업데이트 되면서 이 값을 참조하고 있는 ui도 동적으로 업데이트하기 위해서다. 이렇게 하지 않으면 state 값만 바뀌고 실제 화면은 그대로 남게된다. 

 

class App extends React.Component {
    constructor(props) {
        super(props);
        this.handleIncrease = this.handleIncrease.bind(this)
        this.handleDecrease = this.handleDecrease.bind(this)
        this.state = {
            counter: 0
        };
    }

    handleIncrease() {
        this.setState((prevState) => ({
            counter: prevState.counter + 1
        }))
    }
    handleDecrease() {
        this.setState((prevState) => {return {counter: prevState.counter - 1}})
    }

    render() {
        ...
        const incrButton = '+1'
        const decrButton = '-1'
        return (
            <div>
                ...
                <Action buttonName={incrButton} handleClick={this.handleIncrease} />
                <Action buttonName={decrButton} handleClick={this.handleDecrease} />
                <p>현재 값: {this.state.counter}</p>
            </div>
        )
    }

 

handleIncrease 함수와 handleDecrease 함수에서 setState 내부 구현부를 살짝 다르게 했다. handleIncrease처럼 간단하게 값을 업데이트할 수도 있고 handleDecrease처럼 return까지 포함해서 값을 업데이트 할 수도 있다.

 

3. 소스코드

https://github.com/kwony/react-study/blob/main/src/playground/blog-state.js

 

728x90

'모바일 > react' 카테고리의 다른 글

webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11
babel  (0) 2020.12.06
arrow function  (0) 2020.12.06
TAG REACT, State

item decoration

모바일/안드로이드 2020. 12. 6. 14:25 Posted by 아는 개발자

recycler view를 사용할 때 item 간의 간격을 다르게 주고 싶을 때가 있다. 예를 들어 a타입과 b타입의 아이템 사이의 간격은 상하 10dp, b타입과 c타입의 간격은 상하 5dp 이런식으로 설정하거나 더 보편적으로는 마지막 아이템인 경우에는 간격을 좀 더 띄워서 넣으려고 하는 경우가 있다. 이때 가장 빠르게 떠오르는 방법은 recyclerview의 adapter에서 position별로 margin을 주는 경우인데 이렇게 하면 안된다. recyclerview에서 자체적으로 position을 관리하기 때문에 내가 보고 있는 recyclerview에서 관리하고 있는 position이 다르다. 그래서 나는 분명히 제대로 준것 같은데 실제로 보면 다른 item에 margin이 들어간다. 이 부분이 크게 눈에 띄지 않는 부분이라 잘못 짜두고도 눈치채기가 어려워 종종 그냥 넘어가는데 나중에 디버깅해보면 item간의 간격이 내가 의도한 것과 다르게 표시된다. 그것도 아주 보기 싫게.

 

item간의 간격을 dynamic하게 조절할 때는 recycler view에서 관리하는 item decoration 라이브러리를 사용해야한다. 여기서 넘어오는 view는 recycler view에서 관리하고 있는 현재 item의 view다. 이 인자와 getChildAdapterPosition 함수를 이용해 현재 view item의 index를 찾을 수 있다. 이 정보와 outRect 인자를 활용해서 각 간격을 얼마나 줄 것인지 설정 할 수 있다.

 

recyclerview.addItemDecoration(object: RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        when (parent.getChildAdapterPosition(view)) {
            0 -> {
                outRect.left = DimensionUtils.dp2px(context, 20f).toInt()
                outRect.right = DimensionUtils.dp2px(context, 10f).toInt()
            }
            listAdapter?.itemCount?: 1 - 1 -> {
                outRect.left = DimensionUtils.dp2px(context, 10f).toInt()
                outRect.right = DimensionUtils.dp2px(context, 20f).toInt()
            }
            else -> {
                outRect.left = DimensionUtils.dp2px(context, 10f).toInt()
                outRect.right = DimensionUtils.dp2px(context, 10f).toInt()
            }
        }
    }
})

 

이제 잘못짠 코드들을 하나씩 수정해야겠다..

728x90

babel

모바일/react 2020. 12. 6. 14:12 Posted by 아는 개발자

babel은 JFX로 작성된 react javascript 파일을 브라우저에서 인식할 수 있도록 변경해주는 컴파일러다. 예시로 아래처럼 생긴 코드를 브라우저에 렌더링 하려고 하면

 

const appRoot = document.getElementById('app');

function renderApp() {
    // JSX - JavaScript XML 
    var template = <div>
        <p>Hello react</p>
    </div>

    var appRoot = document.getElementById('app');
    ReactDOM.render(template, appRoot);
 }
 renderApp()

 

요런 에러가뜬다.

 

이건 JFX로 작성된 형태를 브라우저에서 읽을 수 없기 때문이다. react에서 기본적으로 만들어주는 프로젝트를 사용하면 이런 에러가 뜨지 않는데 처음부터 만들어가면 요런 에러를 보게 된다. babel 라이브러리를 이용하면 JFX로 작성한 언어를 컴파일해서 브라우저가 읽을 수 있는 형태로 바꿀 수 있다.

 

먼저 아래 명령어를 사용해서 babel을 설치한다.

 

npm install babel-cli

 

아래 명령어로 컴파일을 할 수 있다. 이 명령어의 뜻은 source 파일에 있는 build-it-visible.js 라는 파일을 babel로 컴파일해서 결과물을 public/scripts/app.js에 입력하라는 뜻이다. presets 옵션은 컴파일 옵션이고, watch는 build-it-visible.js 파일의 변화를 계속 관찰하겠다는 뜻이다. 저장하면 자동으로 컴파일을 해주므로 유용하다.

 

babel src/build-it-visible.js --out-file=public/scripts/app.js --presets=env,react --watch

 

그래서 아까 파일을 babel로 컴파일해주면 요렇게 바뀌게 된다.

 

'use strict';

var appRoot = document.getElementById('app');

function renderApp() {
    // JSX - JavaScript XML 
    var template = React.createElement(
        'div',
        null,
        React.createElement(
            'p',
            null,
            'Hello react'
        )
    );

    var appRoot = document.getElementById('app');
    ReactDOM.render(template, appRoot);
}
renderApp();

 

그리고 브라우저에서는 요렇게 잘 뜨게 된다.

 

 

사실 Babel은 인터프리터로 봐야한다.

728x90

'모바일 > react' 카테고리의 다른 글

webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11
babel  (0) 2020.12.06
arrow function  (0) 2020.12.06

arrow function

모바일/react 2020. 12. 6. 13:59 Posted by 아는 개발자

요즘 트렌드 언어 답게 react에서 사용하는 javascript도 arrow function으로 함수를 줄일 수 있다. 자바에서 사용하는 람다나 swift의 closure 랑 비슷하게 이름없는 함수(anonymous) 를 사용해서 1회성의 함수를 따로 선언하지 않고 삽입해서 쓸 수 있는 기능이다.

 

이 함수들은 모양새는 다르지만 모두 똑같이 제곱값을 리턴하는 함수다.

 

const square1 = function(x) {
    return x * x
};

const square2 = (x) => {
    return x * x;
};

const square3 = (x) => x * x;

 

객체 내에서 함수로 들어갈 때도 동일하게 줄일 수 있다. 

 

const user = {
    name: 'kwony',
    cities: ['pangyo', 'sinchon', 'madrid'],
    printPlacedLived: function() {
        return this.cities.map((city) => {
            return this.name + ' has lived in ' + city + '!';
        });
    },

    printPlacedLived2() {
        return this.cities.map((city) => {
            return this.name + ' has lived in ' + city + '!';
        });
    }
}

 

view 요소의 콜백 함수에도 arrow function을 적용할 수 있다. 아래 코드보면 첫번째 버튼은 arrow function을 적용해서 넣었고 두번째 버튼은 따로 만든 onClickButton 함수를 사용했다. 두개 모두 동일한 역할을 한다.

 

const onClickButton = () => {
    toggle.isVisible = !toggle.isVisible
    render()
}

const render = () => {
    const template = (
        <div>
        <button onClick={() => {
            toggle.isVisible = !toggle.isVisible
            render()
        }}>{toggle.isVisible ? 'hide detail' : 'show detail'}</button>
        <button onClick={onClickButton}>{toggle.isVisible ? 'hide detail' : 'show detail'}</button>

 

간단해보이지만 손에 익으려면 꽤 시간이 걸리니 이것도 놓치지 말고 꼼꼼히 연습해봐야겠다.

728x90

'모바일 > react' 카테고리의 다른 글

webpack  (0) 2020.12.13
localStorage  (0) 2020.12.11
Props  (0) 2020.12.11
state  (0) 2020.12.11
babel  (0) 2020.12.06
arrow function  (0) 2020.12.06

Realm

모바일/iOS 2020. 12. 5. 15:21 Posted by 아는 개발자

Realm은 오픈소스로 운영되는 모바일용 데이터베이스 클래스다. CoreData 처럼 관계형 데이터베이스를 읽고 쓰는 것을 지원하고있다. 개인적으로 CoreData를 사용하는 것 보다 훨씬 직관적이고 사용하기가 간편하다. 이번 포스트에서는 Realm을 Swift에 사용하는 방법을 간단히 정리해본다. 

 

1. Realm cocoapod 임포트

 

pod 'RealmSwift', '~> 4.4.1'

 

RealmSwift 라이브러리를 임포트시킨다. 몇 ios 버전에선 이전 Realm 버전으로 임포트하면 persmission 에러가 뜬다고 해서 4.4.1 버전으로 임의로 설정했다.

 

2. 클래스 설정 

 

import Foundation
import RealmSwift

class Category: Object {
    @objc dynamic var name: String = ""
    
    let items = List<Item>() 
}

 

Realm의 테이블로 추가할 클래스를 선언한다. Object 클래스를 상속해야하는데 이 클래스가 RealmSwift에 포함돼 있어야하는것을 유의하자. name은 Category 클래스의 이름에 해당하는 값이고 items는 Category 클래스와 연결된(relation) 클래스를 의미한다. Category와 Item이 1:N 의 관계가 될 예정이다.

 

import Foundation
import RealmSwift

class Item: Object {
    @objc dynamic var title: String = ""
    @objc dynamic var done: Bool = false
    @objc dynamic var dateCreated: Date?
    var parentCategory = LinkingObjects(fromType: Category.self, property: "items")
}

 

Category 클래스에 포함될 Item 클래스를 선언한다. 아래 parentCategory에 LinkingObjects 를 활용하면 쉽게 둘간의 관계를 정해줄 수 있다.

 

3. CRUD 

 

3.1 Create 

 

let newCategory = Category()
newCategory.name = textField.text!

save(category: newCategory)

func save(category: Category) {
    let realm = try! Realm()
    do {
        try realm.write {
            realm.add(category)
        }
    } catch {
        print("Error saving contet \(error)")
    }
    
    self.tableView.reloadData()
}

 

생성하는 작업은 CoreData랑 비슷하다. 임의의 Category 클래스를 만들고 realm 라이브러리로 추가해주는 작업을 하면 된다. realm.write 함수의 콜백 내에 add 함수를 추가하면 된다. 

 

if let currentCategory = self.selectedCategory {
    do {
        try self.realm.write {
            let realm = try! Realm()
            let newItem = Item()
            newItem.title = textField.text!
            newItem.done = false
            newItem.dateCreated = Date()
            currentCategory.items.append(newItem)
        }

 

Category에  포함된 Item의 경우는 약간 다르다. 아래 코드를 보면 Category처럼 add 함수로 추가하는게 아니라 포함되어있는 Category 클래스의 items 리스트에 append 시켜서 추가하고 있다. 서로 linking되어 있어서 그렇다.

 

3.2 READ 

 

func loadCategoreis() {
    let realm = try! Realm()
    self.categories = realm.objects(Category.self)
    
    self.tableView.reloadData()
}

 

읽어오는 작업은 realm 클래스에서 objects 함수에 읽어오려는 클래스를 추가하면 된다. 이러면 간단히 읽어올 수 있게 된다. 

 

3.3 UPDATE 

 

if let item = todoItems?[indexPath.row] {
    do {
        try realm.write {
            item.done = !item.done

 

realm으로부터 읽어온 클래스를 realm.write 콜백내에서 값을 수정하기만 하면 된다.

 

3.4. DELETE 

 

if let item = todoItems?[indexPath.row] {
    do {
        try realm.write {
            realm.delete(item)

 

수정과 마찬가지로 realm으로 읽어온 클래스를 realm.write 콜백내에서 삭제하면 된다.

 

4. 총평 

 

다른 개발자들도 CoreData가 정말 쓰기 불편했나보다. realm을 찬양하는 개발자들이 더 많다. 그리고 성능도 realm이 coredata에 비해서 압도적으로 훌륭하다. 물론 앱에서 데이터를 저장할 때는 이정도로 중요할것 같지는 않지만 말이다.

 

728x90

'모바일 > iOS' 카테고리의 다른 글

Realm  (0) 2020.12.05
CoreData  (0) 2020.12.05
UserDefaults  (0) 2020.12.05
IQKeyboardManager  (0) 2020.11.30
URLSession  (0) 2020.11.30
Pod  (0) 2020.11.30

CoreData

모바일/iOS 2020. 12. 5. 13:16 Posted by 아는 개발자

CoreData는 iOS 플랫폼 단에서 지원하는 관계형 데이터베이스 라이브러리다. XCode에서 지원하는 툴과 CoreData를 사용하면 SQL의 어려운 쿼리문을 사용하지 않고도 테이블을 만들고 데이터를 추가할 수 있다. 이번 포스트에서는 Xcode를 이용해 CoreData 를 초기화하고 기본 CRUD 작업을 수행하는 것을 다뤄보려고 한다.

 

1. Data Model 파일 생성 및 초기화

 

CoreData를 사용하려면 Data Model 타입의 파일을 하나 생성해야한다. New File을 클릭하고 필터로 data 를 입력해서 Data Model 타입의 파일을 하나 추가한다.

 

이 작업이 완료되면 AppDelegate.swift 파일에 아래 코드를 추가해야한다. 현재 앱에서 바라보고 있는 데이터 모델을 세팅해주고 앱이 꺼지기 전에 데이터를 저장하는 콜백을 등록하는 작업이다. 여기서 추가한 변수인 persistentContainer는 나중에 ViewController에서 여기서 선언한 변수를 사용할 예정이다. 

 

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	... 
    
    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        self.saveContext()
    }

    lazy var persistentContainer: NSPersistentContainer = {
    
        let container = NSPersistentContainer(name: "DataModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

 

2.  Entity, Attribute 추가 

 

아까 생성했던 DataModel 파일을 클릭해보면 왼쪽 하단에 Add Entitiy 라는 버튼이 있다. 현재 앱에서 관리할 테이블을 추가하는 과정이라고 봐도 될 것 같다. 클릭하면 Entity라는 이름으로 새로운 Entity가 생성된다. 용도에 맞게 이름을 변경해준다. 나는 Item이라고 정했다.

 

 

Entity를 만들고 나면 오른쪽 하단에 Add Attribute 버튼이 있는데 Entity가 가지게 될 속성을 추가하는 작업이다. SQL로 생각하면 테이블에 컬럼을 추가하는 것과 같다. 앱을 만들며 필요하다고 생각한 칼럼 값을 여기에 추가하면 된다. Item Entity에 name이라는 속성을 String 타입으로 추가했다. 오른쪽 패널에 이 속성에 특정 값들을 설정 할 수 있다. Optional이면 null을 허용한다는 뜻이고 Derived는 상속을 받는다? 인것 같다. 이거는 구상한 스키마에 따라서 결정하면 될 것 같다.

 

속성을 모두 업데이트 하면 Entity의 오른쪽 패널을 통해 Entity에 대한 속성을 설정 할 수 있다. Module은 Entity로부터 파생되는 Class가 사용되는 모듈을 설정하는데 특별한 경우가 아니면 현재 프로덕트 모듈(Current Product Module)로 변경해준다. Codegen은 Entity가 필요한 클래스를 자동 생성할지(Class Definition) 아니면 커스텀하게 생성할 지 (Category/Extension)을 결정하는 작업이다. Category/Extension을 사용하는 경우 직접 클래스를 만들어야하기 때문에 아직 CoreData를 공부하는 입장에서는 Class Defnition을 먼저 사용해보는게 좋다.

 

 

3. CRUD 

 

3.1 CREATE

 

DataModel 작업을 마치면 ViewController에서 아까 만든 Entity를 직접 데이터로 추가하는 작업이 남았다.  먼저 새로운 Entity를 추가하는 코드를 보자. 아래 코드는 Alert view에서 Item의 이름을 받아와 새로 추가하는 작업이다. 코드 중간부분을 보면 newItem 이라는 새로운 변수를 추가했는데 생성자로 사용한 클래스가 Item이다. 이 Item 클래스는 이전에 Data Model에서 추가한 Entity랑 이름이 같다. 이 생성자에서는 context라는 인자를 받는데 AppDelegate 클래스에서 선언한 persistentContainer 변수의 속성인 viewContext를 상용한다. 그다음 context.save() 함수를 호출해서 이 값을 통해 Item 변수를 새롭게 추가할 수 있다. 이것만 해주면 된다. SQL처럼 insert 어쩌고 저쩌고 쿼리를 날릴 필요가 없어서 간편하다.

 

@IBAction func addButtonPressed(_ sender: Any) {
    
    var textField = UITextField()
    
    let alert = UIAlertController(title: "Add New Todoey Item", message: "", preferredStyle: .alert)
    
    let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
        // what will happend once user clicks the Add Item button on our UIAlert
        
        let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
        let context = container.viewContext
        let newItem = Item(context: context)
        newItem.name = textField.text!
        self.itemArray.append(newItem)
        
        do {
            try context.save()
        } catch {
            print("Error saving contet \(error)")
        }

 

3.2 READ 

 

DataModel에서 만든 Entity에는 모두 fetchRequest() 라는 함수가 있는데 이 함수는 Entity에 해당하는 데이터를 읽어올 수 있는 select Request 쿼리를 갖고 있다. 따로 변수로 만들어서 생성 작업과 동일하게 context를 불러오고 fetch 함수 내에 request를 인자로 넣으면 데이터를 읽어올 수 있다.

func loadItems() {
    let request: NSFetchRequest<Item> = Item.fetchRequest()
    
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    do {
        itemArray = try context.fetch(request)
    } catch {
        print("error fetching data from context \(error)")
    }

 

3.3 UPDATE 

 

수정작업은 CoreData로부터 받아온 클래스의 속성 값을 수정하고 save를 호출하는 작업으로 이뤄진다. 이것 또한 복잡한 쿼리를 넣을 필요 없이 간단히 수정이 가능하다.

 

func updateItem(item: Item) {
    item.name = "update name"
    
    do {
        try context.save()
    } catch {
        print("error fetching data from context \(error)")
    }

 

3.4 DELETE 

 

삭제도 크게 다르지 않다. CoreData로부터 받아온 클래스를 context.delete의 인자로 넣어주고 save를 호출한다. 이렇게만 하면 데이터가 삭제된다.

 

func delete(item: Item) {
    context.delete(item)
    
    do {
        try context.save()
    } catch {
        print("error fetching data from context \(error)")
    }

 

4. 총평 

 

복잡한 쿼리를 짤 필요가 없어서 간편하긴 한데 Xcode 특유의, 마우스를 활용한 작업이 많아서 그런가 익숙해지려면 시간이 좀 걸릴 것 같은 느낌이다. 속성같은 것들은 키보드로 설정하는게 더 간편하고 직관적인데 말이다.

728x90

'모바일 > iOS' 카테고리의 다른 글

Realm  (0) 2020.12.05
CoreData  (0) 2020.12.05
UserDefaults  (0) 2020.12.05
IQKeyboardManager  (0) 2020.11.30
URLSession  (0) 2020.11.30
Pod  (0) 2020.11.30

UserDefaults

모바일/iOS 2020. 12. 5. 12:31 Posted by 아는 개발자

앱을 개발하다 보면 종종 단일의 데이터를 저장해야하는 경우가 생긴다. 예로 들면 어떤 가이드 화면을 보여줬는지 안보여줬는지 유무를 저장하는 Boolean 타입의 데이터나 영상의 음량을 미리 정해두는 Float 타입의 데이터값 같은 것들이 있다. 이런 데이터들은 관계형 데이터베이스로 저장하는 것 보다는 key - value로 저장하는게 효율적인데 iOS에서는 UserDefaults라는 라이브러리를 이용해 이 기능을 제공한다. 안드로이드를 경험한 개발자들은 SharedPreference 클래스와 비슷한 역할을 한다고 보면 될 것 같다. 사용하는 방법도 비슷하고 간편하다.

 

class TodoListViewController: UITableViewController {
    
    let defaults = UserDefaults.standard
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        defaults.setValue(false, forKey: "booleanKey")
        defaults.setValue(123, forKey: "integerKey")
        defaults.bool(forKey: "booleanKey")
        defaults.integer(forKey: "integerKey")

 

값을 업데이트 할 때는 setValue 함수로 value와 key값을 넣고 읽을 때는 불러오려는 데이터의 타입형의 함수에 key 값을 넣어서 호출한다. UserDefaults 함수로 초기화만 잘 해주면 돼서 사용하는데 큰 어려움은 없다.

728x90

'모바일 > iOS' 카테고리의 다른 글

Realm  (0) 2020.12.05
CoreData  (0) 2020.12.05
UserDefaults  (0) 2020.12.05
IQKeyboardManager  (0) 2020.11.30
URLSession  (0) 2020.11.30
Pod  (0) 2020.11.30