Finishing reconciler in ComponentWrapper
This commit is contained in:
parent
707d696f25
commit
7d300fab6a
|
@ -5,96 +5,202 @@
|
|||
// Created by Max Desiatov on 28/11/2018.
|
||||
//
|
||||
|
||||
protocol ComponentWrapper {
|
||||
func mount(with renderer: Renderer, to target: Any)
|
||||
|
||||
func unmount(with renderer: Renderer)
|
||||
// TODO: this won't work in multi-threaded scenarios
|
||||
private var _hooks = Hooks()
|
||||
|
||||
func update(with renderer: Renderer)
|
||||
extension CompositeComponent {
|
||||
public static var hooks: Hooks {
|
||||
return _hooks
|
||||
}
|
||||
}
|
||||
|
||||
protocol ComponentWrapper: class {
|
||||
var node: Node { get set }
|
||||
|
||||
func mount(with reconciler: Reconciler)
|
||||
|
||||
func unmount(with reconciler: Reconciler)
|
||||
|
||||
func update(with reconciler: Reconciler)
|
||||
}
|
||||
|
||||
final class CompositeComponentWrapper: ComponentWrapper {
|
||||
private var node: Node
|
||||
var node: Node
|
||||
private var mountedChildren = [ComponentWrapper]()
|
||||
private let type: AnyCompositeComponent.Type
|
||||
private let parentTarget: Any
|
||||
var state = [String: Any]()
|
||||
|
||||
init(_ node: Node, _ type: AnyCompositeComponent.Type) {
|
||||
init(_ node: Node, _ type: AnyCompositeComponent.Type, _ parentTarget: Any) {
|
||||
self.node = node
|
||||
self.type = type
|
||||
self.parentTarget = parentTarget
|
||||
}
|
||||
|
||||
func mount(with renderer: Renderer, to target: Any) {
|
||||
let renderedNode = type.render(props: node.props, children: node.children)
|
||||
func mount(with reconciler: Reconciler) {
|
||||
let renderedNode = render(with: reconciler)
|
||||
|
||||
let child = renderedNode.makeComponentWrapper()
|
||||
let child = renderedNode.makeComponentWrapper(parentTarget)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: renderer, to: target)
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
|
||||
func unmount(with renderer: Renderer) {
|
||||
for child in mountedChildren {
|
||||
child.unmount(with: renderer)
|
||||
func unmount(with reconciler: Reconciler) {
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
// FIXME: Should call `hooks.effect` finalizers here after `hooks.effect`
|
||||
// is implemented
|
||||
}
|
||||
|
||||
func update(with reconciler: Reconciler) {
|
||||
switch (mountedChildren.last, render(with: reconciler)) {
|
||||
|
||||
// no mounted children, but children available now
|
||||
case let (nil, renderedNode):
|
||||
let child = renderedNode.makeComponentWrapper(parentTarget)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
|
||||
// some mounted children
|
||||
case let (wrapper?, renderedNode):
|
||||
// new node is the same type as existing child, checking props/children
|
||||
if wrapper.node.type == renderedNode.type &&
|
||||
(wrapper.node.props != renderedNode.props ||
|
||||
wrapper.node.children != renderedNode.children) {
|
||||
wrapper.node = renderedNode
|
||||
wrapper.update(with: reconciler)
|
||||
} else
|
||||
// new node is of different type, complete rerender, i.e. unmount old
|
||||
// wrapper, then mount a new one with new node
|
||||
if wrapper.node.type != renderedNode.type {
|
||||
wrapper.unmount(with: reconciler)
|
||||
|
||||
let child = renderedNode.makeComponentWrapper(parentTarget)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
}
|
||||
// FIXME: this is probably not needed, right?
|
||||
mountedChildren = []
|
||||
}
|
||||
|
||||
func update(with renderer: Renderer) {
|
||||
let newNode = render()
|
||||
func render(with reconciler: Reconciler) -> Node {
|
||||
_hooks.currentReconciler = reconciler
|
||||
_hooks.currentComponent = self
|
||||
|
||||
if node.type == newNode.type {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
let result = type.render(props: node.props, children: node.children)
|
||||
|
||||
func render() -> Node {
|
||||
return type.render(props: node.props, children: node.children)
|
||||
_hooks.currentComponent = nil
|
||||
_hooks.currentReconciler = nil
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
final class HostComponentWrapper: ComponentWrapper {
|
||||
private let node: Node
|
||||
var node: Node
|
||||
fileprivate var mountedChildren = [ComponentWrapper]()
|
||||
private let type: AnyHostComponent.Type
|
||||
private let parentTarget: Any
|
||||
private var target: Any?
|
||||
|
||||
init(_ node: Node, _ type: AnyHostComponent.Type) {
|
||||
init(_ node: Node, _ type: AnyHostComponent.Type, _ parentTarget: Any) {
|
||||
self.type = type
|
||||
self.node = node
|
||||
self.parentTarget = parentTarget
|
||||
}
|
||||
|
||||
func mount(with renderer: Renderer, to target: Any) {
|
||||
self.target = renderer.mountTarget(to: target,
|
||||
with: type,
|
||||
props: node.props,
|
||||
children: node.children)
|
||||
func mount(with reconciler: Reconciler) {
|
||||
guard let renderer = reconciler.renderer else { return }
|
||||
|
||||
let target = renderer.mountTarget(to: parentTarget,
|
||||
with: type,
|
||||
props: node.props,
|
||||
children: node.children)
|
||||
self.target = target
|
||||
|
||||
switch node.children.value {
|
||||
case let nodes as [Node]:
|
||||
mountedChildren = nodes.map { $0.makeComponentWrapper() }
|
||||
mountedChildren = nodes.map { $0.makeComponentWrapper(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
|
||||
for child in mountedChildren {
|
||||
child.mount(with: renderer, to: target)
|
||||
}
|
||||
case let node as Node:
|
||||
let child = node.makeComponentWrapper()
|
||||
let child = node.makeComponentWrapper(target)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: renderer, to: target)
|
||||
child.mount(with: reconciler)
|
||||
|
||||
default:
|
||||
// child type that can't be rendered, but still makes sense as a child
|
||||
// child type that can't be rendered, but still makes sense
|
||||
// (e.g. `String`)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
func unmount(with renderer: Renderer) {
|
||||
func unmount(with reconciler: Reconciler) {
|
||||
guard let target = target else { return }
|
||||
|
||||
renderer.unmount(target: target, with: type)
|
||||
reconciler.renderer?.unmount(target: target, with: type)
|
||||
}
|
||||
|
||||
func update(with renderer: Renderer) {
|
||||
func update(with reconciler: Reconciler) {
|
||||
guard let target = target else { return }
|
||||
|
||||
reconciler.renderer?.update(target: target,
|
||||
with: type,
|
||||
props: node.props,
|
||||
children: node.children)
|
||||
|
||||
switch node.children.value {
|
||||
case var nodes as [Node]:
|
||||
switch (mountedChildren.isEmpty, nodes.isEmpty) {
|
||||
// existing children, new children array is empty, unmount all existing
|
||||
case (false, true):
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren = []
|
||||
|
||||
// no existing children, mount all new
|
||||
case (true, false):
|
||||
mountedChildren = nodes.map { $0.makeComponentWrapper(target) }
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
|
||||
// both arrays have items, reconcile by types and keys
|
||||
case (false, false):
|
||||
var newChildren = [ComponentWrapper]()
|
||||
|
||||
while let child = mountedChildren.first, let node = nodes.first {
|
||||
if node.key != nil &&
|
||||
node.type == mountedChildren[0].node.type &&
|
||||
node.key == child.node.key {
|
||||
child.node = node
|
||||
child.update(with: reconciler)
|
||||
newChildren.append(child)
|
||||
mountedChildren.removeFirst()
|
||||
} else {
|
||||
let newChild = node.makeComponentWrapper(reconciler)
|
||||
newChild.mount(with: reconciler)
|
||||
newChildren.append(newChild)
|
||||
}
|
||||
nodes.removeFirst()
|
||||
}
|
||||
|
||||
mountedChildren = newChildren
|
||||
|
||||
// both arrays are empty, nothing to reconcile
|
||||
case (true, true):
|
||||
()
|
||||
}
|
||||
|
||||
case let node as Node:
|
||||
if let child = mountedChildren.first {
|
||||
child.node = node
|
||||
child.update(with: reconciler)
|
||||
} else {
|
||||
let child = node.makeComponentWrapper(target)
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
|
||||
// child type that can't be rendered, but still makes sense as a child
|
||||
// (e.g. `String`)
|
||||
default:
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
|
||||
public struct Hooks {
|
||||
var currentReconciler: StackReconciler?
|
||||
var currentReconciler: Reconciler?
|
||||
var currentComponent: CompositeComponentWrapper?
|
||||
|
||||
public func state<T>(_ initial: T,
|
||||
|
|
|
@ -19,12 +19,12 @@ public struct Node: Equatable {
|
|||
let children: AnyEquatable
|
||||
let type: ComponentType
|
||||
|
||||
func makeComponentWrapper() -> ComponentWrapper {
|
||||
func makeComponentWrapper(_ parentTarget: Any) -> ComponentWrapper {
|
||||
switch type {
|
||||
case let .base(type):
|
||||
return HostComponentWrapper(self, type)
|
||||
return HostComponentWrapper(self, type, parentTarget)
|
||||
case let .composite(type):
|
||||
return CompositeComponentWrapper(self, type)
|
||||
return CompositeComponentWrapper(self, type, parentTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,29 +7,26 @@
|
|||
|
||||
import Dispatch
|
||||
|
||||
// TODO: this won't work in multi-threaded scenarios
|
||||
private var _hooks = Hooks()
|
||||
protocol Reconciler: class {
|
||||
var renderer: Renderer? { get }
|
||||
|
||||
extension CompositeComponent {
|
||||
public static var hooks: Hooks {
|
||||
return _hooks
|
||||
}
|
||||
func queue(state: Any, for component: CompositeComponentWrapper, id: String)
|
||||
}
|
||||
|
||||
final class StackReconciler {
|
||||
final class StackReconciler: Reconciler {
|
||||
private var queuedState = [(CompositeComponentWrapper, String, Any)]()
|
||||
|
||||
private let rootComponent: ComponentWrapper
|
||||
private let rootTarget: Any
|
||||
private weak var renderer: Renderer!
|
||||
private(set) weak var renderer: Renderer?
|
||||
|
||||
init(node: Node, target: Any, renderer: Renderer) {
|
||||
self.renderer = renderer
|
||||
rootTarget = target
|
||||
|
||||
rootComponent = node.makeComponentWrapper()
|
||||
rootComponent = node.makeComponentWrapper(target)
|
||||
|
||||
rootComponent.mount(with: renderer, to: rootTarget)
|
||||
rootComponent.mount(with: self)
|
||||
}
|
||||
|
||||
func queue(state: Any, for component: CompositeComponentWrapper, id: String) {
|
||||
|
@ -44,23 +41,11 @@ final class StackReconciler {
|
|||
}
|
||||
}
|
||||
|
||||
private func render(component: CompositeComponentWrapper) -> Node {
|
||||
_hooks.currentReconciler = self
|
||||
_hooks.currentComponent = component
|
||||
|
||||
let result = component.render()
|
||||
|
||||
_hooks.currentComponent = nil
|
||||
_hooks.currentReconciler = nil
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func updateStateAndReconcile() {
|
||||
for (component, id, state) in queuedState {
|
||||
component.state[id] = state
|
||||
|
||||
component.update(with: renderer)
|
||||
component.update(with: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue