zoukankan      html  css  js  c++  java
  • ReactiveCocoa框架下的MVVM模式解读

    记录一些MVVM文章中关于ReactiveCocoa的代码:

    实例一:带有分页的文章列表,根据文章类别过滤出文章的列表,可以进入文章详细页面

    1:YFBlogListViewModel 首先了解关于列表的ViewModel的代码内容:

    #import <Foundation/Foundation.h>
    #import <ReactiveCocoa.h>
    
    
    @class YFCategoryArticleListModel;
    
    
    /**
     *  文章列表的视图模型.
     */
    @interface YFBlogListViewModel : NSObject
    @property (copy, nonatomic) NSArray * blogListItemViewModels; //!< 文章.内部存储的应为文章列表单元格的视图模型.注意: 刷新操作,存储第一页数据;翻页操作,将存储所有的数据,并按页面排序.
    
    /**
     *  使用一个分类文章列表数据模型来快速初始化.
     *
     *  @param model 文章列表模型.
     *
     *  @return 实例对象.
     */
    
    
    - (instancetype)initWithCategoryArtilceListModel: (YFCategoryArticleListModel *) model;
    
    /**
     *  获取首页的数据.常用于下拉刷新.
     *
     */
    - (void)first;
    
    /**
     *  翻页,获取下一页的数据.常用于上拉加载更多.
     */
    - (void)next;
    
    
    @end
    #import "YFBlogListViewModel.h"
    #import <ReactiveCocoa.h>
    #import <AFNetworking.h>
    #import <RACAFNetworking.h>
    #import "YFCategoryArticleListModel.h"
    #import <MJExtension.h>
    #import "YFBlogListItemViewModel.h"
    #import "YFArticleModel.h"
    
    @interface YFBlogListViewModel ()
    @property (strong, nonatomic) AFHTTPRequestOperationManager * httpClient;
    @property (strong, nonatomic) NSNumber * nextPageNumber; //!< 下次要请求第几页的数据.
    @property (copy, nonatomic) NSString * category; //!< 文章类别.
    @property (copy, nonatomic) NSString * requestPath; //!< 完整接口地址.
    
    
    
    @end
    
    @implementation YFBlogListViewModel
    
    - (instancetype)initWithCategoryArtilceListModel:(YFCategoryArticleListModel *)model
    {
        self = [super init];
        
        if (nil != self) {
            // 设置 self.category 与 model.category 的关联.
            [RACObserve(model, category) subscribeNext:^(NSString * categoryName) {
                self.category = categoryName;
            }];
            
            // 和类型无关的RAC 初始化操作,应该剥离出来.
            [self setup];
        }
        
        return self;
    }
    
    /**
     *  和数据模型无关的初始化设置,放到独立的方法中.
     */
    - (void)setup
    {
        // 初始化网络请求相关的信息.
        self.httpClient = [AFHTTPRequestOperationManager manager];
        self.httpClient.requestSerializer = [AFJSONRequestSerializer serializer];
        self.httpClient.responseSerializer = [AFJSONResponseSerializer serializer];
        
        // 设置 self.nextPageNumber 与self.category的关联.
        [RACObserve(self, category) subscribeNext:^(id x) {
            // 只要分类变化,下次请求,都需要重置为请求第零页的数据.
            self.nextPageNumber = @0;
        }];
        
        // 接口完整地址,肯定是受分类和页面的影响的.但是因为分类的变化最终会通过分页的变化来体现,所以此处仅需监测分页的变化情况即可.
        [RACObserve(self, nextPageNumber)  subscribeNext:^(NSNumber * nextPageNumber) {
            NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=%@&model[page]=%@", self.category, nextPageNumber];
            
            self.requestPath = path;
        }];
        
        // 每次数据完整接口变化时,必然要同步更新 blogListItemViewModels 的值.
        [[RACObserve(self, requestPath) filter:^BOOL(id value) {
            return value;
        }] subscribeNext:^(NSString * path) {
            /**
             *  分两种情况: 如果是变为0,说明是重置数据;如果是大于0,说明是要加载更多数据;不处理向上翻页的情况.
             */
            
            NSMutableArray * articls = [NSMutableArray arrayWithCapacity: 42];
            
            if (YES != [self.nextPageNumber isEqualToNumber: @0]) {
                [articls addObjectsFromArray: self.blogListItemViewModels];
            }
            
            [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {
                // 使用MJExtension将JSON转换为对应的数据模型.
                NSArray * newArticles = [YFArticleModel objectArrayWithKeyValuesArray: JSONAndHeaders.first];
                
                // RAC 风格的数组操作.
                RACSequence * newblogViewModels = [newArticles.rac_sequence
                                        map:^(YFArticleModel * model) {
                                            YFBlogListItemViewModel * vm = [[YFBlogListItemViewModel alloc] initWithArticleModel: model];
                                            
                                            return vm;
                                        }];
                
                
                [articls addObjectsFromArray: newblogViewModels.array];
                
                self.blogListItemViewModels = articls;
            }];
        }];
    }
    
    - (void)first
    {
        self.nextPageNumber = @0;
    }
    
    - (void)next
    {
        self.nextPageNumber  = [NSNumber numberWithInteger: [self.nextPageNumber integerValue] + 1];
    }
    
    @end

    2:YFCategoryArticleListModel模型的内容

    #import <Foundation/Foundation.h>
    
    /**
     *  分类文章列表.
     */
    @interface YFCategoryArticleListModel : NSObject
    @property (copy, nonatomic) NSString * category; //!< 分类
    @property (strong, nonatomic) NSArray * articles; //!< 此分类下的文章列表.
    
    @end

    3:ViewController的代码

    #import <UIKit/UIKit.h>
    
    @class YFBlogListViewModel;
    
    @interface YFMVVMPostListViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>
    @property (nonatomic, strong) UITableView * tableView;
    @property (strong, nonatomic) YFBlogListViewModel * viewModel;
    
    @end
    #import "YFMVVMPostListViewController.h"
    #import "YFBlogListViewModel.h"
    #import <ReactiveCocoa.h>
    #import "YFCategoryArticleListModel.h"
    #import "YFBlogListViewModel.h"
    #import "YFBlogListItemViewModel.h"
    #import "YFArticleModel.h"
    #import "YFBlogDetailViewModel.h"
    #import <MJRefresh.h>
    #import "YFMVVMPostViewController.h"
    
    @interface YFMVVMPostListViewController ()
    
    @end
    
    @implementation YFMVVMPostListViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        [RACObserve(self.viewModel, blogListItemViewModels) subscribeNext:^(id x) {
            [self updateView];
        }];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    - (UITableView *)tableView
    {
        if (nil == _tableView) {
            _tableView = [[UITableView alloc] init];
            
            [self.view addSubview: _tableView];
            
            [_tableView makeConstraints:^(MASConstraintMaker *make) {
                make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
            }];
            
            _tableView.delegate = self;
            _tableView.dataSource = self;
            
            NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
            
            [_tableView registerClass: NSClassFromString(cellReuseIdentifier) forCellReuseIdentifier:cellReuseIdentifier];
            
            _tableView.header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
                [self.viewModel first];
            }];
            
            _tableView.footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
                [self.viewModel next];
            }];
            
        }
        
        return _tableView;
    }
    
    /**
     * 更新视图.
     */
    - (void) updateView
    {
        [self.tableView.header endRefreshing];
        [self.tableView.footer endRefreshing];
        
        [self.tableView reloadData];
    }
    
    # pragma mark - tabelView代理方法.
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        NSInteger number  = self.viewModel.blogListItemViewModels.count;
        
        return number;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
        
        UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: cellReuseIdentifier forIndexPath:indexPath];
        
        YFBlogListItemViewModel * vm = self.viewModel.blogListItemViewModels[indexPath.row];
        
        NSString * content = vm.intro;
        
        cell.textLabel.text = content;
        
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        
        return cell;
    }
    
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // 跳转到博客详情.
        YFBlogListItemViewModel * itemVM = self.viewModel.blogListItemViewModels[indexPath.row];
        
        YFMVVMPostViewController * postVC = [[YFMVVMPostViewController alloc] init];
        
        YFBlogDetailViewModel * detailVM = [[YFBlogDetailViewModel alloc] init];
        detailVM.blogId = itemVM.blogId;
        
        postVC.viewModel = detailVM;
        
        [self.navigationController pushViewController: postVC animated: YES];
    }
    
    @end

    4:跳转到当前页面的内容

            YFMVVMPostListViewController * mvvmPostVC = [[YFMVVMPostListViewController alloc] init];
            
            YFCategoryArticleListModel * articleListModel = [[YFCategoryArticleListModel alloc] init];
            articleListModel.category = @"ui";
            
            YFBlogListViewModel * listVM = [[YFBlogListViewModel alloc] initWithCategoryArtilceListModel: articleListModel];
            
            mvvmPostVC.viewModel = listVM;
            
            [self.navigationController pushViewController: mvvmPostVC animated: YES];

    5:详细页面的ViewModel代码:

    #import <Foundation/Foundation.h>
    @class YFArticleModel;
    
    /**
     *  文章详情的视图模型.
     */
    
    @interface YFBlogDetailViewModel : NSObject
    @property (copy, nonatomic) NSString * content; // 要显示的内容.
    @property (copy, nonatomic) NSString * blogId; //!< 博客ID.
    
    - (instancetype)initWithModel: (YFArticleModel *) model;
    
    @end
    #import "YFBlogDetailViewModel.h"
    #import <ReactiveCocoa.h>
    #import "YFArticleModel.h"
    #import <RACAFNetworking.h>
    #import <MJExtension.h>
    
    @interface YFBlogDetailViewModel ()
    @property (strong, nonatomic) AFHTTPRequestOperationManager * httpClient;
    @property (copy, nonatomic) NSString * requestPath; //!< 完整接口地址.
    
    @end
    @implementation YFBlogDetailViewModel
    
    - (instancetype)init
    {
        self = [self initWithModel: nil];
        
        return self;
    }
    
    - (instancetype)initWithModel:(YFArticleModel *)model
    {
        self = [super init];
        
        if (nil != self) {
            // 设置self.blogId与model.id的相互关系.
            [RACObserve(model, id) subscribeNext:^(id x) {
                self.blogId = x;
            }];
            
            [self setup];
        }
        
        return self;
    }
    
    /**
     *  公共的与Model无关的初始化.
     */
    - (void)setup
    {
        // 初始化网络请求相关的信息.
        self.httpClient = [AFHTTPRequestOperationManager manager];
        self.httpClient.requestSerializer = [AFJSONRequestSerializer serializer];
        self.httpClient.responseSerializer = [AFJSONResponseSerializer serializer];
        
        // 接口完整地址,肯定是受id影响.
        [[RACObserve(self, blogId) filter:^BOOL(id value) {
            return value;
        }] subscribeNext:^(NSString * blogId) {
            NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=%@", blogId];
            
            self.requestPath = path;
        }];
        
        // 每次完整的数据接口变化时,必然要同步更新 self.content 的值.
        [[RACObserve(self, requestPath) filter:^BOOL(id value) {
            return value;
        }] subscribeNext:^(NSString * path) {
            [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {
                // 使用MJExtension将JSON转换为对应的数据模型.
                YFArticleModel * model = [YFArticleModel objectWithKeyValues:JSONAndHeaders.first];
               
                self.content = model.body;
            }];
        }];
    }
    
    @end

    6:详细页面的ViewController

    #import <UIKit/UIKit.h>
    
    @class YFBlogDetailViewModel;
    
    @interface YFMVVMPostViewController : UIViewController
    @property (strong, nonatomic) YFBlogDetailViewModel * viewModel;
    
    @end
    #import "YFMVVMPostViewController.h"
    #import "YFBlogDetailViewModel.h"
    #import <ReactiveCocoa.h>
    
    @interface YFMVVMPostViewController ()
    @property (strong, nonatomic) UIWebView * webView;
    @end
    
    @implementation YFMVVMPostViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        [RACObserve(self.viewModel, content) subscribeNext:^(id x) {
            [self updateView];
        }];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    - (UIWebView *)webView
    {
        if (nil == _webView) {
            _webView = [[UIWebView alloc] init];
            
            [self.view addSubview: _webView];
            
            [_webView makeConstraints:^(MASConstraintMaker *make) {
                make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
            }];
        }
        
        return _webView;
    }
    
    /**
     * 更新视图.
     */
    - (void) updateView
    {
        [self.webView loadHTMLString: self.viewModel.content baseURL:nil];
    }
    
    @end

    实例二:用户列表实例

    1:用户列表的ViewModel代码UsersViewModel

    #import <ReactiveViewModel/ReactiveViewModel.h>
    
    @class RACCommand;
    
    #pragma mark -
    
    @interface UsersViewModel : RVMViewModel
    
    /// Array of UserViewModel objects filled by userViewModelsCommand.
    @property (nonatomic, readonly) NSArray *userViewModels;
    
    /// Input: nil
    @property (nonatomic, readonly) RACCommand *userViewModelsCommand;
    
    /// Input: nil
    @property (nonatomic, readonly) RACCommand *clearImageCacheCommand;
    
    @property (nonatomic, readonly, getter=isLoading) BOOL loading;
    
    @end
    #import "UsersViewModel.h"
    
    #import "UserViewModel.h"
    
    #import "UserController.h"
    
    #import "User.h"
    
    #import "ImageController.h"
    
    #import <ReactiveCocoa/ReactiveCocoa.h>
    #import <ReactiveCocoa/RACEXTScope.h>
    
    #pragma mark -
    
    @implementation UsersViewModel
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            UserController *userController = [[UserController alloc] init];
            ImageController *imageController = [ImageController sharedController];
    
            _userViewModelsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id _) {
                return [[[userController fetchRandomUsers:100]
                            subscribeOn:[RACScheduler scheduler]]
                            map:^NSArray *(NSArray *users) {
                                return [[[users rac_sequence]
                                            map:^UserViewModel *(User *user) {
                                                return [[UserViewModel alloc] initWithUser:user imageController:imageController];
                                            }]
                                            array];
                            }];
            }];
    
            RAC(self, userViewModels) =
                [[[_userViewModelsCommand executionSignals]
                    switchToLatest]
                    deliverOn:[RACScheduler mainThreadScheduler]];
    
            RAC(self, loading) =
                [_userViewModelsCommand executing];
    
            _clearImageCacheCommand = [[RACCommand alloc] initWithEnabled:[RACObserve(self, loading) not] signalBlock:^RACSignal *(id _) {
                return [imageController purgeLocalCaches];
            }];
        }
        return self;
    }
    
    @end

    2:另外封装UserController的代码:

    @class RACSignal;
    
    #pragma mark -
    
    @interface UserController : NSObject
    
    /// Sends an array of fabricated User objects then completes.
    - (RACSignal *)fetchRandomUsers:(NSUInteger)numberOfUsers;
    
    @end
    #import "UserController.h"
    
    #import "User.h"
    
    #import <ReactiveCocoa/ReactiveCocoa.h>
    #import <LoremIpsum/LoremIpsum.h>
    
    #pragma mark -
    
    @implementation UserController
    
    - (RACSignal *)fetchRandomUsers:(NSUInteger)numberOfUsers {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSMutableArray *usersArray = [NSMutableArray array];
            for (int i = 0; i < numberOfUsers; i++) {
                NSString *name = [LoremIpsum name];
                NSURL *avatarURL = [[LoremIpsum URLForPlaceholderImageFromService:LIPlaceholderImageServiceHhhhold withSize:CGSizeMake(96, 96)] URLByAppendingPathComponent:[NSString stringWithFormat:@"jpg?test=%i", i]];
                User *user = [[User alloc] initWithName:name avatarURL:avatarURL];
                [usersArray addObject:user];
            }
            [subscriber sendNext:[usersArray copy]];
            [subscriber sendCompleted];
            return nil;
        }];
    }
    
    @end

    3:Model的代码:

    #pragma mark -
    
    @interface User : NSObject
    
    @property (nonatomic, readonly) NSString *name;
    @property (nonatomic, readonly) NSURL *avatarURL;
    
    - (instancetype)initWithName:(NSString *)name avatarURL:(NSURL *)avatarURL;
    
    @end
    #import "User.h"
    
    
    #pragma mark -
    
    @implementation User
    
    - (instancetype)initWithName:(NSString *)name avatarURL:(NSURL *)avatarURL {
        self = [super init];
        if (self != nil) {
            _name = name;
            _avatarURL = avatarURL;
        }
        return self;
    }
    
    @end

    4:ViewController的代码

    @class UsersViewModel;
    
    @interface UsersViewController : UITableViewController
    
    - (instancetype)initWithViewModel:(UsersViewModel *)viewModel;
    
    @end
    #import "UsersViewController.h"
    
    #import "UsersViewModel.h"
    #import "UserViewModel.h"
    
    #import "UserCell.h"
    
    #import <ReactiveCocoa/ReactiveCocoa.h>
    #import <ReactiveCocoa/RACEXTScope.h>
    
    #pragma mark -
    
    @interface UsersViewController ()
    
    @property (nonatomic, readonly) UsersViewModel *viewModel;
    
    @end
    
    
    @implementation UsersViewController
    
    - (instancetype)initWithViewModel:(UsersViewModel *)viewModel {
        self = [super init];
        if (self != nil) {
            _viewModel = viewModel;
        }
        return self;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.tableView.rowHeight = 48;
        [self.tableView registerClass:[UserCell class] forCellReuseIdentifier:NSStringFromClass([UserCell class])];
    
        @weakify(self);
    
        self.title = NSLocalizedString(@"Random Users", nil);
    
        UIBarButtonItem *clearImageCacheBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Clear Cache", nil) style:UIBarButtonItemStylePlain target:nil action:nil];
        clearImageCacheBarButtonItem.rac_command = self.viewModel.clearImageCacheCommand;
        self.navigationItem.rightBarButtonItem = clearImageCacheBarButtonItem;
    
        self.refreshControl = [[UIRefreshControl alloc] init];
        [[[self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged]
            mapReplace:self.viewModel.userViewModelsCommand]
            subscribeNext:^(RACCommand *userViewModelsCommand) {
                [userViewModelsCommand execute:nil];
            }];
    
        [RACObserve(self.viewModel, loading)
            subscribeNext:^(NSNumber *loading) {
                @strongify(self);
                if ([loading boolValue]) {
                    [self.refreshControl beginRefreshing];
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                } else {
                    [self.refreshControl endRefreshing];
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                }
            }];
    
        [[RACObserve(self.viewModel, userViewModels)
            ignore:nil]
            subscribeNext:^(id _) {
                @strongify(self);
                [self.tableView reloadData];
            }];
    
        [self.viewModel.userViewModelsCommand execute:nil];
    }
    
    #pragma mark UITableViewDataSource
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return [self.viewModel.userViewModels count];
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UserViewModel *viewModel = self.viewModel.userViewModels[indexPath.row];
    
        UserCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([UserCell class]) forIndexPath:indexPath];
        cell.viewModel = viewModel;
        cell.viewModel.active = YES;
        return cell;
    }
    
    # pragma mark UITableViewDelegate
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        UserViewModel *userViewModel = self.viewModel.userViewModels[indexPath.row];
        NSLog(@"Selected: %@", userViewModel);
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    
    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    
    }
    
    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
        UserCell *userCell = (UserCell *)cell;
        userCell.viewModel.active = NO;
    }
    
    @end

    6:UserCell代码:

    @class UserViewModel;
    
    #pragma mark -
    
    @interface UserCell : UITableViewCell
    
    @property (nonatomic) UserViewModel *viewModel;
    
    @end
    #import "UserCell.h"
    
    #import "ImageView.h"
    
    #import "UserViewModel.h"
    #import "ImageViewModel.h"
    
    #import <ReactiveCocoa/ReactiveCocoa.h>
    
    #pragma mark -
    
    @interface UserCell ()
    
    @property (nonatomic, readonly) ImageView *avatarImageView;
    
    @end
    
    @implementation UserCell
    
    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        if (self != nil) {
            _avatarImageView = [[ImageView alloc] init];
            [self.contentView addSubview:_avatarImageView];
        }
        return self;
    }
    
    - (void)setViewModel:(UserViewModel *)viewModel {
        if (_viewModel == viewModel) return;
    
        _viewModel = viewModel;
    
        self.avatarImageView.viewModel = _viewModel.imageViewModel;
        self.textLabel.text = _viewModel.name;
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
    
        self.avatarImageView.frame = CGRectMake(0, 0, 48, 48);
        self.textLabel.frame = CGRectMake(58, 0, 260, 48);
    }
    
    @end

    7:调用主控制器跳转

        UsersViewModel *usersViewModel = [[UsersViewModel alloc] init];
        UsersViewController *usersViewController = [[UsersViewController alloc] initWithViewModel:usersViewModel];

    另:reactivecocoa afnetworking地交互可以查下面这个实例,地址:https://github.com/octokit/octokit.objc

    小项目框架设计(ReactiveCocoa+MVVM+AFNetworking+FMDB) 地址:http://www.tuicool.com/articles/Q3uuQvA

  • 相关阅读:
    上传相同文件名的时候不能触发change事件的解决方案
    react自定义组件属性类型检测
    在react项目当中使用redux
    redux使用需要注意的地方
    关于在redux当中 reducer是如何知道传入的state是初始化state下面的哪一条数据
    react当中子组件改变父组件的状态
    vscode编辑器开发react时,设置使emmet支持自定义组件
    Sass之二(进阶篇)
    Sass之一(基础篇)
    sass ruby环境 安装配置,使用sublime text3 中sass
  • 原文地址:https://www.cnblogs.com/wujy/p/5083689.html
Copyright © 2011-2022 走看看