LoginSignup
13
4

More than 5 years have passed since last update.

ネストしたstateの更新にLensを使う

Posted at

基本的にはimmutability-helperとかlodash.setとか好きに使えばいいと思いますが、TypeSafeかつComposableにやりたいという場合のためにLensを試して見ます。

ScalaのLensライブラリであるmonocleのts版(monocle-ts)を使ってみます。

Lensとは?

以下のようにネストしたobjectをいい感じに取得したり更新できるものです。

const employee: Employee = {
  name: 'john',
  company: {
    name: 'awesome inc',
    address: {
      city: 'london',
      street: {
        num: 23,
        name: 'high street'
      }
    }
  }
}

末端のnameにcapitalize関数をimmutableに当てたい場合、通常以下のようになります...

const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)

const employee2 = {
  ...employee,
  company: {
    ...employee.company,
    address: {
      ...employee.company.address,
      street: {
        ...employee.company.address.street,
        name: capitalize(employee.company.address.street.name)
      }
    }
  }
}

Lensを使うと、

import { Lens, Optional } from 'monocle-ts'

const company = Lens.fromProp<Employee, 'company'>('company')
const address = Lens.fromProp<Company, 'address'>('address')
const street = Lens.fromProp<Address, 'street'>('street')
const name = Lens.fromProp<Street, 'name'>('name')

company
  .compose(address)
  .compose(street)
  .compose(name)
  .modify(capitalize)(employee)

このようにimmutableに更新でき、かつ合成可能な形になっています。

使ってみる

以下のようなネストしたStateで試しみる。

interface State {
  data: {
    value: string,
    meta: {
      prop: number
    }
  }
}
import { Lens } from 'monocle-ts'

interface State {
  data: {
    value: string,
    meta: {
      prop: number
    }
  }
}

const data = Lens.fromProp<State, "data">("data")
const meta = Lens.fromProp<State["data"], "meta">('meta')
const prop = Lens.fromProp<State["data"]["meta"], "prop">('prop')

const lens = data.compose(meta).compose(prop)

const state: State = {
  data: {
    value: "something",
    meta: {
      prop: 1
    }
  }
}

console.log(lens.get(state))
// 1

console.log(lens.set(100)(state))
// { data: { value: 'something', meta: { prop: 100 } } }

console.log(lens.modify(prop => prop + 1)(state))
// { data: { value: 'something', meta: { prop: 2 } } }

console.log(state)
// { data: { value: 'something', meta: { prop: 1 } } }

取得に失敗する場合

例えば以下のようにnullがある可能性があると取得できずにエラーになります。

そういう場合にLensでなくPrismというものが用意されています。

import { fromNullable } from "fp-ts/lib/Option";
import { Prism } from "monocle-ts";

interface State2 {
  x: { id: number } | null;
}

const xLens = Lens.fromProp<State2, "x">("x")
const nullable = new Prism<{id: number} | null, {id: number}>(fromNullable, a => a)
const idLens = Lens.fromProp<{id: number}, "id">("id")
const x = xLens.composePrism(nullable).composeLens(idLens)

console.log(x.getOption({x: {id: 1}}).getOrElseValue(0))
// 1

console.log(x.set(9)({x: {id: 1}}))
// { x: { id: 9 } }

console.log(x.set(9)({x: null}))
// { x: null }

fp-ts

monocle-tsは、fp-tsに依存してるのMonadとか好きな人にはいいと思います。:innocent:

import { monoidProduct, monoidSum } from "fp-ts/lib/Monoid";
import { fromFoldable } from "monocle-ts";
import { array } from "fp-ts/lib/Array";

const xs = ["a", "bb"];
const fold = fromFoldable(array)<string>();
const len = (s: string) => s.length;

console.log(fold.foldMap(monoidSum)(len)(xs));
// 3
console.log(fold.foldMap(monoidProduct)(len)(xs));
// 2

まとめ

Lensはなかなか使う機会がないですが、stateはネストしてることが多いので使う機会があるかもと思って書いて見ましたが、まぁ普通に使わないですね :innocent:

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4