Finishing reconciler in ComponentWrapper

This commit is contained in:
Max Desiatov 2018-12-02 19:56:53 +00:00
parent 707d696f25
commit 7d300fab6a
No known key found for this signature in database
GPG Key ID: FE08EBF9CF58CBA2
4 changed files with 159 additions and 68 deletions

View File

@ -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:
()
}
}
}

View File

@ -7,7 +7,7 @@
public struct Hooks {
var currentReconciler: StackReconciler?
var currentReconciler: Reconciler?
var currentComponent: CompositeComponentWrapper?
public func state<T>(_ initial: T,

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}