Reactivecocoa: ํ™•์ธ์ด ํ•„์š”ํ•œ ๋ช…๋ น์€ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๊นŒ?

์— ๋งŒ๋“  2014๋…„ 03์›” 10์ผ  ยท  9์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: ReactiveCocoa/ReactiveCocoa

๋‚˜๋Š” ์ด๊ฒƒ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋” ๋ฐ˜์‘์ ์ธ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ํ™•์‹ ํ•˜์ง€๋งŒ ๋‚˜๋Š” ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ๋จธ๋ฆฌ๋ฅผ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ํŒŒ๊ดด์ ์ธ ์ž‘์—…(์ด ๊ฒฝ์šฐ ์‚ญ์ œ)์„ ์ˆ˜ํ–‰ํ•˜๋Š” UIButton์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ๋ณด๊ธฐ ๋ชจ๋ธ์—๋Š” ์‚ญ์ œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ช…๋ น์„ ๋ฐ˜ํ™˜ํ•˜๋Š” deleteCommand ์†์„ฑ์ด ์žˆ์œผ๋ฉฐ ๋ฒ„ํŠผ์„ ๋ช…๋ น์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋‚ด๊ฐ€ ์ •๋ง๋กœ ์›ํ•˜๋Š” ๊ฒƒ์€ ๋ฒ„ํŠผ์„ ํƒญํ–ˆ์„ ๋•Œ ํ™•์ธ UIAlertView ์ด ํ‘œ์‹œ๋˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ํ™•์ธ์„ ๋ˆ„๋ฅด๋ฉด ์‚ญ์ œ๊ฐ€ ์ˆ˜ํ–‰๋˜๊ณ  ์ทจ์†Œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์‚ญ์ œ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด "ํ™•์ธ" ๋™์ž‘์€ ๋‚ด ๋ทฐ ๋™์ž‘์˜ ๊ธฐ๋ณธ์ ์ธ ๋ถ€๋ถ„์ด๋ฏ€๋กœ ๋ทฐ ๋ชจ๋ธ์— ์†ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค. ๋จผ์ € ํ™•์ธ ์—†์ด ์‚ญ์ œ๋ฅผ ํ—ˆ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ํ™•์ธ์„ ์–ป๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์‚ฌํ•ญ(์˜ˆ: UIAlertView )์€ ๋ทฐ์— ๋Œ€ํ•ด ์•„๋ฌด ๊ฒƒ๋„ ์•Œ์ง€ ๋ชปํ•˜๋ฏ€๋กœ ๋ทฐ ๋ชจ๋ธ์— ์†ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฒฝ๊ณ ์˜ ์‹ค์ œ ํ‘œ์‹œ๋Š” ๋ณด๊ธฐ ์ปจํŠธ๋กค๋Ÿฌ์— ์†ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ UIButton ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ํ™•์ธ๊ณผ ํ•จ๊ป˜ ์‚ญ์ œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํ•˜๋‚˜์˜ RACCommand ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฉ‹์ง€๊ณ  ์šฐ์•„ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

question

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

๋ณด๊ธฐ ๋ชจ๋ธ์—์„œ AlertViewModel s๋ฅผ ๋ณด๋‚ด๋Š” alerts ์‹ ํ˜ธ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฒ„ํŠผ์— ์ ์ ˆํ•œ ๋ช…๋ น์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋‚ด ์•ฑ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.

์ด "ํ™•์ธ" ๋™์ž‘์€ ๋‚ด ๋ทฐ ๋™์ž‘์˜ ๊ธฐ๋ณธ์ ์ธ ๋ถ€๋ถ„์ด๋ฏ€๋กœ ๋ทฐ ๋ชจ๋ธ์— ์†ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค. ๋จผ์ € ํ™•์ธ ์—†์ด ์‚ญ์ œ๋ฅผ ํ—ˆ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

๋‚˜๋Š” ๊ฐœ์ธ์ ์œผ๋กœ ๊ทธ๋ƒฅ ๊ฐ„๋‹จํ•œ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ๊ฐˆ ๊ฒƒ : ๋ฐ”์ธ๋“œ ๋ฒ„ํŠผ์˜ enabled ๋ช…๋ น์— ์•„๋‹Œ ๋ช…๋ น ์ž์ฒด, ์ˆ˜๋™ ๋ช…๋ น์˜ ํ˜ธ์ถœ -execute: ๋กœ๋ถ€ํ„ฐ UIAlertView .

