Redux: connect()์˜ ๊ถŒ์žฅ ์‚ฌ์šฉ๋ฒ•

์— ๋งŒ๋“  2015๋…„ 08์›” 07์ผ  ยท  34์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: reduxjs/redux

์•ˆ๋…•ํ•˜์„ธ์š” @gaearon - ๋ฌธ์„œ๋ฅผ ์ฝ๋Š” ๋™์•ˆ ๋‹ค์Œ ์ง„์ˆ ์ด ๋‚˜๋ฅผ

๊ทธ๋Ÿฐ ๋‹ค์Œ react-redux์˜ connect() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Redux์— ์—ฐ๊ฒฐํ•˜๋ ค๋Š” ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ƒ์œ„ ๊ตฌ์„ฑ ์š”์†Œ ๋˜๋Š” ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ์— ๋Œ€ํ•ด์„œ๋งŒ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์‹ญ์‹œ์˜ค. ๊ธฐ์ˆ ์ ์œผ๋กœ ์•ฑ์˜ ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ Redux ์Šคํ† ์–ด์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋„ˆ๋ฌด ๊นŠ๊ฒŒ ์—ฐ๊ฒฐํ•˜๋ฉด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ธฐ ๋” ์–ด๋ ค์›Œ์ง€๋ฏ€๋กœ ํ”ผํ•˜์„ธ์š”.

Deep prop chains๋Š” ๋‚ด๊ฐ€ Flux๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋งŒ๋“  React์˜ ๋ฌธ์ œ ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋” ์‰ฝ๊ฒŒ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•œ ์ ˆ์ถฉ์•ˆ์€ prop ํ๋ฆ„์„ ์„ค์ •/์œ ์ง€/์ถ”์ ํ•ด์•ผ ํ•˜๋ฉฐ ๋” ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋ถ€๋ชจ๊ฐ€ ์ž๋…€์˜ ๋ฐ์ดํ„ฐ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚ด ๊ฒฝํ—˜์ƒ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋” ๋‚ซ๋‹ค๋Š” ํ™•์‹ ์€ ์—†์ง€๋งŒ ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜์‹œ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.์Šค๋งˆ์ผ:

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋งŽ์€ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์—ฐ๊ฒฐ()ํ•  ๋•Œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ ์™ธ์— ๋‹ค๋ฅธ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์•„๋‹ˆ์š”, ์ •๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. ํ•˜ํ–ฅ์‹ ํ๋ฆ„์„ ํฌ๊ธฐํ•˜๋ฉด ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  34 ๋Œ“๊ธ€

๋‚˜๋Š” Brent์— ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Relay์™€ ๊ฐ™์€ ๊ฒƒ์˜ ์ด๋ฉด์— ์žˆ๋Š” ๊ทผ๋ณธ์ ์ธ ์ƒ๊ฐ์˜ ๋Œ€๋ถ€๋ถ„์ด๋ฉฐ ๋” ๋†’์€ ์‘์ง‘๋ ฅ์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

์•„๋งˆ๋„ ์šฐ๋ฆฌ๋Š” "์‚ฌ๋žŒ๋“ค์€ ์—ฌ๊ธฐ์— ๋‹ค๋ฅธ ์„ ํ˜ธ๋„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค"๋ผ๊ณ  ๋งํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@gaearon - ํŠธ๋ ˆ์ด์‹ฑ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋ฐ๋ชจ๋ฅผ ํฌํ•จํ•˜๋Š”

๋ฉ‹์žˆ๋Š”. ์—ด์–ด๋‘๊ณ  ์ดˆ๊ธฐ ๋ฌธ์„œ๊ฐ€ ์ •๋ฆฌ๋œ ํ›„ ๋‹ค์‹œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@gaearon ์ž˜ ๋ดค์Šต๋‹ˆ๋‹ค! :) ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ €์—๊ฒŒ Ping

์ €๋Š” Redux๋ฅผ ์ฒ˜์Œ ์ ‘ํ–ˆ์ง€๋งŒ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ƒ๋‹นํžˆ ๋งŽ์ด ๋ณ€๊ฒฝํ•œ ์•ฑ์„ ๊ตฌ์ถ•ํ•˜๋ฉด์„œ ์ง€๋‚œ 1๋…„ ๋™์•ˆ ์ด๊ฒƒ์ด ์ €์—๊ฒŒ ๊ฝค ํฐ ๊ณจ์นซ๊ฑฐ๋ฆฌ์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‚˜๋Š” ์ด๊ฒƒ ์— ๋Œ€ํ•ด

๋งŽ์€ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์—ฐ๊ฒฐ()ํ•  ๋•Œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ ์™ธ์— ๋‹ค๋ฅธ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

๋งŽ์€ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์—ฐ๊ฒฐ()ํ•  ๋•Œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ ์™ธ์— ๋‹ค๋ฅธ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์•„๋‹ˆ์š”, ์ •๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. ํ•˜ํ–ฅ์‹ ํ๋ฆ„์„ ํฌ๊ธฐํ•˜๋ฉด ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ค‘๊ฐ„ ์ˆ˜์ค€ ๊ตฌ์„ฑ ์š”์†Œ์˜ ๋ถˆํ•„์š”ํ•œ ๋‹ค์‹œ ๋ Œ๋”๋ง์„ ํ”ผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—?

์˜ˆ.

ํ† ๋ก ์— ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด: ์ €๋Š” ์ฃผ๋กœ ์—ฐ๊ฒฐ ๊ตฌ์„ฑ ์š”์†Œ์— ๋Œ€ํ•œ ์‚ฌ์šฉ์„ ์ œํ•œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ์Šค๋งˆํŠธ ๊ตฌ์„ฑ ์š”์†Œ(์˜ˆ: ์–‘์‹ ๋ชจ๋‹ฌ)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‚ด ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์š”์†Œ ๋˜๋Š” ๋…ธ๋“œ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๋ฉ์ฒญํ•œ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ด๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋‹น์‹ ์ด ์ข€ ๋” ์ƒ์šฉ๊ตฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜์ง€๋งŒ, ๋ฉ์ฒญํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์‰ฝ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์—ฌ์ „ํžˆ ์ด๊ฒƒ์„ ์‹คํ—˜ํ•˜๊ณ  ์žˆ์ง€๋งŒ ์ด๊ฒƒ์ด ์‰ฌ์šด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ์„ ํฌ๊ธฐํ•˜์ง€ ์•Š๊ณ  ์Šค๋งˆํŠธ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋Œ€๋žต์ ์ธ ์˜ˆ๋ฅผ ๋“ค์ž๋ฉด:

ํ‘ธ.jsx

export class Foo extends Component {
  render () {
    return (
      <div className='foo'>
        {/* foo stuff going on in here */}
        {this.props.Bar}
      </div>
    )
  }
}

FooContainer.jsx

@connect(getState, getActions)
export class FooContainer extends Component {
  render () {
    return (
      <Foo
        Bar={<BarContainer/>}
        {...this.props}
       />
    )
  }
}

@brentvatne ๋ฌด์–ธ๊ฐ€๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ˜„์žฌ ๋ฌธ์„œ ๊ตฌ์กฐ์—์„œ ์–ด๋””์— ๋งž์ถœ์ง€ ํŒŒ์•…ํ•˜๊ณ  ์ž์œ ๋กญ๊ฒŒ ์ง„ํ–‰ํ•˜์„ธ์š”! :-)

2015/10/19 ์—…๋ฐ์ดํŠธ: ์ด ๋Œ“๊ธ€์„ ๊ฐœ์„ ํ•˜์—ฌ react-redux-provide ๋กœ ์ถœ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ https://github.com/rackt/redux/issues/419#issuecomment -149325401์„ ์ฐธ์กฐํ•˜์„ธ์š”.

#475์—์„œ ๊ณ„์†ํ•ด์„œ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•ด๋‚ธ ๊ฒƒ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋น„๋ก ์ด ๋…ผ์˜๊ฐ€ ํ˜„์žฌ react-redux ์†ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ;)

Sideways ํ• ๋‹น์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“ˆ์‹ ๊ณต๊ธ‰์ž

๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ, ๋‚ด๊ฐ€ ์ทจํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ์‹ ๊ณต๊ธ‰์ž๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ _truly_ "๋ฉ์ฒญํ•œ" ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ—ˆ์šฉํ•˜๊ณ , ๋ฌธ์ œ์˜ ์ตœ๋Œ€ ๋ถ„๋ฆฌ๋ฅผ ์‹œํ–‰ํ•˜๋ฉฐ, ๊ตํ™˜ ๊ฐ€๋Šฅํ•œ ๊ณต๊ธ‰์ž๋ฅผ ๋งค์šฐ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ณด๋‹ค ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์„ ์‹œํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ... actions , constants , containers , reducers ๋ฐ stores ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค(์˜ˆ์ œ ๋‚ด์—์„œ ๊ณตํ†ต ) ๋‹จ์ผ providers ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” providers ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๋…๋ฆฝ ์‹คํ–‰ํ˜• ๊ณต๊ธ‰์ž๋ฅผ ํŒจํ‚ค์ง•ํ•˜๊ณ  ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ํŠน์ • ์ ‘๊ทผ ๋ฐฉ์‹์ด ์ฑ„ํƒ๋˜๋ฉด ์ •๋ง ๋ฉ‹์ง„ ๊ฒƒ๋“ค์ด ๋‚˜์˜ฌ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค!! (์ฐธ๊ณ : ๋ฌผ๋ก  ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•  ํ•„์š”๋Š” ์—†์ง€๋งŒ 1) ํ›จ์”ฌ ๋” ์ฝ๊ธฐ/์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  2) ์ƒ์šฉ๊ตฌ๋ฅผ ์ค„์ด๊ณ  3) ๊ฐœ๋ณ„ ๊ณต๊ธ‰์ž๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ž‘์•„์„œ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. )

"๋ฉ์ฒญํ•œ" ๊ตฌ์„ฑ ์š”์†Œ์˜ ์˜ˆ

// components/Branch.js

import React, { Component } from 'react';
import provide from '../utilities/provide.js';
import { branchName, tree, toggle, open, theme } from '../common/propTypes.js';
import Limbs from './Limbs.js';

<strong i="22">@provide</strong>  // maybe require specifying expected props? e.g., @provide('theme')
export default class Branch extends Component {
  static propTypes = { branchName, tree, toggle, open, theme };

  onClick(event) {
    const { branchName, toggle } = this.props;  // toggle is from a provider

    event.stopPropagation();
    toggle(branchName);
  }

  render() {
    const props = this.props;
    const { branchName, tree, open, theme } = props;  // latter 3 from providers
    const classes = theme.sheet.classes || {};
    const imgSrc = open ? 'folder-open.png' : 'folder-closed.png';

    return (
      <div
        onClick={::this.onClick}
        className={classes.branch}
      >
        <h4 className={classes.branchName}>
          <img
            className={classes.branchIcon}
            src={theme.imagesDir+imgSrc}
          />

          <span>{branchName}</span>
        </h4>

        <Limbs
          tree={tree}
          open={open}
        />
      </div>
    );
  }
}

์˜ˆ์‹œ ์ œ๊ณต์ž

// providers/toggle.js

import createProvider from '../utilities/createProvider.js';

export const TOGGLE = 'TOGGLE';

export const actions = {
  toggle(fullPath) {
    return { type: TOGGLE, fullPath };
  }
};

export const reducers = {
  open(state = {}, action) {
    switch (action.type) {
      case TOGGLE:
        const { fullPath } = action;
        return { ...state, [fullPath]: !state[fullPath] };

      default:
        return state;
    }
  }
};

function merge (stateProps, dispatchProps, parentProps) {
  return Object.assign({}, parentProps, {
    open: !!stateProps.open[parentProps.fullPath]
  });
}

export const provider = createProvider(actions, reducers, merge);
export default provider;

์˜†์œผ๋กœ ํ• ๋‹น

์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ์•„์ด๋””์–ด๋Š” ์ž„์˜์˜ ๊ณต๊ธ‰์ž๋ฅผ "๋ฉ์ฒญํ•œ" ๊ตฌ์„ฑ ์š”์†Œ์— ์‰ฝ๊ฒŒ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์•ฑ์„ ๋งˆ์šดํŠธํ•  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

import assignProviders from './utilities/assignProviders.js';
import createStoreFromProviders from './utilities/createStoreFromProviders.js';

import dark from './themes/dark.js';
import github from './sources/github.js';
import * as providers from './providers/index.js';
import * as components from './components/index.js';

