Facebook Componentkit 概况了解

官网文档

[TOC]

一、概况

Components are immutable objects that specify how to configure views.
A simple analogy is to think of a component as a stencil: 
a fixed description that can be used to paint a view but that is not a view itself.
A component is often composed of other components, 
building up a component hierarchy that describes a user interface.
复制代码

理解: Component(组件)是不可变的对象,用来告诉我们如何初始化view。 Component(组件)是一个固定的描述,这个描述可以用来打印view。 简单恰当的比喻: Component(组件)相当于stencil(镂空板),是一个固定的图案,用来打印view,但不是view。

镂空版印刷(stencil printing),指在木片、纸板、金属或塑料等片材上刻划出图文,并挖空制成镂空版,通过刷涂或喷涂方法使油墨透过通孔附着于承印物上。

Component(组件)相当于印章 view相当于印章盖出来的图案

一个组件通常由其他组件组合而成,组件的层级结构可以用来描述用户界面。

1.1Components 三大特性:

demo代码

@implementation ArticleComponent

+ (instancetype)newWithArticle:(ArticleModel *)article
{
  return [super newWithComponent:
          [CKFlexboxComponent
           newWithView:{}
           size:{}
           style:{
             .direction = CKFlexboxDirectionVertical,
           }
           children:{
             {[HeaderComponent newWithArticle:article]},
             {[MessageComponent newWithMessage:article.message]},
             {[FooterComponent newWithFooter:article.footer]},
           }];
}

@end
复制代码
声明式 Declarative:

Instead of implementing -sizeThatFits: and -layoutSubviews and positioning subviews manually, you declare the subcomponents of your component (here, we say “stack them vertically”). 相比原生设置UI需要手动设置位置,声明式的特性只需要我们做一个声明描述即可,比如垂直排列元素。 非常方便。

函数式Functional:

Data flows in one direction. Methods take data models and return totally immutable components. 数据单向流动,数据流向UI 根据Data获取对应的不可变的Components组件

When state changes, ComponentKit re-renders from the root and reconciles the two component trees from the top with as few changes to the view hierarchy as possible.

状态改变时,ComponentKit会重新绘制。这时候有oldState和newState,会仔细核对两个组件树(Component tree),从root node到top node 尽量使用最少的重绘来更新view层级结构。

组合式Composable:

Here FooterComponent is used in an article, but it could be reused for other UI with a similar footer. Reusing it is a one-liner. CKFlexboxComponent is inspired by the flexbox model of the web and can easily be used to implement many layouts. 比如上文demo中的FooterComponent可以复用在别的组件里。 CKFlexboxComponent是类似于flexbox的Component。

1.2、Components 优缺点

Strengths 优点
  • Simple and Declarative 简单和声明式: 比较类似 React.

  • Scroll Performance 滚动流畅: 布局都在后台线程,保证了主线程的流畅度。

  • View Recycling 视图复用

  • Composability 组合式使用:Component可以组合起来使用

Considerations 可改进的地方
  • 列表式的界面支持比较好,不是列表式的界面支持不够理想。
  • ComponentKit is fully native and compiled. 全Native,不支持跨端。
  • ComponentKit 基于 Objective-C++. 不支持Swift,有学习成本。

二、相关API

2.1 Component类(避免直接继承CKComponent类)

@interface CKComponent : NSObject

/** Returns a new component. */
+ (instancetype)newWithView:(const CKComponentViewConfiguration &)view
                       size:(const CKComponentSize &)size;

@end
复制代码

component 是不可变的,没有addSubcomponent方法 。 component可以在任意线程创建,因此所有的布局计算可以避免阻塞主线程。The Objective-C 使用 +newWith... 便利构造器保持代码可读

2.2 Composite Components(复合组件,可以直接继承此类)

避免直接继承 CKComponent 类。 可以直接继承CKCompositeComponent. composite component包含了另一个component,对外隐藏了它的实现。 比如需要做一个分享按钮,可以制作一个ShareButtonComponent 复合组件,里面包含CKButtonComponent组件

@implementation ShareButtonComponent

+ (instancetype)newWithArticle:(ArticleModel *)article
{
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:@selector(shareTapped)
           options:{...}]];
}

- (void)shareTapped
{
  // Share the article
}

@end
复制代码

2.3 Views(视图)

使用 newWithView:size: class method:来创建component组件

+ (instancetype)newWithView:(const CKComponentViewConfiguration &)view
                       size:(const CKComponentSize &)size;