์˜ˆ ๊ธฐ๋ณธ์ ์œผ๋กœ Denis๊ฐ€ ๋งํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ–ˆ๋‹ค.
๋ณด๊ธฐ ๋ชจ๋ธ์—๋Š” ๋‹ค์Œ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ดˆ๊ธฐ ์ž‘์—…์— ๋Œ€ํ•œ ๋ช…๋ น(์ด ๊ฒฝ์šฐ ์‚ญ์ œ).
  2. ํ™•์ธ์ด ํ•„์š”ํ•  ๋•Œ ๊ฐ’์„ ๋ณด๋‚ด๋Š” ์‹ ํ˜ธ
  3. ํ™•์ธ์„ ์œ„ํ•œ ๋‹จ์ผ ๋ช…๋ น ๋˜๋Š” ์ˆ˜๋ฝ ๋ฐ ๊ฑฐ๋ถ€๋ฅผ ์œ„ํ•œ ๋ช…๋ น

์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค(ํ…Œ์ŠคํŠธ๋˜์ง€ ์•Š์•˜์œผ๋ฉฐ ๊ฐ•ํ™”/์•ฝํ™” ํ•„์š” ์—†์Œ).

self.deleteCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id objectToDelete) { 
    [return self.confirmationCommand.executionSignals
        switchToLatest]
            flattenMap:^id(NSNumber *confirmed) {
                if(confirmed) {
                    return [self deleteObject:objectToDelete];
                } else {
                    return [RACSignal error:someErrorWithAMessageMaybe];
                }
            }];
}];

self.confirmationRequiredSignal = [self.deleteCommand.executing
    filter: ^BOOL(NSNumber *executing) {
        return executing.boolValue;
    }];

๋”ฐ๋ผ์„œ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ ์‹œ์ž‘์˜ ์‚ญ์ œ ๋ฒ„ํŠผ์€ ์ฆ‰์‹œ ์‹คํ–‰์„ ์‹œ์ž‘ํ•˜๋Š” ๋ทฐ ๋ชจ๋ธ์—์„œ deleteCommand ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์‚ญ์ œ ๋ช…๋ น์€ ์ด์ œ confirmationCommand.executionSignals ๊ฐ€ ๊ฐ’์„ ์ „์†กํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ญ์ œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด self.confirmationRequiredSignal ์— ๊ฐ’์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋Š” viewModel์˜ confirmationRequiredSignal ๊ฐ€ ๊ฒฝ๊ณ  ๋ณด๊ธฐ๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๊ฒฝ๊ณ  ๋ณด๊ธฐ์˜ ๋ฒ„ํŠผ์„ ConfirmCommand์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์„ ๊ด€์ฐฐํ•ฉ๋‹ˆ๋‹ค(ํ™•์ธ ๋ช…๋ น์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฒ„ํŠผ์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๋ฏ€๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. confirmed ๊ฐ’์— ๋งคํ•‘ํ•ด์•ผ ํ•จ). ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํƒญํ•˜๋ฉด self.confirmationCommand ๊ฐ€ ์‹คํ–‰๋˜๊ณ  confirmed ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ฃผ๊ธฐ๋ฅผ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค. self.deleteCommand.errors ๋ฅผ ๊ด€์ฐฐํ•˜์—ฌ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ๋ญ”๊ฐ€๋ฅผ ๋˜์ง€๊ณ  ์‹ถ๋‹ค๋ฉด ์—ฌ๊ธฐ์—์„œ ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ์„ ํƒ์ ์œผ๋กœ deleteCommand ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋ช…๋ น ์‹คํ–‰์„ ์™„๋ฃŒํ•˜๊ธฐ ์œ„ํ•ด ๋นˆ ์‹ ํ˜ธ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ชจ๋ธ์€ ๊ฒฝ๊ณ  ๋ณด๊ธฐ, ์ž‘์—… ์‹œํŠธ ๋“ฑ๊ณผ ํ•จ๊ป˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. RACCommands ๋Š” ์ •๋ง ๋Œ€๋‹จํ•ฉ๋‹ˆ๋‹ค.

@Coneko ๋Š” ๊ฐ„๋‹จํ•œ ํ™•์ธ์ด ๋ฐ˜๋“œ์‹œ ๋ทฐ ๋ชจ๋ธ์— ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด log in with twitter ๋ฒ„ํŠผ์„ ํƒญํ•˜๊ณ  ๊ณ„์†(๋˜๋Š” ์ทจ์†Œ)ํ•˜๋ ค๋ฉด ์‚ฌ์šฉํ•  ํŠธ์œ„ํ„ฐ ๊ณ„์ •(์—ฌ๋Ÿฌ ๊ณ„์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ)์„ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์ œ๊ฐ€ ๋ฐฉ๊ธˆ ๋งŒ๋“  ์‹ค์ œ ์˜ˆ์ž…๋‹ˆ๋‹ค.)