const { packageList, sources, toggle, theme } = providers;
const { Branches, Branch, Limbs, Limb } = components;

const initialState = {
  packageList: [
    'github:gaearon/react-redux<strong i="10">@master</strong>',
    'github:loggur/branches<strong i="11">@master</strong>',
    'github:rackt/redux<strong i="12">@master</strong>'
  ],
  sources: {
    github: github({
      token: 'abc123',
      auth: 'oauth'
    })
  },
  open: {
    'github': true,
    'github:gaearon': true,
    'github:gaearon/react-redux<strong i="13">@master</strong>': true,
    'github:rackt': true,
    'github:rackt/redux<strong i="14">@master</strong>': true
  },
  theme: dark
};

const store = createStoreFromProviders(providers, initialState);

assignProviders({ theme }, components);
assignProviders({ packageList }, { Branches });
assignProviders({ sources }, { Branches, Branch });
assignProviders({ toggle }, { Branch, Limb });

React.render(
  <Provider store={store}>
    {() => <Branches/>}
  </Provider>,
  document.getElementById('root')
);

์ด ๋ชจ๋“  ๊ฒƒ์ด ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ณ  ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค.

์ถ”๊ฐ€ ์ฝ”๋“œ

์ด ์˜ˆ์ œ์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ ๋ช‡ ๊ฐœ ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•ด ์„ค๊ณ„๋œ ๊ธฐ์กด redux ๋ฐ react-redux ๋ฉ”์†Œ๋“œ์˜ ์กฐํ•ฉ์ธ ์ž‘์€ ๋ชจ๋“ˆ(4๊ฐœ ์ค‘ 3๊ฐœ๋Š” ๋ช‡ ์ค„ ๊ธธ์ด์ž„)์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์— ๊ตญํ•œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์€ ๋‹จ์ง€ ์ผ์„ ๋” ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

// utilities/createProvider.js

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

/**
 * Creates an object to be used as a provider from a set of actions and 
 * reducers.
 *
 * <strong i="10">@param</strong> {Object} actions
 * <strong i="11">@param</strong> {Object} reducers
 * <strong i="12">@param</strong> {Function} merge Optional
 * <strong i="13">@return</strong> {Object}
 * <strong i="14">@api</strong> public
 */
export default function createProvider (actions, reducers, merge) {
  return {
    mapState(state) {
      const props = {};

      for (let key in reducers) {
        props[key] = state[key];
      }

      return props;
    },

    mapDispatch(dispatch) {
      return bindActionCreators(actions, dispatch);
    },

    merge
  };
}
// utilities/createStoreFromProviders.js

import { createStore, combineReducers } from 'redux';

import { createStore, applyMiddleware, combineReducers } from 'redux';

/**
 * Creates a store from a set of providers.
 *
 * <strong i="17">@param</strong> {Object} providers
 * <strong i="18">@param</strong> {Object} initialState Optional
 * <strong i="19">@return</strong> {Object}
 * <strong i="20">@api</strong> public
 */
export default function createStoreFromProviders (providers, initialState) {
  const reducers = {};
  const middleware = [];
  let create = createStore;

  for (let key in providers) {
    let provider = providers[key];

    Object.assign(reducers, provider.reducers);

    if (provider.middleware) {
      if (Array.isArray(provider.middleware)) {
        for (let mid of provider.middleware) {
          if (middleware.indexOf(mid) < 0) {
            middleware.push(mid);
          }
        }
      } else if (middleware.indexOf(provider.middleware) < 0) {
        middleware.push(provider.middleware);
      }
    }
  }

  if (middleware.length) {
    create = applyMiddleware.apply(null, middleware)(createStore);
  }

  return create(combineReducers(reducers), initialState);
}
// utilities/assignProviders.js

/**
 * Assigns each provider to each component.  Expects each component to be
 * decorated with `@provide` such that it has an `addProvider` static method.
 *
 * <strong i="5">@param</strong> {Object} providers
 * <strong i="6">@param</strong> {Object} components
 * <strong i="7">@api</strong> public
 */
export default function assignProviders (providers, components) {
  for (let providerName in providers) {
    let provider = providers[providerName];

    if (provider.default) {
      provider = provider.default;
    } else if (provider.provider) {
      provider = provider.provider;
    }

    for (let componentName in components) {
      let addProvider = components[componentName].addProvider;
      if (typeof addProvider === 'function') {
        addProvider(providerName, provider);
      }
    }
  }
}

๋งˆ์ง€๋ง‰์œผ๋กœ provide ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜†์œผ๋กœ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ connect ์˜ ์ˆ˜์ •๋œ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

// utilities/provide.js

import React, { Component, PropTypes } from 'react';
import createStoreShape from 'react-redux/lib/utils/createStoreShape';
import shallowEqual from 'react-redux/lib/utils/shallowEqual';
import isPlainObject from 'react-redux/lib/utils/isPlainObject';
import wrapActionCreators from 'react-redux/lib/utils/wrapActionCreators';
import invariant from 'invariant';

const storeShape = createStoreShape(PropTypes);
const defaultMapState = () => ({});
const defaultMapDispatch = dispatch => ({ dispatch });
const defaultMerge = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
});

// Helps track hot reloading.
let nextVersion = 0;

