Created
July 21, 2016 09:06
-
-
Save xialvjun/341ec56cceb7bb00cc8455921e875ecd to your computer and use it in GitHub Desktop.
Inspired by theadam/react-flyd. Allows for flyd streams to be embedded directly into JSX, and to update content when the streams fire events.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, createElement, cloneElement, isValidElement } from 'react' | |
import { isStream, on, combine } from 'flyd' | |
export function reactive(tag='') { | |
class ReactiveClass extends Component { | |
constructor(props) { | |
super(props) | |
this.displayName = `ReactiveClass-${typeof tag==='string' ? tag : (tag.displayName || tag.name || '')}`; | |
this.state = {} | |
} | |
componentWillMount() { | |
this.subscribe(this.props) | |
} | |
componentWillReceiveProps(nextProps) { | |
this.subscribe(nextProps) | |
} | |
componentWillUnmount() { | |
this.unsubscribe() | |
} | |
addPropListener(name, prop$) { | |
on((value) => { | |
// don't re-render if value is the same. | |
if (value === this.state[name]) { | |
return | |
} | |
this.setState({ [name]: value }) | |
}, prop$) | |
// return these prop$ rather than above on$ because we usually create streams in jsx. Just end those on$ will left out those prop$s which will lead to a memory leak. | |
// And since prop$ is the parent of on$, just end prop$ is enough. | |
// And since we will end prop$, user shouldn't use outter streams directly on jsx. Just a stream.map(v => v) is enough. | |
return prop$ | |
} | |
subscribe(props) { | |
if (this.subscriptions) { | |
this.unsubscribe() | |
} | |
this.subscriptions = [] | |
Object.keys(props).forEach(key => { | |
const value = props[key] | |
if (isStream(value)) { | |
const subscription = this.addPropListener(key, value) | |
this.subscriptions.push(subscription) | |
} | |
}) | |
} | |
unsubscribe() { | |
this.subscriptions.forEach(subscription => subscription.end(true)) | |
this.subscriptions = null | |
this.state = {} | |
} | |
render() { | |
const finalProps = {...this.props, ...this.state} | |
if (tag) { | |
return createElement(tag, finalProps) | |
} | |
const {children, ...props} = finalProps | |
return cloneElement(children, props) | |
} | |
} | |
return ReactiveClass | |
} | |
function hasStream(obj) { | |
return Object.keys(obj).some(key => isStream(obj[key])) | |
} | |
function wrapChildren(children) { | |
const notValidElement$s = children.filter(child => isStream(child) && !isValidElement(child())) | |
if (notValidElement$s.length > 0) { | |
return [combine(() => { | |
return children.map(child => { | |
if (!isStream(child)) { | |
return child | |
} | |
if (notValidElement$s.indexOf(child) > -1) { | |
return child() | |
} | |
return createElement(reactive(), {}, child) | |
}) | |
}, notValidElement$s)] | |
} | |
return children.map(child => { | |
if (!isStream(child)) { | |
return child | |
} | |
return createElement(reactive(), {}, child) | |
}) | |
} | |
export default function h(tag, props, ...children) { | |
let defaultProps = props || {} | |
let wrappedChildren = wrapChildren(children) | |
if (hasStream(defaultProps) || hasStream(wrappedChildren)) { | |
return createElement(reactive(tag), defaultProps, ...wrappedChildren) | |
} | |
return createElement(tag, defaultProps, ...wrappedChildren) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment