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

Immutable JSを使ったスケーラブルなReactアプリケーション開発

Immutable JSを使ったスケーラブルなReactアプリケーション開発

#coraldevelopers 2019/12/19のLTの話です

Shun Mizukami

December 19, 2019
Tweet

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ %*((-&גࣜձࣾ औక໾$50 ਫ্ॣ ࡀҐdϨϯλϧνϟοτͷΫϥοΩϯάΛ࢝Ίͯϓ ϩάϥϛϯάσϏϡʔ 1)1 1FSM $ 8JO"1*ͱ͔৮ͬͯ·ͨ͠

    େֶd਺ֶઐ߈ ໊ݹ԰େ  େֶӃdʮఆཧূ໌ࢧԉܥʯͷݚڀ ˣ ๭ελʔτΞοϓ ˣ ϑϦʔϥϯε ˣ %*((-&૑ۀ೥໨ˡࠓ͜͜
  2. +4ͷΦϒδΣΫτ͸ෆมͰͳ͍ const someObj = { x: 1 }; bigOperation(someObj); if

    (someObj.x !== 1) { throw ‘BUG!'; } ෆมͰ͸ͳ͍
  3. +4ͷΦϒδΣΫτ͸ෆมͰͳ͍ όάͬͨΒ ͜͜ΛಡΈղ͔ͳ͍ͱ͍͚ͳ͍ const someObj = { x: 1 };

    bigOperation(someObj); if (someObj.x !== 1) { throw ‘BUG!'; } ෆมͰ͸ͳ͍
  4. +4ͷΦϒδΣΫτ͸ෆมͰͳ͍ όάͬͨΒ ͜͜ΛಡΈղ͔ͳ͍ͱ͍͚ͳ͍ const someObj = { x: 1 };

    bigOperation(someObj); if (someObj.x !== 1) { throw ‘BUG!'; } ਺ઍߦ͔΋͠Εͳ͍ɻਏ͍ɻ
  5. +4ͷΦϒδΣΫτ͸ෆมͰͳ͍ const someObj = ImmutableSomething({ x: 1 }); bigOperation(someObj); if

    (someObj.x !== 1) { throw ‘BUG!'; } ෆมͳΒ͹ɺ ͜ͷόά͸ى͖ͳ͍
  6. ෆมΦϒδΣΫτ +BWBͷ΍Γํ ɾpOBMͱ͔ࢦఆ ɾHFUUFS͔͠࡞Βͳ͍ final class Person { private final

    String name; public Person(String name) { this.name = name; } public String getName() { return this.name; } }
  7. ෆมΦϒδΣΫτ *O+BWB4DSJQU ɾશͯϝϯόΞΫηεՄೳ ɹɾΞϯείͰӅ͢ ɾHFUUFSΛ࡞Δ class Person { constructor(name) {

    this._name = name; } get name() { return this._name; } } ˞ &DNB4DSJQUͷ4UBHF1SPQPTBMΛڐ༰͢Δ৔߹ɺ QSJWBUFDMBTTpFMETͱ͍͏ख΋͋Δ https://github.com/tc39/proposal-class-fields
  8. *NNVUBCMF+4ͱ͸ σʔλߏ଄ *NNVUBCMF+4 1MBJO+4 ഑ྻ -JTU "SSBZ ,FZ7BMVFϚοϓ .BQ0SEFSFE.BQ .BQ8FBL.BQ0CKFDU

    ॏෳͳ͠ू߹ 4FU0SEFSFE4FU 4FU8FBL4FU ஗ԆϦετ 4FR ͳ͠ ελοΫ 4UBDL ͳ͠ ೚ҙͷߏ଄ମ 3FDPSE DMBTTఆٛ0CKFDU ڧྗͳϓϦϛςΟϒΛؚΉɺෆมΦϒδΣΫτΛऔΓѻ͏ϥΠϒϥϦ
  9. ࣮૷্ɺTUBUF΍QSPQTͷσʔλͷॻ͖׵͕͑ඞཁʹͳΔέʔε͸΄΅ͳ͍ // bad handleSelect(id) { const { selectedIds } =

    this.state; selectedIds.push(id); this.setState({ selectedIds, }); } // good handleSelect(id) { const { selectedIds } = this.state; this.setState({ selectedIds: [...selectedIds, id], }); } ͔͠͠ɺهड़͕໘౗ ಛʹ0CKFDUͷDPQZ *NNFS΍JNNVUBCJMJUZIFMQFSΛ࢖ͬͨͱͯ͠΋ɺ ഁյతॻ͖ํΛ཈ࢭͰ͖ΔΘ͚Ͱ͸ͳ͍ ഁյత
  10. *NNVUBCMF+4৔߹ // ImmutableJS List handleSelect(id) { const { selectedIds }

    = this.state; this.setState({ selectedIds: selectedIds.push(id), }); } ͍͕ͭ͜*NNVUBCMF-JTUʹͳͬͨ৔߹ -JTUQVTIͷ݁Ռ͸৽͍͠-JTUΠϯελϯε ίʔυ͕୹͘ɺ͔ͭඇഁյత
  11. ڧྗͳྫ ͩΊͩΑͶ ਖ਼͍͕͠ɺOFX͢Δͷ͸حົ class User { constructor(name, age) { this.name

    = name; this.age = age; } } // bad handleChangeName(name) { const { user } = this.state; user.name = name; this.setState({ user, }); } // good handleChangeName(name) { const { user } = this.state; this.setState({ user: new User(name, user.age), }); }
  12. class User extends Immutable.Record({ name: '', age: 0, }) {

    } // bad handleChangeName(name) { const { user } = this.state; user.name = name; // will throw "Error: Cannot set on an immutable record." this.setState({ user, }); } // good handleChangeName(name) { const { user } = this.state; this.setState({ user: user.set('name', name), }); } ڧྗͳྫ ྫ֎Ͱམͪͯ͘ΕΔʢૉ੖Β͍͠ΑͶ ʣ ˠ։ൃʹྑ੍͍໿Λ͔͚ͯ͘ΕΔ 3FDPSETFU͸৽͍͠ΠϯελϯεΛฦ͢
  13. *NNVUBCMF-JTU ෆมͷ഑ྻ // init with Array const l1 = List([4,

    2, 3]); // => List [4, 2, 3] // #get(index) to get value at index l1.get(1) // => 2 // #set(index, value) to set value at index const l2 = l1.set(0, 1); // => List [1, 2, 3] l1 // => List [4, 2, 3] <- not mutated // generic operations like map, forEach and reduce can be used const l3 = l2.map(x => 1 + x) // => List [2, 3, 4] // #delete(index) to delete #unshift(value) to add at first const l4 = l3.delete(2).unshift(4) // => List [4, 2, 3] // equality l1.equals(l4) // => true l1.equals(l2) // => false
  14. *NNVUBCMF.BQ ෆมͷ,FZ7BMVF.BQ // init with entries const m1 = Map([['a',

    1], ['b', 2]]) // => Map { "a": 1, "b": 2 } // #get(key) to get value at key m1.get('b') // => 2 // #set(key, value) to set value at key const m2 = m1.set('c', 3); // => Map { "a": 1, "b": 2, "c": 3 } m1 // Map { "a": 1, "b": 2 } <- not mutated // key can be non-string value const m3 = m2.set(5, 4) // => Map { "a": 1, "b": 2, "c": 3, 5: 4 } // key type is NOT converted implicitly m3.get('5') // => undefined m3.get(5) // => 4 // #delete(key) to delete key-value pair const m4 = m3.delete(5).delete('c'); // => Map { "a": 1, "b": 2 } // equality m1.equals(m4) // => true m1.equals(m2) // => false
  15. *NNVUBCMF4FU ෆมͷॏෳͳ͠ू߹ // init with Array const s1 = Set([1,

    2, 3]) // => Set [1, 2, 3] // #has(value) to check value presence // NOTE: Set#has is potentially faster O(logN) than Array#includes O(N) s1.has(2) // => true // Set#add(value) to add new value s1.add(3) // => Set [1, 2, 3] <- duplicated, no change const s2 = s1.add(4) // => Set [1, 2, 3, 4] // Set#delete(value) to delete value from set const s3 = s2.delete(1) // => Set [2, 3, 4] // set-theoretical operations s1.intersect(s3) // => Set [2, 3] <- S1 ∩ S3 s1.union(s2) // => Set [1, 2, 3, 4] <- S1 ∪ S3 s1.subtract(s3) // => Set [1] <- S1 \ S3
  16. ଞͷϓϦϛςΟϒ ஗ԆϦετ const s1 = Seq([1, 2, 3]) const s2

    = s1.map(x => { console.log(`mapping ${x}`) return x % 2 === 0 ? x / 2 : null; }) const s3 = s2.filter(x => { console.log(`filtering ${x}`); return x !== null; }) s3.toList() // mapping 1 // filtering null // mapping 2 // filtering 1 // mapping 3 // filtering null // => List [ 1 ]
  17. class User extends Immutable.Record({ firstName: '', lastName: '', age: 0,

    }) { get fullName() { return `${this.firstName} ${this.lastName}` } } // initialize const u = new User({ firstName: 'Shun', lastName: 'Mizukami', age: 29 }) // to get value u.firstName // => "Shun" // cant set value u.firstName = "Taro" // => error thrown // invoke instance method u.fullName // => "Shun Mizukami" // Record#set to set and return new instance u.set('age', 30) // => User { firstName: "Shun", lastName: "Mizukami", age: 30 } *NNVUBCMF3FDPSE ϞσϧΛ࡞ΕΔ
  18. ॏΊͷॲཧͳΒNFNPJ[F͢Δ class User extends Record({ firstName: '', lastName: '', age:

    0, }) { @memoize get fullName() { return `${this.firstName} ${this.lastName}` } } σʔλ͕ෆมͳͷͰϝϞԽͰؒҧͬͨΩϟογϡΛ͢Δ৺഑͕ͳ͍ ˞NFNPJ[FEFDPSBUPS
  19. 3FDPSEͷϞσϧʹQSPQ5ZQFTΛॻ͘ import IPT from 'react-immutable-proptypes'; import PT from 'proptypes'; class

    User extends Record({ firstName: '', lastName: '', age: 0, }) { // User.t as a PropTypes static t = IPT.recordOf({ firstName: PT.string, lastName: PT.string, age: PT.number, }) get fullName() { return `${this.firstName} ${this.lastName}` } } ˞JNNVUBCMF޲͚1SPQ5ZQFTϥΠϒϥϦ ˞.PEFMʹॻ͘͜ͱͰɺ.PEFMUͰ࢖͑Δ
  20. // Unofficial Record inheritance helper // https://gist.github.com/mizukami234/5bd93a91f632fa8b2a984c32d2ff408e import RecordExtend from

    'Utils'; class Vec2 extends Record({ x: 0, y: 0, }) { get sum() { return this.x + this.y; } } class Vec3 extends RecordExtend(Vec2, { z: 0, }) { get sum() { return super.sum + this.z; } } 3FDPSEΫϥεͷܧঝ ඇެࣜ ಺෦࣮૷্ɺ௨ৗͷܧঝ͸Ͱ͖ͳ͍ (JTUʹ͍͋͛ͯ·͢
  21. // example: List // creates 100 instances and destroyed except

    the last one let l = List() for (let i = 0; i < 100; i++) { l = l.push(i) } // withMutations passes mutable instance enabled within a limited scope const l = List().withMutations(m => { for (let i = 0; i < 100; i++) { m.push(i) } }) ͦ͏͸ݴͬͯ΋ɺύϑΥʔϚϯεతʹ .VUBUJPO͕ཉ͍͠৔߹ ຖճੜ੒͢Δ ͜ͷதͰ͚ͩNVUBCMFʹͳΔ