export default function provide (WrappedComponent) {
  const version = nextVersion++;
  const providers = [];
  let shouldSubscribe = false;

  function getDisplayName () {
    return ''
      +'Provide'
      +(WrappedComponent.displayName || WrappedComponent.name || 'Component')
      +'('+providers.map(provider => provider.name).join(',')+')';
  }

  function addProvider (name, { mapState, mapDispatch, merge }) {
    if (Boolean(mapState)) {
      shouldSubscribe = true; 
    }

    providers.push({
      name,
      mapState: mapState || defaultMapState,
      mapDispatch: isPlainObject(mapDispatch)
        ? wrapActionCreators(mapDispatch)
        : mapDispatch || defaultMapDispatch,
      merge: merge || defaultMerge
    });

    Provide.displayName = getDisplayName();
  }

  function computeStateProps (store) {
    const state = store.getState();
    const stateProps = {};

    for (let provider of providers) {
      let providerStateProps = provider.mapState(state);

      invariant(
        isPlainObject(providerStateProps),
        '`mapState` must return an object. Instead received %s.',
        providerStateProps
      );

      Object.assign(stateProps, providerStateProps);
    }

    return stateProps;
  }

  function computeDispatchProps (store) {
    const { dispatch } = store;
    const dispatchProps = {};

    for (let provider of providers) {
      let providerDispatchProps = provider.mapDispatch(dispatch);

      invariant(
        isPlainObject(providerDispatchProps),
        '`mapDispatch` must return an object. Instead received %s.',
        providerDispatchProps
      );

      Object.assign(dispatchProps, providerDispatchProps);
    }

    return dispatchProps;
  }

  function computeNextState (stateProps, dispatchProps, parentProps) {
    const mergedProps = {};

    for (let provider of providers) {
      let providerMergedProps = provider.merge(
        stateProps, dispatchProps, parentProps
      );

      invariant(
        isPlainObject(providerMergedProps),
        '`merge` must return an object. Instead received %s.',
        providerMergedProps
      );

      Object.assign(mergedProps, providerMergedProps);
    }

    return mergedProps;
  }

  const Provide = class extends Component {
    static displayName = getDisplayName();
    static contextTypes = { store: storeShape };
    static propTypes = { store: storeShape };
    static WrappedComponent = WrappedComponent;
    static addProvider = addProvider;

    shouldComponentUpdate(nextProps, nextState) {
      return !shallowEqual(this.state.props, nextState.props);
    }

    constructor(props, context) {
      super(props, context);
      this.version = version;
      this.store = props.store || context.store;

      invariant(this.store,
        `Could not find "store" in either the context or ` +
        `props of "${this.constructor.displayName}". ` +
        `Either wrap the root component in a <Provider>, ` +
        `or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
      );

      this.stateProps = computeStateProps(this.store);
      this.dispatchProps = computeDispatchProps(this.store);
      this.state = { props: this.computeNextState() };
    }

    recomputeStateProps() {
      const nextStateProps = computeStateProps(this.store);
      if (shallowEqual(nextStateProps, this.stateProps)) {
        return false;
      }

      this.stateProps = nextStateProps;
      return true;
    }

    recomputeDispatchProps() {
      const nextDispatchProps = computeDispatchProps(this.store);
      if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
        return false;
      }

      this.dispatchProps = nextDispatchProps;
      return true;
    }

    computeNextState(props = this.props) {
      return computeNextState(
        this.stateProps,
        this.dispatchProps,
        props
      );
    }

    recomputeState(props = this.props) {
      const nextState = this.computeNextState(props);
      if (!shallowEqual(nextState, this.state.props)) {
        this.setState({ props: nextState });
      }
    }

    isSubscribed() {
      return typeof this.unsubscribe === 'function';
    }

    trySubscribe() {
      if (shouldSubscribe && !this.unsubscribe) {
        this.unsubscribe = this.store.subscribe(::this.handleChange);
        this.handleChange();
      }
    }

    tryUnsubscribe() {
      if (this.unsubscribe) {
        this.unsubscribe();
        this.unsubscribe = null;
      }
    }

    componentDidMount() {
      this.trySubscribe();
    }

    componentWillReceiveProps(nextProps) {
      if (!shallowEqual(nextProps, this.props)) {
        this.recomputeState(nextProps);
      }
    }

    componentWillUnmount() {
      this.tryUnsubscribe();
    }

    handleChange() {
      if (this.recomputeStateProps()) {
        this.recomputeState();
      }
    }

    getWrappedInstance() {
      return this.refs.wrappedInstance;
    }

    render() {
      return (
        <WrappedComponent ref='wrappedInstance' {...this.state.props} />
      );
    }
  }

  if ((
    // Node-like CommonJS environments (Browserify, Webpack)
    typeof process !== 'undefined' &&
    typeof process.env !== 'undefined' &&
    process.env.NODE_ENV !== 'production'
   ) ||
    // React Native
    typeof __DEV__ !== 'undefined' &&
    __DEV__ //eslint-disable-line no-undef
  ) {
    Provide.prototype.componentWillUpdate = function componentWillUpdate () {
      if (this.version === version) {
        return;
      }

      // We are hot reloading!
      this.version = version;

      // Update the state and bindings.
      this.trySubscribe();
      this.recomputeStateProps();
      this.recomputeDispatchProps();
      this.recomputeState();
    };
  }

  return Provide;
}

provide ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ๋Œ€ํ•ด ์–ธ๊ธ‰ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ํ•œ ๊ฐ€์ง€๋Š” react-devtools ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ๊ฒƒ์„ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค( Branches ๋Š” ProvideBranches(theme,packageList,sources) ):
2015-08-15-010648_1366x768_scrot

์ด ๋Œ€์‹ ( Branches ๊ฐ€ Connect(Branches) ๋ž˜ํ•‘๋˜๋Š” ์›๋ž˜ connect ์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Œ):
2015-08-14-220140_1366x768_scrot

์ œํ•œ ์‚ฌํ•ญ

์ดํ‹€ ์ „์— redux ์— ๋Œ€ํ•ด ๋ฐฐ์šฐ๊ธฐ ์‹œ์ž‘ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์†”์งํžˆ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๊ฒฐ๊ณผ๋กœ ์–ด๋–ค ์ œํ•œ(์žˆ๋Š” ๊ฒฝ์šฐ)์ด ์žˆ๋Š”์ง€ ์ „ํ˜€ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๋‚ด ๋จธ๋ฆฌ ๊ผญ๋Œ€๊ธฐ์—์„œ, ๋‚˜๋Š” ์ •๋ง๋กœ ์•„๋ฌด ๊ฒƒ๋„ ์ƒ๊ฐํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๋ชจ๋“  ๊ฒƒ์ด ๋‚ด ์‚ฌ์šฉ ์‚ฌ๋ก€์— ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์•„๋งˆ๋„ ๋” ๊ฒฝํ—˜์ด ๋งŽ์€ ์‚ฌ๋žŒ์ด ์•Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํŠน์ • ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ƒ๊ฐํ•ด ๋‚ธ ์ด์œ ๋Š” ํ• ๋‹น๋œ ๊ณต๊ธ‰์ž๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” _์ •๋ง_ "๋ฉ์ฒญํ•œ" ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ๋‹ค๋Š” ์•„์ด๋””์–ด๊ฐ€ ๋งˆ์Œ์— ๋“ค๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๊ด€์‹ฌ์‚ฌ์˜ ์ง„์ •ํ•œ ๋ถ„๋ฆฌ๋ฅผ ์‹œํ–‰ํ•˜๊ณ  ๋ชจ๋“ˆ๋กœ ์‰ฝ๊ฒŒ ๊ตํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ์ œ๊ณต์ž๋ฅผ ์›ํ•˜๋Š” ์ˆ˜๋งŒํผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ @gaearon ์ด ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ข‹์•„ํ•˜๊ณ  react-redux ๋ฒ”์œ„์— ํ•ด๋‹นํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์ถ”๊ฐ€๋œ PR์„ ์ œ์ถœํ•˜๊ฒŒ ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ react-redux-providers ๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฒŒ์‹œํ•œ ๋‹ค์Œ ๊ฒฐ๊ตญ์—๋Š” ์‹ค์ œ ์‚ฌ๋ก€์™€ ํ•จ๊ป˜ ์˜ˆ์ œ ๊ณต๊ธ‰์ž(์˜ˆ: react-redux-provide-toggle )๋ฅผ ๊ฒŒ์‹œํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ค‘๊ฐ„ ์ˆ˜์ค€ ๊ตฌ์„ฑ ์š”์†Œ์˜ ๋ถˆํ•„์š”ํ•œ ๋‹ค์‹œ ๋ Œ๋”๋ง์„ ํ”ผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—?

์˜ˆ

์ง€๊ธˆ ์ทจํ•œ ๋ฐฉํ–ฅ์€ ์ƒํƒœ์— Immutable.js๋ฅผ ์‚ฌ์šฉํ•˜๊ณ (์•ฑ ์ƒํƒœ์™€ UI ์ƒํƒœ ๋ชจ๋‘, ์ผ๋ถ€ UI ์ƒํƒœ๋Š” ๊ตฌ์„ฑ ์š”์†Œ์— ์œ ์ง€) ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ์— PureRenderMixin์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํ•˜ํ–ฅ์‹ ํ๋ฆ„์„ ์‚ฌ์šฉํ•  ๋•Œ์˜ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ œ๊ฑฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๊นŒ?

ํŽธ์ง‘: ๋‚ฎ์€ ์ˆ˜์ค€์˜ ๊ตฌ์„ฑ ์š”์†Œ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์—ฌ์ „ํžˆ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์ค‘๊ฐ„ ์ˆ˜์ค€ ๊ตฌ์„ฑ ์š”์†Œ์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ œ๊ฑฐํ•˜์ง€ ๋ชปํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋งŽ์€ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝํ—˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@danmaz74 ์šฐ๋ฆฌ๋Š” ์ด์™€ ๊ฐ™์€ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ

@eldh ์—…๋ฐ์ดํŠธ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜๋Š” ๋™์•ˆ ์ฐธ๊ณ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. :)

์ด์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? timbur์˜ ์˜ˆ๋Š” ๋‚˜์—๊ฒŒ ์™„๋ฒฝํ•˜๊ฒŒ ์ดํ•ด๋˜์ง€๋งŒ ๋‹จ์ผ ์—ฐ๊ฒฐ ์—ฐ์Šต์€ ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋ฐ˜๋Œ€ ๋…ผ์ฆ, ์ฆ‰ "์‚ฌ๋žŒ๋“ค์€ ์—ฌ๊ธฐ์—์„œ ์„ ํ˜ธํ•˜๋Š” ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค"์— ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ผ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํƒœ๋ฅผ 10๊ฐœ์˜ ๊ตฌ์„ฑ ์š”์†Œ ๊นŠ์€ ๊ณ„์ธต ๊ตฌ์กฐ๋กœ ์™„์ „ํžˆ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ๊ฑฐ๋Œ€ํ•œ mapStateToProps ํ•จ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค... ๊ทธ ์ด๋ฉด์˜ ์ƒ๊ฐ์ด ๋ฌด์—‡์ธ์ง€ ๋˜๋Š” ๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ์˜คํ•ดํ•˜๊ณ  ์žˆ๋Š”์ง€ ์•ฝ๊ฐ„ ํ˜ผ๋ž€์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค...

๋‹จ์ผ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฑฐ๋Œ€ํ•œ mapStateToProps ํ•จ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ฌด๋„ ๋‹จ์ผ ์—ฐ๊ฒฐ์„ ์˜นํ˜ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ react-redux์˜ connect() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Redux์— ์—ฐ๊ฒฐํ•˜๋ ค๋Š” ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ƒ์œ„ ๊ตฌ์„ฑ ์š”์†Œ ๋˜๋Š” ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ ์— ๋Œ€ํ•ด์„œ๋งŒ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์‹ญ์‹œ์˜ค. ๊ธฐ์ˆ ์ ์œผ๋กœ ์•ฑ์˜ ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ Redux ์Šคํ† ์–ด์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋„ˆ๋ฌด ๊นŠ๊ฒŒ ์—ฐ๊ฒฐ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ธฐ๊ฐ€ ๋” ์–ด๋ ค์›Œ์ง€๋ฏ€๋กœ

"๋‹จ์ผ"์€ ์˜ˆ์ œ์—์„œ ๋งŒ๋“  ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์€ ์•ฑ๋งŒ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ๋” ๋ช…ํ™•ํžˆ ํ•˜๊ธฐ ์œ„ํ•ด ์ž์œ ๋กญ๊ฒŒ ๋ฌธ์„œ๋ฅผ ์ˆ˜์ •ํ•˜์‹ญ์‹œ์˜ค. ๋‚˜๋Š” ์ง€๊ธˆ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋กœ ๋ฐ”์˜๋ฏ€๋กœ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ PR์„ ํ•˜์ง€ ์•Š๋Š” ํ•œ ์ด ๋ฌธ์ œ๊ฐ€ ์–ด๋–ค ์›€์ง์ž„๋„ ๊ธฐ๋Œ€ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ๋‹น์‹ ๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ช…ํ™•ํ•˜๊ฒŒ ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์นจ๋‚ด react-redux-provide ์ถœ์‹œ์— ์ด๋ฅด๋ €์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ํ™•์ธ

์•„์ฃผ ์ข‹์•„ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

์ €๋Š” ์ƒˆ๋กœ์šด redux ํ”„๋กœ์ ํŠธ๋ฅผ ๋ง‰ ์‹œ์ž‘ํ–ˆ๊ณ  '์Šค๋งˆํŠธ' ๊ตฌ์„ฑ ์š”์†Œ์— ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋‚˜์—๊ฒŒ ํ›จ์”ฌ ๋” ์ดํ•ด๊ฐ€ ๋˜๋ฉฐ ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์ด ์žˆ๋‹ค๋ฉด ์ถ”๊ฐ€ ์Šน๋ฆฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ, ๋ชจ๋“  ์ œ์–ด๋ฅผ ๊ธฐ๋ณธ ์•ฑ์ด๋‚˜ ๋ผ์šฐํ„ฐ๊นŒ์ง€ ๋ฒ„๋ธ”๋งํ•˜๋ฉด SRP๊ฐ€ ์™„์ „ํžˆ ์†์‹ค๋ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋ฅผ ๋ถ„ํ•ดํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ์•ฑ์ด ์–ผ๋งˆ๋‚˜ ์ปค์•ผ ํ• ๊นŒ์š”?

๊ด€๋ จ ์š”์†Œ๋ฅผ ๊ตฌ์„ฑ ์š”์†Œ ํด๋”๋กœ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์ƒ๊ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์„ฑ๋ถ„ ์˜†์— ๊ฐ์†๊ธฐ๋ฅผ ๋‘์‹ญ์‹œ์˜ค.

๊ถ๊ทน์ ์œผ๋กœ ๋‚˜๋Š” redux/flux๊ฐ€ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์— ๋Œ€ํ•œ ํฐ ์ด์ ์ด๋ผ๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ‘œ์ค€ mv์—์„œ ๊ทธ๋Ÿฌํ•œ ์ •์‹ ์  ๋ณ€ํ™”-UI ์•ฑ ๊ฐœ๋ฐœ์„ ๊ฐ„๋‹จํ•˜๊ณ  ๋ˆ„๊ตฌ๋‚˜ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ๊ฒƒ์ด ๋ฌด์—‡์ด๋“  ๊ฐ„์—, ๊ฒฐ๊ตญ์—๋Š” ๊ทธ ํ”Œ๋Ÿญ์Šค๊ฐ€ ์ถ”์ƒํ™”๋˜๊ณ  ์šฐ๋ฆฌ๋Š” ์ด๋™ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. mv*์™€ ๋” ๋น„์Šทํ•˜๊ฒŒ ๋ณด์ด๋Š” ๊ฒƒ์œผ๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ #1285์—์„œ ์ˆ˜์ •๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์„œ์—์„œ ๋” ์ด์ƒ ์ปจํ…Œ์ด๋„ˆ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
http://redux.js.org/docs/basics/UsageWithReact.html

์•ˆ๋…•ํ•˜์„ธ์š”, ์—ฌ๊ธฐ์— ๋„์›€์ด ๋  ๋งŒํ•œ ๋ช‡ ๊ฐ€์ง€ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์ผ์Šต๋‹ˆ๋‹ค. :)

https://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1

https://github.com/reactjs/redux/issues/419#issuecomment -183769392๋„ #1353์— ๋„์›€์ด ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

@timbur ๋ฉ‹์ง„ ๊ธฐ์‚ฌ! ์ด ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ƒ๊ฐ์„ ๊ณต์œ ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? https://github.com/reactjs/react-redux/issues/278

์ด๊ฒƒ์ด ๋ˆˆ์— ๋„๊ฒŒ ๋ถ„๋ช…ํ•œ์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด ์—ฌ๊ธฐ์— ์ถ”๊ฐ€ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์–ธ๊ธ‰๋œ ๋งŽ์€ ๋‚ด์šฉ์ด ์ด ์Šค๋ ˆ๋“œ์— ์˜ค๋Š” redux๋ฅผ ์ฒ˜์Œ ์ ‘ํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” ๋‹ค์†Œ ์ถ”์ƒ์ ์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ์ฒ˜์Œ redux๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ ๋‚ด ์•ฑ์ด ๋งŽ์€ ๋ถ„๋ฆฌ๋ฅผ ํ•„์š”๋กœ ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— (๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•œ ์˜ค๋ž˜๋œ ๋ฉ์ฒญํ•œ) "๊ตฌ์„ฑ ์š”์†Œ" ์•ˆ์— "์ปจํ…Œ์ด๋„ˆ"(์—ฐ๊ฒฐ๋œ ๊ตฌ์„ฑ ์š”์†Œ)๋ฅผ ์ž˜๋ชป ์ ์œผ๋กœ ํ‘œ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์–ผ๋งˆ๋‚˜ ํ‹€๋ ธ์–ด. ์ด๋Ÿฌํ•œ ๊ตฌ์„ฑ ์š”์†Œ ์ค‘ ๋งŽ์€ ๋ถ€๋ถ„์„ ์žฌ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ฌ์•˜์„ ๋•Œ ์ƒ๋‹นํ•œ ์–‘์˜ ๋ฆฌํŒฉํ† ๋ง์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ–ˆ๊ณ  ๋งŽ์€ ์Šค๋งˆํŠธ ํ•ญ๋ชฉ์„ ํŠธ๋ฆฌ ๋งจ ์œ„๋กœ ์˜ฎ๊ฒผ์ง€๋งŒ ๊ณง ๋‹ค๋ฃจ๊ธฐ ์–ด๋ ค์›Œ์กŒ์Šต๋‹ˆ๋‹ค. ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ƒ๋‹จ์˜ "์ œ๊ณต์ž"(ํ•„์š”ํ•œ ๋Œ€๋กœ)๋ฟ ์•„๋‹ˆ๋ผ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ์•ฑ(ํ•˜์ง€ ์•Š์•„์•ผ ํ•จ)๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ์ ‘๊ทผํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๋งจ ์œ„์— ๊ณต๊ธ‰์ž๊ฐ€ ์žˆ๋Š” ๋ฒ™์–ด๋ฆฌ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—๋งŒ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค . ์•ฑ์€ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ์˜ ๊ณ„์ธต ๊ตฌ์กฐ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ข…์ข… ๋ชฉ๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์Šค๋งˆํŠธ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ†ตํ•ด ID๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ID๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์€ ๋ฌด์–ธ๊ฐ€๊ฐ€ ์•ฑ์˜ ๋„๋ฉ”์ธ์— ์†ํ•œ๋‹ค๋Š” ํ‘œ์‹œ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ํ•œ ์ปจํ…Œ์ด๋„ˆ์— ์žˆ๋Š” ID ๋ชฉ๋ก์„ ๊ฐ€์ ธ์™€์„œ ID๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ปจํ…Œ์ด๋„ˆ ๋‚ด์—์„œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ID ์—†์ด๋„ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์—์„œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œ์‹œํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ๊ณ„์ธต์„ ํ†ตํ•ด ์•ฑ์˜ ์—ฐ๊ฒฐ๋œ ๋ถ€๋ถ„์„ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์˜ (๋ณต์žกํ•œ) ์˜ˆ์ œ๋ฅผ ์กฐ๋กฑํ–ˆ์Šต๋‹ˆ๋‹ค.

// Provider component that renders some containers and some components and provides the store
class TodoAppProvider {
  constructor() {
    // setup store etc.
  }

  render() {
    return (
      <Provider store={this.store}> {/* Provider from 'react-redux' */}
        <AppLayoutComponent title="My Todos" footer={<TodoFooter />}>
          <TodoListsContainer />
        </AppLayoutComponent>
      </Provider>
    );
  }
);

// AppLayoutComponent
// Lots of nice css, other dumb components etc. no containers!
export default const AppLayoutComponent = ({ title, children, footer }) => (
  <header>
    {title}
  </header>
  <main>
    {children /* This variable can be a container or components but it's not hardcoded! */}
  </main>
  <footer>
    {footer}
  </footer>
);

// TodoFooter
// Another dumb component
export default const TodoFooter = () => (
  <footer>
    &copy; {Date.now() /* we are copyrighted to the millisecond */}
  </footer>
);

// TodoListsContainer
// Smart component that renders all the lists
class TodoListsContainer extends React.Component {
  render() {
    return () {
      <div>
        {todoLists.map(id => (
          {/* this container renders another container */ }
          <TodoListContainer key={id} todoListId={id} />
        ))}
      </div>
    }
  }
}

const mapStateToProps = state => ({
  todoLists: getTodoLists(state),
});

export default connect(mapStateToProps)(TodoListsContainer);

// TodoListContainer
// Gets the props and visibleTodo IDs for the list
class TodoListContainer {
  render() {
    const { id, title, visibleTodos } = this.props;
    return (
      <div>
        {/* Render a component but passes any connected data in as props / children */}
        <TodoListPanelComponent title={title}>
          {visibleTodos.map(id => (
            <TodoContainer todoId={id} />
          ))}
        </TodoListPanelComponent>
      </div>
    );
  }
}

const mapStateToProps = (state, { todoListId }) => ({
  ...getTodoList(state, todoListId), // A todo object (assume we need all the attributes)
  visibleTodos: getVisibleTodos(state, todoListId), // returns ids
});

export default connect(mapStateToProps)(TodoListContainer);


// TodoListPanelComponent
// render the panel to sit the todos in
// children should be todos
// No containers!
export default const TodoListPanelComponent = ({ title, children }) => (
  <div>
    <h3>{title}</h3>
    <div>
      {children}
    </div>
  </div>
);

// TodoContainer
// This just wraps the TodoComponent and passed the props
// No separate class or JSX required!
const mapStateToProps = (state, { todoId }) => ({
  ...getTodo(state, todoId),
});

const mapDispatchToProps = (dispatch, { todoListId }) => ({
  handleFilter: () => dispatch(hideTodo(id)), // Pass ALL smart stuff in
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoComponent); // Passing in the component to connect

// TodoComponent
// Render the component nicely; again, as all of its connected stuff passed in
// The FilterLinkContainer is an example of a smell that will come back to bite you!
export default const TodoComponent = ({ content, isComplete, handleFilter }) => (
  <div>
    <div>
      {content}
    </div>
    <div>
      {isComplete ? 'โœ“' : 'โœ—'}
    </div>
    <div>
      {/* Don't do this, can't re-use TodoComponent outside the app context! */}
      <FilterLinkContainer />

      {/* Instead do this (or similar), component can be reused! */}
      <Link onClick={handleFilter}>
        'Filter'
      </Link>
    </div>
  </div>
);

๋”ฐ๋ผ์„œ ์—ฌ๊ธฐ์„œ ์ปจํ…Œ์ด๋„ˆ ๊ณ„์ธต ๊ตฌ์กฐ๋Š” TodoAppProvider > TodoListsContainer > TodoListContainer > TodoContainer ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์€ ๊ฐ๊ฐ ์„œ๋กœ์— ์˜ํ•ด ๋ Œ๋”๋ง๋˜๊ณ  ๊ตฌ์„ฑ ์š”์†Œ ๋‚ด๋ถ€์—์„œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์œผ๋ฉฐ ์›์‹œ ๋ณด๊ธฐ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(React ๋ž˜ํ•‘ ์ด์œ ๋กœ ๊ฐ€๋” div ์ œ์™ธ).

๊ถ๊ทน์ ์œผ๋กœ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๋ฐฉ์‹์€ ๋งˆ์น˜ ์ฒ˜์Œ์— UI๊ฐ€ ๊ด€์‹ฌ์„ ๊ฐ–๋Š” ์œ ์šฉํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋งคํ•‘ํ•˜๋Š” ์—ฐ๊ฒฐ๋œ ๊ตฌ์„ฑ ์š”์†Œ์˜ ํŠธ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ UI๋Š” ์ „ํ˜€ ์—†์œผ๋ฉฐ ์ปจํ…Œ์ด๋„ˆ์˜ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด ๋งคํ•‘๋œ ์ƒํƒœ ํŠธ๋ฆฌ ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ํ›„, ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ(์ฆ‰, DOM์—์„œ) ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์— ํ”„๋ฆฌ์  ํ…Œ์ด์…˜ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ดํŽด๋ณด๊ณ  ๋ฟŒ๋ฆฝ๋‹ˆ๋‹ค. ๋ถ„๋ช…ํžˆ ์ด 2๋‹จ๊ณ„ ๋ฐฉ์‹์œผ๋กœ ์•ฑ์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์œ ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ์ด์™€ ๊ฐ™์€ ๋ชจ๋ธ์„ ๊ฐœ๋…ํ™”ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

connect() ๋‘ ๋ฒˆ ์ด์ƒ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜(๋˜๋Š” ๋‹ค๋ฅธ ์•„ํ‚คํ…์ฒ˜์ƒ์˜ ์ด์œ )๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์—ฐ๊ฒฐ ์ง„์ž…์ ์„ ์ถ”์ƒํ™”ํ•˜์—ฌ ๊ตฌ์„ฑ ์š”์†Œ์— ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์†Œํ’ˆ์„ ์ œ๊ณตํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

// connectCommonProps.js (mergeProps not included for the sake of simplicity)

const _mapStateToProps = (state) => ({ [often used slices of state] });

const _mapDispatchToProps = (dispatch) => ({ [often used actions] });

const connectCommonProps = (mapStateToProps, mapDispatchToProps, component) => {
    // First connect
    const connectedComponent = connect(mapStateToProps, mapDispatchToProps)(component);

    // Second connect
    return connect(_mapStateToProps, _mapDispatchToProps)(connectedComponent);
};

export default connectMapAndFieldProps;
// Some component that needs the often used props
...
export default connectCommonProps(..., ..., Component);

๋‚˜๋Š” ์—ฌ๊ธฐ์—์„œ ๊ฒŒ์œผ๋ฅด๊ณ  ์„ ์–ธ์„ ๋‹จ์ˆœํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— mapStateToProps ์˜ ๋‘ ๊ฐ€์ง€ ๋ฒ„์ „๊ณผ mapDispatchToProps ์˜ ๋‘ ๊ฐ€์ง€ ๋ฒ„์ „์„ ๊ฒฐํ•ฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ connect ๊ฐ€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ๋‚˜์œ ์ƒ๊ฐ์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

@timotgl : Dan์€ ๋” ์ด์ƒ ํ™œ์„ฑ ์œ ์ง€ ๊ด€๋ฆฌ์ž๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. Dan์—๊ฒŒ ์ง์ ‘ ping์„ ๋ณด๋‚ด์ง€ ๋งˆ์‹ญ์‹œ์˜ค.

์—ฌ๋Ÿฌ ๊ตฌ์„ฑ ์š”์†Œ ์—ฐ๊ฒฐ ์—

๊ฐœ์ธ์ ์œผ๋กœ ๋‹ค๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‹œ๋„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. "๊ณตํ†ต props" HOC ๋˜๋Š” ๋Œ€๋ถ€๋ถ„ ์ž์‹์—๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๋ฌด์–ธ๊ฐ€๊ฐ€ ์žˆ๊ฑฐ๋‚˜ ์„ ํƒ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๊ตฌ์„ฑ ์š”์†Œ์˜ mapState ํ•จ์ˆ˜์—์„œ ๊ณตํ†ต props๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ํ•„์š”ํ•œ ํŠน์ • props์™€ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค.

@markerikson ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค ๋ชฐ๋ž์Šต๋‹ˆ๋‹ค ๋ฉ˜์…˜์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์šฐ์„  ์ž‘๋™ํ•˜๊ณ  ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋ฐ˜์‘ ๊ฐœ๋ฐœ ๋„๊ตฌ์˜ ๋‹ค๋ฅธ ์—ฐ๊ฒฐ๋œ ๊ตฌ์„ฑ ์š”์†Œ์ฒ˜๋Ÿผ ๋‚˜ํƒ€๋‚˜๋ฉฐ ์ถ”๊ฐ€ ๋ž˜ํผ ๋˜๋Š” ์ด์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” OOP/์ƒ์† ํŒจ๋Ÿฌ๋‹ค์ž„์„ ํฌํ•จํ•˜๊ณ  ์‹ถ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— HOC์— ๋ฐ˜๋Œ€ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ์— ๋” ๋งŽ์€ ์†Œํ’ˆ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ผ ๋ฟ์ด๋ฏ€๋กœ ๋™์ž‘์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

mapStateToProps ์—์„œ ๋ฐฐ์„ ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์ข‹์€ ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€๋งŒ ์ ์–ด๋„ 2๊ฐœ์˜ ์ง„์ž…์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ•˜๋‚˜์˜ ๋„์šฐ๋ฏธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ๋” ๊ฐ„๋‹จํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

"2๊ฐœ์˜ ์ง„์ž…์ "์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import {selectCommonProps} from "app/commonSelectors";

import {selectA, selectB} from "./specificSelectors";

const mapState = (state) => {
    const propA = selectA(state);
    const propB = selectB(state);
    const commonProps = selectCommonProps(state);

    return {a, b, ...commonProps};
}

@markerikson ๋‘ ๊ฐœ์˜ ์ง„์ž…์ ์œผ๋กœ mapDispatchToProps ๋Œ€ํ•ด ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๊ณ  ์ž ์žฌ์ ์œผ๋กœ mergeProps ๋Œ€ํ•ด์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

mergeProps ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ํƒˆ์ถœ๊ตฌ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ผ๋ฐ˜์ ์œผ๋กœ ์‹ค์ œ mapDispatch ํ•จ์ˆ˜๋ฅผ ์‹ค์ œ๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ๋Œ€์‹  "๊ฐ์ฒด ์†๊ธฐ"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

import {addTodo, toggleTodo} from "./todoActions";

class TodoList extends Component {}

const actions = {addTodo, toggleTodo};
export default connect(mapState, actions)(TodoList);

๋ชจ๋“  "๊ณตํ†ต" ์ž‘์—… ์ž‘์„ฑ์ž๋ฅผ ๋‹ค์‹œ ๋‚ด๋ณด๋‚ด๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ผ๋ถ€ index.js ํŒŒ์ผ์„ ์‰ฝ๊ฒŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import * as commonActions from "app/common/commonActions";
import {specificAction1, specificAction2} from "./actions";

const actionCreators = {specificAction1, specificAction2, ...commonActions};

export default connect(null, actionCreators)(MyComponent);

mergeProps ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ํƒˆ์ถœ๊ตฌ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@markerikson๋‹˜ ์•ˆ๋…•ํ•˜์„ธ์š”, ๋ˆ„๊ตฐ๊ฐ€ mergeProps ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. mapDispatchToProps์˜ ๋‚ด ์ž‘์—…์— ํ•„์š”ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์•„๋‹Œ mapStateToProps์—์„œ ์†Œํ’ˆ์„ "์ˆจ๊ธฐ๊ธฐ"ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์œ ์ผ์ž…๋‹ˆ๊นŒ?

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