타입스크립트 기초 - 16

Stulta Amiko·2022년 7월 26일
0

타입스크립트 기초

목록 보기
16/24
post-thumbnail

렌즈를 활용하여 객체의 속성 다루기

렌즈는 하스켈에서 Control.Lens 라이브러리 내용중 자바스크립트에서 동작할 수 있는 게터와 세터 기능만을 람다함수로 구현한 것이다.

prop/assoc

렌즈 기능을 이해하기 위해서는 prop 함수와 assoc 함수를 알아야한다.
prop 은 property 의 줄임말이고 객체의 특정 속성값을 가져오는 함수로서 게터의 역할과 비슷하다.
assoc 함수는 세터와 비슷한 기능을 한다.

import * as R from 'ramda'
import { IPerson,makeRandomIPerson } from './model/person'

const person:IPerson = makeRandomIPerson()

const name = R.pipe(
    R.prop('name'),
    R.tap(name => console.log(name))
)(person)

prop 함수를 사용하는 예제를 먼저 보면 prop은 게터와 같은 역할을 한다고 했기 때문에 위와같이 IPerson 타입에서 'name'에 해당하는 부분만 가져오는 모습을 볼 수 있다.

import * as R from 'ramda'
import { IPerson,makeRandomIPerson } from './model/person'

const getName = R.pipe(R.prop('name'),R.tap(name => console.log(name)))

const person: IPerson = makeRandomIPerson()
const originalName = getName(person)

const modifiedPerson = R.assoc('name', 'Albert Einstein')(person)
const modifiedName = getName(modifiedPerson)

위와같은 방법으로 assoc함수를 사용할 수 있다. 첫번째 파라미터로 대응되는 key를 넣어주고 두번째 값으로 value를 넣어주는것 세터와 거의 다른게 없다


lens

렌즈 기능을 사용하려면 렌즈를 만들어야하는데 렌즈는 다음처럼
R.lens,R.prop,R.assoc 의 조합으로 만들 수 있다


view,set,over

import * as R from 'ramda'

export const makeLens = (propName: string) => 
    R.lens(R.prop(propName),R.assoc(propName))

export const getter = (lens) => R.view(lens)
export const setter = (lens) => <T>(newValue: T) => R.set(lens, newValue)
export const setterUsingFunc = (lens) => <T,R>(func: (T) => R) => R.over(lens,func)

위와 같은 형식으로 먼저 렌즈를 만들고 게터 셋터 그리고 세터를 이용하는 함수를 만들어서 먼저 모듈화 시켜놓는다.

import * as R from 'ramda'
import { makeLens,getter,setter,setterUsingFunc } from './lens'
import { IPerson,makeRandomIPerson } from './model/person'

const nameLens = makeLens('name')
const getName = getter(nameLens)
const setName = setter(nameLens)
const setNameUsingFunc = setterUsingFunc(nameLens)

const person: IPerson = makeRandomIPerson()

const name = getName(person)
const newPerson = setName('Albert Einstein')(person)
const anotherPerson = setNameUsingFunc(name => `'Mr. ${name}'`)(person)
const capitalPerson = setNameUsingFunc(R.toUpper)(person)

console.log(
    name,getName(newPerson),getName(anotherPerson),getName(capitalPerson),
)

그리고 위 함수를 이용하기 위해서 무작위로 이름을 생성한다.

실행결과
Jordan Fox Albert Einstein 'Mr. Jordan Fox' JORDAN FOX

이름은 랜덤 생성된것이므로 다를 수 있다.

일단 코드를 먼저보면 nameLens에서 렌즈를 만드는 모습을 볼 수 있다.
그러고서 게터 세터와 세터를 이용하는 함수를 만든다.

사용할때는 일단 기본적으로 처음사용한게 게터
그다음이 세터로 이름을 바꾸는 작업을 하는 모습을 볼 수 있다.
후에는 세터를 이용하는 함수를 이용해서 이름에 Mr을 붙이는것을 볼 수 있고
그다음에는 전부 대문자로 만드는 모습을 볼 수 있다.
코드만 봐도 대충 뭘 하는지 쉽게 알 수 있다.