复制代码

关于CKComponentViewConfiguration

struct CKComponentViewConfiguration {
  CKComponentViewClass viewClass;//使用[UIImageView class] 或者 [UIButton class]
  std::unordered_map<CKComponentViewAttribute, id> attributes;//属性map
};
复制代码

使用newWithView

[CKComponent 
 newWithView:{
   [UIImageView class],
   {
     {@selector(setImage:), image},
     {@selector(setContentMode:), @(UIViewContentModeCenter)} // Wrapping into an NSNumber
   }
 }
 size:{image.size.width, image.size.height}];
复制代码

ComponentKit 会做如下的事情:

1.当component被挂载时候自动创建或复用 UIImageView 2.自动调用 setImage: and setContentMode: 使用给定的值 3.如果更新view tree时候value没有变化就跳过不调用setImage: or setContentMode:方法

2.4 Layout(布局)

UIView 实例在属性里面存储 position 和 size 信息。 当约束条件变化的时候Core Animation 通过调用layoutSubviews来更新这些属性。

CKComponent 实例并不存储position 和 size 信息。 ComponentKit 使用给定的约束,调用 layoutThatFits: 方法 , component 会返回 CKComponentLayout,包含自身的size和children component的size和position信息

struct CKComponentLayout {
  CKComponent *component;
  CGSize size;
  std::vector<CKComponentLayoutChild> children;
};

struct CKComponentLayoutChild {
  CGPoint position;
  CKComponentLayout layout;
};
复制代码
Layout Components
  • CKFlexboxComponent 基于简化版的 CSS flexbox. 允许垂直或者水平排列元素,各种对齐等。

  • CKInsetComponent 应用内边距的布局

  • CKBackgroundLayoutComponent 背景布局

  • CKOverlayLayoutComponent  覆盖布局

  • CKCenterLayoutComponent  中心布局

  • CKRatioLayoutComponent  比例布局

  • CKStaticLayoutComponent  固定布局

2.5 Responder Chain(响应者链)

  1. component的下一级响应者是它自己的 controller(如果有的话)
  2. component的 controller 的下一级响应者是component的父级component.
  3. 如果 component 没有controller, 它的下一级响应者是自己的父级component
  4. 根 component的下一级响应者是它所被添加的view
  5. 一般来说 view的下一级响应者是它的superview.
  6. 最终,view会找到和component层级结构一样的根view
  7. 如果要使用CKComponentActionSend,可以手动桥接view responder chain 和 the component responder chain。

注意component 并不是UIResponder的子类,不能成为 first responder. 但是component也是实现了nextResponder 和 targetForAction:withSender:方法.

Tap点击的实现

使用CKComponentActionAttribute在 UIControl 上实现Tap点击

@implementation SomeComponent

+ (instancetype)new
{
  return [self newWithView:{
    [UIButton class],
    {CKComponentActionAttribute(@selector(didTapButton))}
  }];
}

- (void)didTapButton
{
  // Aha! The button has been tapped.
}

@end
复制代码
手势的实现

使用CKComponentTapGestureAttribute可以在任何UIView上实现

@implementation SomeComponent

+ (instancetype)new
{
  return [self newWithView:{
    [UIView class],
    {CKComponentTapGestureAttribute(@selector(didTapView))}
  }];
}

- (void)didTapView
{
  // The view has been tapped.
}

@end
复制代码

2.6 Actions(子component和父component通信)

一般来说子components 需要和.父component进行通信。 比如一个按钮component需要告诉父component它被点击了。 Component actions 可以实现这个目的。

Component Actions是什么?

CKAction<T...> 是 Objective-C++ 类,包含 一个 SEL (Objective-C的方法名), 和一个 target.  CKAction<T...> 允许你指定参数传给指定方法。 CKAction的 send方法可以带着发送者component和参数传递给receiver

由于历史原因,CKComponentActionSend可带action,sender,和一个可选对象。 循着响应者链,找到一个响应者响应对应的方法,把参数传给它。

CKComponentActionSend 只能在主线程被调用!

@implementation SampleComponent
+ (instancetype)new
{
  CKComponentScope scope(self);
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:{scope, @selector(someAction:event:)}
           options:{}]];
}

- (void)someAction:(CKButtonComponent *)sender event:(UIEvent *)event
{
  // Do something
}
@end

@implementation SampleOtherComponentThatDoesntCareAboutEvents
+ (instancetype)new
{
  CKComponentScope scope(self);
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:{scope, @selector(someAction:)}
           options:{}]];
}

- (void)someAction:(CKButtonComponent *)sender
{
  // Do something
}
@end

@implementation SampleOtherComponentThatDoesntCareAboutAnyParameters
+ (instancetype)new
{
  CKComponentScope scope(self);
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:{scope, @selector(someAction)}
           options:{}]];
}

- (void)someAction
{
  // We don't take any arguments in this example.
}
@end

@interface SampleControllerDelegatingComponentController : CKComponentController
/** Component actions may be implemented either on the component, or the controller for that component. */
- (void)someAction;
@end

@implementation SampleControllerDelegatingComponent
+ (instancetype)new
{
  CKComponentScope scope(self);
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:{scope, @selector(someAction)}
           options:{}]];
}
@end

@implementation SampleControllerDelegatingComponentController
- (void)someAction
{
  // Do something
}
@end
复制代码
如何传递Action

简单规则: 方法应该在它们被引用的地方在一个文件中 下面的例子父component和子component耦合比较厉害,如果别的component想要使用子component,或者父类改了方法名,运行的时候就会崩溃。

//有隐患
@implementation ParentComponent
+ (instancetype)new
{
  return [super newWithComponent:[ChildComponent new]];
}
- (void)someAction:(CKComponent *)sender
{
  // Do something
}
@end

@implementation ChildComponent
+ (instancetype)new
{
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:@selector(someAction:)]];
}
@end
复制代码

从父component传递方法到子component,子component只知道需要一个action,耦合比较小。

//正确的例子
@implementation ParentComponent
+ (instancetype)new
{
  CKComponentScope scope(self);

  return [super newWithComponent:
          [ChildComponent
           newWithAction:{scope, @selector(someAction:)}]];
}

- (void)someAction:(CKComponent *)sender
{
  // Do something
}
@end

@implementation ChildComponent
+ (instancetype)newWithAction:(CKTypedComponentAction<>)action
{
  return [super newWithComponent:
          [CKButtonComponent
           newWithAction:action]];
}
@end
复制代码

2.7 State(对应React的State)

ComponentKit是受 React启发的. React components 拥有下面两个元素

  • props: 从parent传过来。在ComponentKit中类似+new方法传给子component的参数

  • state: 父component不用关心,这是子component内部实现。 在 Thinking in React 有相关的讨论。

CKComponent 的 state.

@interface CKComponent
- (void)updateState:(id (^)(id))updateBlock mode:(CKUpdateMode)mode;
@end
复制代码

继续阅读... 展开的demo

#import "CKComponentSubclass.h" // import to expose updateState:
@implementation MessageComponent

+ (id)initialState
{
  return @NO;
}

+ (instancetype)newWithMessage:(NSAttributedString *)message
{
  CKComponentScope scope(self);
  NSNumber *state = scope.state();
  return [super newWithComponent:
          [CKTextComponent
           newWithAttributes:{
             .attributedString = message,
             .maximumNumberOfLines = [state boolValue] ? 0 : 5,
           }
           viewAttributes:{}
           accessibilityContext:{}]];
}

- (void)didTapContinueReading
{
  [self updateState:^(id oldState){ return @YES; } mode:CKUpdateModeAsynchronous];
}

@end
复制代码

2.8 Scopes(用来作为component的id)

如下图,如果item没有id 就没法进行区分

如下图,每个item都有自己的id,这样可以具体区分

Scopes给component提供了一个永久的身份标识符。不管component被创建过多少次,scope都是一样的。

  1. component 有 state,那么必须要定义一个 scope
  2. component 有component controller,那么必须要定义一个 scope
  3. component的子component有 state 或者 component controllers 那么必须要定义一个 scope
定义 Scope

使用 CKComponentScope在 +new方法中

@implementation ListItemComponent

+ (instancetype)newWithListItem:(ListItem *)listItem
{
  // Defines a scope that is uniquely identified by the component's class (i.e. ListItemComponent) and the provided identifier.
  CKComponentScope scope(self, listItem.uniqueID);
  const auto c = /* ... */;
  return [super newWithComponent:c];
}

@end
复制代码

component 没有 model object

@implementation ListComponent

+ (instancetype)newWithList:(List *)list
{
  // Defines a scope that is uniquely identified by the component's class (i.e. ListComponent).
  CKComponentScope scope(self);
  const auto c = /* ... */;
  return [super newWithComponent:c];
}

@end
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值