@sprynmr ํ”ผ๋“œ๋ฐฑ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋น„์Šทํ•˜์ง€๋งŒ IMO๊ฐ€ ์•ฝ๊ฐ„ ๋” ๊ฐ„๋‹จํ•œ ์†”๋ฃจ์…˜์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ผ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” deleteWithConfirmation: ๋ผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ์†์„ฑ์„ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. RACSignal ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ธ”๋ก์€ ๋‹จ์ผ ๊ฐ’, @YES ๋˜๋Š” @NO ์„ ์ „์†กํ•˜์—ฌ ํ™•์ธ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ธ”๋ก์ž…๋‹ˆ๋‹ค

๋ช…๋ น ์ž์ฒด๋Š” ํ™•์ธ ์‹ ํ˜ธ์—์„œ ์ฒซ ๋ฒˆ์งธ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ์ ์ ˆํ•œ ์‹ ํ˜ธ์— ๋งคํ•‘ํ•˜๋Š” ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค. ํ™•์ธ๋˜์ง€ ์•Š์œผ๋ฉด ์‚ญ์ œ ์‹ ํ˜ธ๊ฐ€ ๋น„์–ด ์žˆ๋Š” ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค.

- (RACCommand *)deleteWithConfirmation:(RACSignal*(^)(void))signalBlock
{
    NSParameterAssert(signalBlock);

    @weakify(self);

    return [[RACCommand alloc] initWithEnabled:nil signalBlock:^RACSignal *(id input) {
        @strongify(self);

        RACSignal *confirmationSignal = signalBlock();

        return [[confirmationSignal take:1] flattenMap:^RACStream *(id value) {
            if ([value boolValue]) {
                return [self doDeletion]; // for example
            }
            else {
                return [RACSignal empty];
            }
        }];
    }];
}

๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฒ„ํŠผ์— ๋ช…๋ น์„ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

self.deleteButton.rac_command = [self.viewModel deleteWithConfirmation:^RACSignal *{
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Delete Widget" message:@"Are you sure you want to delete this widget?" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:@"Delete", nil];

        [alert show];

        return [alert.rac_buttonClickedSignal map:^id(NSNumber *buttonIndex) {
            return @([buttonIndex integerValue] == 1);
        }];
    }];

๋ณด์‹œ๋‹ค์‹œํ”ผ, ๋‚ด๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์‹ ํ˜ธ๋Š” ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ ์‹ ํ˜ธ์—์„œ YES/NO ๊ฐ’์œผ๋กœ์˜ ๋งต์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

๋‚˜๋ฅผ ์œ„ํ•ด ์ถฉ๋ถ„ํžˆ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค! ํ™•์ธ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹จ์ผ YES ๊ฐ’์„ ๋ณด๋‚ด๋Š” ์‹ ํ˜ธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ธ”๋ก์„ ๋‹จ์ˆœํžˆ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹ํ•ฉ๋ฆฌ์ ์œผ๋กœ ์œ ์—ฐํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ๋‚ด ๋ทฐ ๋ชจ๋ธ์—์„œ ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋กœ ์บก์Šํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

- (RACCommand *)deleteWithoutConfirmationCommand
{
    return [self deleteWithConfirmation:^RACSignal *{
        return [RACSignal return:@YES];
    }]
}

์ด๊ฒƒ๋„ ๊ฝค ๊ดœ์ฐฎ์€ ์†”๋ฃจ์…˜์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ์„ ์‚ญ์ œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

@sprynmr ๋‚˜๋Š” ๋‹น์‹ ์˜ ๋Œ€๋‹ต์„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๋‚˜๋ฅผ ๋‹นํ˜น์Šค๋Ÿฝ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์žˆ๋‹ค.
๋ธ”๋ก์—์„œ ๋ฐ˜ํ™˜๋œ ์‹ ํ˜ธ๋Š” ์™„๋ฃŒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค( self.confirmationCommand.executionSignals.switchToLatest ). ์ด๋Š” ๋ช…๋ น์ด ํ•ญ์ƒ ์‹คํ–‰ ๋ชจ๋“œ๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ๋‹ค๋ฅธ ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™์‹œํ‚ค๋‚˜์š”?

self.deleteCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id objectToDelete) { 
    [return self.confirmationCommand.executionSignals
        switchToLatest]
            flattenMap:^id(NSNumber *confirmed) {
                if(confirmed) {
                    return [self deleteObject:objectToDelete];
                } else {
                    return [RACSignal error:someErrorWithAMessageMaybe];
                }
            }];
}];

@haifengkao take:1 ๋’ค์— switchToLatest take:1 ์™€ ๊ฐ™์ด ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ์œ ์ง€ ์ฃผ๊ธฐ์— ์ฃผ์˜ํ•˜์‹ญ์‹œ์˜ค. self ๋Œ€ํ•œ ์—ฌ๋Ÿฌ ์ฐธ์กฐ๋ฅผ @strongify/weakify

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