lensPath

IPerson 객체의 longitde 값을 알려면

person.location.coordinates.longitude 와 같이 긴 코드를 작성해야하는 불편한 상황이 발생하게 된다.
이런 긴 경로의 속성을 렌즈로 만들려면 lensPath를 사용한다.

import * as R from 'ramda'
import { makeLens,getter,setter,setterUsingFunc } from './lens'
import { IPerson,makeRandomIPerson } from './model/person'

const longitudeLens =R.lensPath(['location','coordinates','longitude'])
const getLongitude = getter(longitudeLens)
const setLongitude = setter(longitudeLens)
const setLongitudeUsingFunc = setterUsingFunc(longitudeLens)

const person: IPerson = makeRandomIPerson()

const longitude = getLongitude(person)
const newPerson = setLongitude(0.123456)(person)
const anothrPerson = setLongitudeUsingFunc(R.add(0.1234567))(person)

console.log(
    longitude,getLongitude(newPerson),getLongitude(anothrPerson)
)

위와같은 방식으로 longitude만 값을 바꾸거나 수정하는 코드를 만들었다.
lensPath를 이용해서 만드는 모습을 볼 수 있다.

객체 다루기

toPairs / fromPairs

import * as R from 'ramda'
import { IPerson,makeRandomIPerson } from './model/person'

const person: IPerson = makeRandomIPerson()
const pairs: [string,any][] = R.toPairs(person)
console.log('pairs',pairs)

먼저 toPairs함수는 객체의 속성을 분해해 배열로 만들어준다.

위 코드의 실행결과를 보면

pairs [
[ 'name', 'Sarah Robinson' ],
[ 'age', 29 ],
[ 'title', 'Education Adminator' ],
[
'location',
{
country: 'HU',
city: 'Tuobdov',
address: '1224 Warku Ridge',
coordinates: [Object]
}
]
]

위와같은 결과를 내게 되는데 객체에있는 키와 밸류가
[key,value] 형태의 배열로 만들어 진것을 확인할 수 있다.

다음은 fromPairs함수이다.
fromPairs 함수는 toPairs와 반대로 [key:value] 형태의 배열을 다시 객체로 만들어준다.

import * as R from 'ramda'
import { IPerson,makeRandomIPerson } from './model/person'

const pairs: [string,any][] = R.toPairs(makeRandomIPerson())
const person: IPerson = R.fromPairs(pairs) as IPerson
console.log(person)

{
name: 'Tommy Fletcher',
age: 40,
title: 'Organizational Development Manager',
location: {
country: 'AW',
city: 'Noegoco',
address: '248 Vuso Highway',
coordinates: { latitude: -6.43962, longitude: -100.80624 }
}
}

pairs에서 객체의 값을 가지고 있던 배열을 다시 객체로 만드는 모습을 볼 수 있다.


keys / values

먼저 keys 함수부터 살펴보면
이름만 들어도 직감할 수 있듯이 key값만 가져오는 함수임을 알 수 있다.

import * as R from 'ramda'
import { makeRandomIPerson } from './model/person'

const keys: string[] = R.keys(makeRandomIPerson())
console.log('keys',keys)

실행결과
keys [ 'name', 'age', 'title', 'location' ]

IPerson의 key를 전부 가져오는것을 알 수 있다.
key값은 전부 string이기 때문에 타입도 string 배열인 것을 알 수 있다.

다음은 values 함수이다.

import * as R from 'ramda'
import { makeRandomIPerson } from './model/person'

const values: any[] = R.values(makeRandomIPerson())
console.log('values',values)

실행결과
values [
'Ray Robertson',
45,
'Professional Athlete',
{
country: 'IN',
city: 'Ocalium',
address: '111 Ovouc Mill',
coordinates: { latitude: -26.31672, longitude: -154.33124 }
}
]

key가 전부 string인것에 반하여 value는 여러 타입을 가질 수 있기때문에 any 타입 배열로 만들었다.

보면 key값은 제외하고 value값만 긁어오는 모습을 볼 수 있다.


zipObj

zipObj 함수는 키배열과 값배열을 결합해서 객체로 만들어준다.

import * as R from 'ramda'
import { makeRandomIPerson,IPerson } from './model/person'

const person: IPerson = makeRandomIPerson()

const key: string[] = R.keys(person)
const values: any[] = R.values(person)

const zipped: IPerson = R.zipObj(key,values) as IPerson
console.log(zipped)

실행결과
{
name: 'Nathan Hogan',
age: 56,
title: 'Electro Optical Engineer',
location: {
country: 'AS',
city: 'Reszipvih',
address: '197 Hawe Highway',
coordinates: { latitude: -41.35164, longitude: -126.92718 }
}
}

위와같이 그냥 첫번째 파라미터로 key를 넣고 두번째 파라미터로 value를 넣는것을 볼 수 있다.


mergeLeft / mergeRight

mergeLeft는 왼쪽객체의 우선순위가 높은 함수이고
Right는 반대로 작용한다
두개의 객체를 입력받아 속성을 결합한다.

import * as R from 'ramda'

const left = {name: 'jack'}, right = {name: 'jane',age: 33}
const person_L = R.mergeLeft(left,right)
const person_R = R.mergeRight(left,right)

console.log(person_L)
console.log(person_R)

실행결과
{ name: 'jack', age: 33 }
{ name: 'jane', age: 33 }

위 코드의 실행결괄를 보면 알 수 있듯이 mergeLeft 일때는 왼쪽에 있는 값이 우선순위가 높아서 이름이 jack이 되는것을 볼 수 있고
반대로 mergeRight일때는 우측에 있는 객체가 우선순위가 높아서 jane으로 바뀌는 것을 볼 수 있다.


mergeDeepLeft / mergeDeepRight

바로 위에서 본 mergeLeft나 mergeRight의 경우 객체의 속성에 담긴 객체를 바꾸지는 못한다.
IPerson의 속성값들만 바꿔줄 뿐 location이나 location.coordinates의 속성값을 바꾸지는 못한다.

여기서 mergeDeepLeft 혹은 mergeDeepRight를 이용하게 되면 경로의 속성값도 바꿔줄 수 있다.

import * as R from 'ramda'
import { makeRandomIPerson,IPerson } from './model/person'
import { makeRandomICoordinates,ICoordinates } from './model/coordinates'
import { makeRandomILocation,ILocation } from './model/location'

const person: IPerson = makeRandomIPerson()
const location: ILocation = makeRandomILocation()
const coordinates: ICoordinates = makeRandomICoordinates()

const newLocation = R.mergeDeepRight(location,{coordinates})
const newPerson = R.mergeDeepRight(person,{location: newLocation})

console.log('person',person)
console.log('new Person',newPerson)

실행결과
person {
name: 'Fannie Burke',
age: 49,
title: 'Commercial Artist',
location: {
country: 'UG',
city: 'Harfifvuc',
address: '1224 Veno Plaza',
coordinates: { latitude: -85.21719, longitude: 32.52957 }
}
}
new Person {
name: 'Fannie Burke',
age: 49,
title: 'Commercial Artist',
location: {
country: 'DM',
city: 'Kimassez',
address: '1509 Ogeri Terrace',
coordinates: { latitude: -60.08834, longitude: 44.6996 }
}
}

deep을 사용하지 않으면 접근할 수 없는 객체 내부에 객체 내부에 객체까지 접근해서 바꾸는 못브을 볼 수 있다.

위는 원래 객체고 아래가 변경된 객체이다 location과 coordinates까지
바뀐 모습을 볼 수 있다.

0개의 댓글