Advertisement
Guest User

Untitled

a guest
May 27th, 2015
322
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.20 KB | None | 0 0
  1. ## Observable React elements with RxJS
  2.  
  3. When I just started using RxJS with React, I was subscribing to observables in `componentDidMount` and disposing subscriptions at `componentWillUnmount`.
  4.  
  5. But, soon I realised that it is not fun to do all that subscriptions(that are just updating property in component state) manually, and written mixin for this...
  6.  
  7. Later I have rewritten it as "high order component" and added possibility to pass also obsarvers that will receive events from component.
  8.  
  9. But, shortly after, I realised that for server side rendering, I need to do something quite complicated(in order to wait until all data would be loaded, before rendering - actually, I was building an isomorphic application, and still working on it).
  10.  
  11. I thought - why not just make observable react elements?
  12.  
  13. And, I realised that it is completely ok, and much better than previous attempt.
  14.  
  15. My implementation looks following:
  16.  
  17. ```JSX
  18. function observableObject(observables) {
  19. var keys = Object.keys(observables);
  20. var valueObservables = keys.map(key=>observables[key]);
  21.  
  22. if (keys.length === 0) {
  23. return Rx.Observable.return({});
  24. }
  25.  
  26. return Rx.Observable.combineLatest(valueObservables, (...values)=> {
  27. let res = {};
  28. for (let i = 0, l = keys.length; i < l; i++) {
  29. res[keys[i]] = values[i];
  30. }
  31. return res;
  32. })
  33. }
  34. // creates stream of react elements
  35. function observableElement(Element, observables = {}, observers = {}, props = {}) {
  36. var propsObservable = observableObject(observables);
  37.  
  38. var callbacks = {};
  39.  
  40. Object.keys(observers).forEach(key=> {
  41. var observer = observers[key];
  42. callbacks[key] = (value)=>observer.onNext(value);
  43. });
  44.  
  45. return propsObservable.map((state)=> {
  46. return (
  47. <Element {...props} {...state} {...callbacks}/>
  48. );
  49. });
  50. }
  51. ```
  52.  
  53. And main application, changed from:
  54.  
  55. ```JSX
  56. React.render(
  57. <ExampleApplication data={observable} handler={observer}/>
  58. document.getElementById('container')
  59. );
  60. ```
  61.  
  62. to something like:
  63.  
  64. ```JSX
  65. observableElement(ExampleApplication, {data:observable}, {handler:observer}})
  66. .subscribe((element)=>{
  67. React.render(element, document.getElementById('container'));
  68. });
  69. ```
  70.  
  71. And it is pretty awesome:
  72. - in comparison to mixin, react components again are just views
  73. - observables are easy to composse, e.g. - I can pass one element steam to other one, or jusr combine few of them..
  74. - no need to specify default values for each observable for first render - component would be rendered only when all required data would be loaded
  75.  
  76. And most awesome part - it happens to be isomorphic, for server side in my request handler, I need to do just this:
  77.  
  78. ```JSX
  79. observableElement(ExampleApplication, {data:observable}, {handler:observer}})
  80. .first()
  81. .subscribe((element)=>{
  82. var html = React.renderToString(element);
  83. res.render('main', {appHtml: html});
  84. });
  85. ```
  86.  
  87. It will automatically wait for all data required for rendering, and after rendering, as expected - it will dispose everything not needed.
  88.  
  89. ### Update 1:
  90.  
  91. It was good, but not as optimal as it can be...
  92.  
  93. The problem is that everytime when something changes we are updating whole rendering tree, but react is capable to update olny changed subtree, so I decided to add Proxy element that will not be changing and will update nested component when some of obseravles emit new value.
  94.  
  95. Also I have rewritten observableElement function as a class allowing to extend it adding new properties.
  96.  
  97. ### Update 2
  98.  
  99. Steams of react element are awesome… But - they are not working well with react `context`, and as figured out - to support it I should create react element only inside render function...
  100.  
  101. So, I decided to switch from streams of react elements to stream of react components.
  102.  
  103. Also I have added method on RxComponent allowing to decorate encolosed component(it allows to apply "high order component" to react compoment enclosed in RxCompoent).
  104.  
  105. ```JSX
  106. function createProxyComponent(Component, observable, initialState) {
  107. class RxProxy extends React.Component {
  108. componentWillMount() {
  109. this.setState(initialState);
  110. }
  111.  
  112. componentDidMount() {
  113. this.subscribtion = observable.subscribe((state)=> {
  114. this.setState(state);
  115. });
  116. }
  117.  
  118. componentWillUnmount() {
  119. this.subscribtion.dispose();
  120. }
  121.  
  122. render() {
  123. return (<Component {...this.props} {...this.state}/>);
  124. }
  125. }
  126. return RxProxy;
  127. }
  128.  
  129. /**
  130. * Creates observable form ready to render ReactElements.
  131. * The same ReactElement would be emitted on every observables combination.
  132. */
  133. class RxComponent extends AnonymousObservable {
  134. /**
  135. * @param {React.Component} Component
  136. * @param {Object.<string, Rx.Observable>=} observables
  137. * @param {Object.<string, Rx.Observer>=}observers
  138. * @param {Object=} props
  139. */
  140. constructor(Component, observables = {}, observers = {}, props = {}) {
  141. super(observer=> {
  142. const callbacks = {};
  143.  
  144. Object.keys(observers).forEach(key=> {
  145. callbacks[key] = (value)=>observers[key].onNext(value);
  146. });
  147.  
  148. const propsObservable = objectObserver(observables).share();
  149.  
  150. const initialState = {};
  151. const Proxy = createProxyComponent(Component, propsObservable, initialState);
  152. Proxy.defaultProps = Object.assign({}, props, callbacks);
  153.  
  154. return propsObservable
  155. .do(state=>Object.assign(initialState, state))
  156. .map(()=>Proxy)
  157. .subscribe(observer);
  158. });
  159.  
  160. this.params = [Component, observables, observers, props];
  161. }
  162.  
  163. /**
  164. * Extend defined params
  165. * @param {Object.<string, Rx.Observable>=} observables
  166. * @param {Object.<string, Rx.Observer>=} observers
  167. * @param {Object=} props
  168. * @returns {RxComponent}
  169. */
  170. extend(observables, observers, props) {
  171. const [Component, prevObservables, prevObservers, prevProps] = this.params;
  172. return new RxComponent(
  173. Component,
  174. observables && Object.assign({}, prevObservables, observables),
  175. observers && Object.assign({}, prevObservers, observers),
  176. props && Object.assign({}, prevProps, props)
  177. );
  178. }
  179.  
  180. /**
  181. * Extend defined params
  182. * @param {function(component: React.Component): React.Component} decorator
  183. * @returns {RxComponent}
  184. */
  185. decorate(decorator) {
  186. const [Component, observables, observers, props] = this.params;
  187. return new RxComponent(
  188. decorator(Component),
  189. observables,
  190. observers,
  191. props
  192. );
  193. }
  194. }
  195. ```
  196.  
  197. Now it can be used as following:
  198.  
  199. ```JSX
  200. let appComponent = new RxComponent(ExampleApplication, {data:observable}, {handler:observer},{property});
  201.  
  202. // add/replace some properties
  203. // this can be quite useful if you want to have some defaults in your component definition
  204. // or just want to replace something defined before, or add context specific properties
  205.  
  206. appComponent = appComponent.extend({/* observables */}, {/* observers */}, {/* properties */})
  207.  
  208. // create new Observalbe with only distinct elements (initialy RxComponent will emit the same element on overy data change)
  209. //
  210. // it is not inside RxComponent to allow side effects implementation when data changes...
  211. // example of one of possible side effects: srolling to hash after data changes are rendered
  212.  
  213. appCompomnent = appComponent.distinctUntilChanged();
  214.  
  215. appComponent
  216. .subscribe((Component)=>{
  217. React.render(<Component/>, document.getElementById('container'));
  218. });
  219. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement