ClojureScript

Reagent

Reframe

Javascript

React

Redux

vs

DongWoo Kim @NHNEnt

Clojure

Clojure

  • Functional Language
    • Immutable Data + First Class Function
  • Modern Lisp
    • Code as Data -> map, vector
  • Dynamic Language
  • Polymorphism - Multimethod / Protocol
  • Runs on JVM (symbiotic)

Why Clojure?

  • A Lisp
  • for Functional Programming
  • symbiotic with an established Platform
  • designed for Concurrency

By Rich Hickey

Why Clojure?

  • Functional : Solution to the multi-core problem
  • Lisp : Simple Syntax, Homoiconic
  • Runs on JVM : Interoperate with Java
  • Concurrncy : Software Transactional Memory
  • Performance : Shared Immutable Data Structure
  • Support : Communities and Tools

by Uncle Bob

Javascript

Javascript

  • Designed by Brendan Eich in 1995
  • Dynamic & Weakly typed Language
  • Multi-paradigm Language
    • OO + Functional + Imperative
  • Interpreted Language (JIT)

Influenced by...

C Syntaxes (if, while, for, switch …)
Distinguishes statement and expression
Java Name, Syntaxes for OOP
Core APIs (Math, Date…)
Scheme Closure, Lexical Scope
First Class Function, Dynamic Type
Self Prototype based Inheritence

Most Popular Language

Most Popular Language

  • Browsers can only run Javascript
    (...except Web Assembly)
  • Node.js : Server-Side Application
  • Electron : Desktop Application
  • React Native : Native Mobile Application

Any application that can be written in JavaScript, will eventually be written in JavaScript

- Jeff Atwood 2007 -

 

Most Blamed Language

Most Blamed Language

Most Blamed Language

  • ​Started as a trivial script language
  • Compatibility: Can't break exisiting web pages
  • Has improved significantly since ES2015
  • 2017 Most loved Language Ranked #11
    (Same as Clojure)

Caveats

Compile to Javascript

  • 2006 Google Web Toolkit
  • 2007 CoffeeScript
  • 2011 Dart
  • 2011 ClojureScript
  • 2012 TypeScript
  • 2012 Elm
  • 2013 PureScript
  • 2016 BuckleScript / Reason

ClojureScript

Rational

  • JavaScript’s Reach
  • JavaScript is not Robust
  • Client-service Applications are on the Rise
  • JavaScript Engines Gain Power
  • Google Leads the Way
  • The Library Problem

ClojureScript

  • Compiler for Clojure that targets Javascript
  • Written in Clojure
    • Runs on JVM (Cross Compiler)
  • Using Google Clojure Library
  • Using Google Closure Compiler (GCC)

Closure Library

  • Open Source Library set used by Google Services
  • Cross-Browser, OOP, Math, String, DOM...
  • Used by ClojureScript-generated Javascript
  • Intended for use with the Clojure Compiler

Closure Compiler

  • Written in Java (runs on JVM)
  • Compiles from JavaScript to better JavaScript
  • Using JSDoc annotation for better optimizing
  • Check syntax, types, dangerous codes
  • Minifying & Mangling
  • Dead Code Elimination
  • Code Splitting

Persistent Data Structure

JS vs CLJS

Similarities

  • First Class Function
  • Dynamic Typing
  • Anonymous function (Lambda)
  • Closure & Lexical Scoping
  • Destructuring
  • Rest Arguments
  • Literal Syntax
    • JS: Object / Array
    • CLJS: Map / Vector / Set / List

Mutable vs Immutable


 const state = {count: 0}

 state.count = state.count + 1

 (def state (atom {:count 0}))

 (swap! state #(update % :count inc))

JS

CLJS

Reference vs Value


 const personA = {
   age: 32,
   name: {first: 'John', last: 'Doe'}
 }

 const personB = {
   age: 32,
   name: {first: 'John', last; 'Doe'}
 }

 console.log(personA === personB) 
 // false

 (def person-a {:age 32
                :name {:first "John"
                       :last "Doe"}})

 (def person-b {:age 32
                :name {:first "John" 
                       :last "Doe"}})
 
 (println (= person-a person-b))
 ;; true

JS

CLJS

Core Library

JS

CLJS

map, map-indexed, mapcat, reduce, first, filter, find, flatten, get, get-in, group-by, identical?, interleave, interpose, juxt, keys, last, next, nfirst, nnest, partial, partition, partition-all, partition-by, take, take-last, take-nth, take-while, trampoline, transduce, vals....

map, filter, reduce, concat, assign, some, every, keys, find...

+

Lodash.js

(Ramda.js)

Async

JS

CLJS

core.async

Promise, Generator

Async / Await

+

js-csp

(RxJS)

Tools

JS CLJS
Dependency npm / yarn Leiningen / Boot
Scaffolding Yeoman Leiningen / Boot
Compile Babel / TypeScript Leiningen / Boot
Bundling Webpack / Rollup Leiningen / Boot
Interactive
Programming
Webpack Dev Server Figwheel

Comparision

Hot Module Replace ++

Dead Code Elimination ++

Lisp!

Javascript

lodash.js

Immutable.js

js-csp

Babel

Webpack

npm / Yeoman

ClojureScript

Leiningen

Figwheel

JS Interop

Global Vars / API


 (js/alert "Hello")

 (js/document.getElementById "app")

 (js/document.body.lastChild.innerHTML.charAt 7)

 (js/some.of.my.libraries.api.method "args")

 (js/$.ajax #js {:url "/"
                 :success (fn [res] (js/console.log res))})

Method / Property


 ;; same as javacript string
 (def s "Hello Clojure")

 ;; method call
 (.toUpperCase s) ;; "HELLO CLOJURE"

 ;; property access
 (.-length s) ;; 13

 ;; chaning
 (def my-div (js/document.querySelector "div"))

 (.-length (.toUpperCase (.-innerHTML my-div)))
 (->> my-div .-innerHTML .toUpperCase .-length)
 (.. my-div -innerHTML toUpperCase -length)

Object / Array


 ;; macro
 (def arr (array 1 2 3))
 (def obj (js-obj "x" 1 "y" 2))

 ;; reader literal
 (def arr #js [1 2 3])
 (def obj #js {:x 1 :y 2})

 ;; clj->js function
 (def arr (clj->js [1 2 3]))
 (def obj (clj->js {:x 1 :y 2}))

 ;; jc->clj function
 (def arr-clj (js->clj arr))
 (def obj-clj (js->clj obj))

ClojureScript -> Javascript

React

Declarative

  • Imperative -> Declarative
  • Don't need to manipulate DOM directly
  • Using Virtual DOM Structure (React Element)
  • Optimized DOM manipulation by Reconcile Engine

Component Based

  • Build encapsulated components that manage their own state
  • Build pages by composing these Components
  • Send data between components using props
  • Template -> JSX (Javascript Data Structure)

Learn Once,

Write Anywhere

  • React Native
  • React VR

Sample Code

 
 class Counter extends React.Component {
   state = {
     count: 0
   }

   increase() {
     this.setState({
       count: this.state.count + 1
     });
   }

   render() {
     return (
       <div>
         <button onClick={() => this.increase()}>
           Count: {this.state.count}
         </button>
       </div>
     );
   }
 }

React

Functional Approach

JSX = Javascript


 <div>
   <TodoInput />
   <ul>
     {todos.map(todo => 
       <TodoItem todo={todo} />
     )}
   </ul>
   <div>Completed: {completed.length}</div>
 </div>

 React.createElement(
   "div",
   null,
   React.createElement(TodoInput, null),
   React.createElement(
     "ul",
     null,
     todos.map(function (todo) {
       return React.createElement(
         TodoItem, { todo: todo }
       );
     })
   ),
   React.createElement(
     "div",
     null,
     "Completed: ",
     completed.length
   )
 );

Component = Function

  • Input : Props
  • Ouput : V-DOM(React Element) Tree

All React components must act like pure functions with respect to their props.

Immutable Props

  • Props are read-only

Whether you declare a component as a function or a class,
it must never modify its own props

Immutable State

  • call setState() for explicit state change

Never mutate this.state directly,
as calling setState() afterwards may replace the mutation you made.
Treat this.state as if it were immutable.

SholuldComponentUpdate

SCU

Immutability for Performance

  • shallow compare to determine updating
 
 class MyComponent extends React.Component {
   shouldComponentUpdate(nextProps, nextState) {
     return Object.keys(nextProps).every(
       key => nextProps[key] === this.props[key]
     );
   }
   render() {
     return <div className={this.props.className}>foo</div>;
   }
 }
 
 class MyComponent extends React.PureComponent {
   render() {
     return <div className={this.props.className}>foo</div>;
   }
 }

Reagent

  • Define components using plain function
  • Define DOM structures using vector, map (Hiccup)
  • Use R-atom (extended atom) to manage state
    • Automagically re-rendered R-atom changes
  • Optimized performance using value compare

Simple React Wrapper

Reagent provides a minimalistic interface between ClojureScript and React.

Sample Code


 (def click-count (r/atom 0))

 (defn counting-component []
   [:div
    "The atom " [:code "click-count"] " has value: "
    @click-count ". "
    [:input {:type "button" :value "Click me!"
             :on-click #(swap! click-count inc)}]])
 
 (defn hello-component [name]
   [:p "Hello, " name "!"])

 (defn say-hello []
   [hello-component "world"])

React vs Reagent

Simple Component

 
 function simpleComponent() {
   return (
     <div>
       <p>I am a component!</p>
       <p className="someclass">
         I have 
         <strong>bold</strong>
         <span style={{color: "red"}}> and red</span>
         text.
       </p>
     </div>
   )
 }

React

Simple Component


 (defn simple-component []
   [:div
    [:p "I am a component!"]
    [:p.someclass
      "I have " 
      [:strong "bold"]
      [:span {:style {:color "red"}} " and red "] 
      "text."]])

Reagent

Nested Component


 const todos = [
   'Learn ClojureScript',
   'Learn Reagent',
   'Learn Reframe'
 ];

 function TodoItem(props) {
   return <li className="todo-item">{props.todo}</li>;
 }

 function TodoList() {
   return (
     <ul className="todo-list">
       {todos.map(todo => <TodoItem todo={todo} />)}
     </ul>
   );
 }

React

Nested Component


 (def todos ["Learn ClojureScript"
             "Learn Reagent"
             "Learn Reframe"])

 (defn todo-item [todo]
   [:li {:class "todo-item"} todo])

 (defn todo-list []
   [:ul {:class "todo-list"}
    (for [todo todos] [todo-item todo])])

Reagent

State


 function TodoItem({todo, deleteTodo}) {
   return (
     <li className="todo-item">
       {todo.text}
       <button onClick={() => deleteTodo(todo.id)}>X</button>
     </li>
   );
 }

 class TodoList extends React.Component {
   state = {
     todos: [
       {id: 1, text: 'Learn ClojureScript'},
       {id: 2, text: 'Learn Reagent'},
       {id: 3, text: 'Learn Reframe'}
     ]
   }

   deleteTodo = (targetId) => {
     const {todos} = this.state;

     this.setState({
       todos: todos.filter(({id}) => targetId !== id)
     });
   }

   render() {
     return (
       <ul className="todo-list">
         {this.state.todos.map(todo =>
           <TodoItem 
             key={todo.id}
             todo={todo}
             deleteTodo={this.deleteTodo} />
         )}
       </ul>
     );
   }
 }

React

State

 
 (def todos (r/atom [{:id 1, :text "Learn ClojureScrpt"}
                     {:id 2, :text "Learn Reagent"}
                     {:id 3, :text "Learn Reframe"}]))

 (defn delete-todo [todo]
   (swap! todos (partial remove #(= todo %))))

 (defn todo-item [todo]
   [:li {:class "todo-item"}
     (:text todo)
     [:button {:on-click #(delete-todo todo)} "X"]])

 (defn todo-list []
   [:ul
    (for [todo @todos]
      ^{:key (:id todo)} [todo-item todo])])

Reagent

Controlled Component

React

 
 class TodoInput extends React.Component {
   state = {value: ''}

   onChange = ev => {
     this.setState({value: ev.target.value});
   }

   onKeyPress = ev => {
     if (ev.which === 13) {
       this.props.addTodo(this.state.value);
     }
   }

   render() {
     return (
       <input type="text"
         onChange={this.onChange}
         onKeyPress={this.onKeyPress}
         value={this.state.value} />
     );
   }
 }

Controlled Component

Reagent


 (defn todo-input []
   (let [value (r/atom "")
         on-change #(reset! value (.. % -target -value))
         on-key-press #(if (= (.-which %) 13) 
                         (add-todo @value))]
     (fn []
       [:input {:type :text
                :on-change on-change
                :on-key-press on-key-press
                :value @value}])))

Performance

React

 
 class Counter extends React.PureComponent {
   render() {
     const {idx, increase, data: {info, value}} = this.props;
     return (
       <button onClick={() => increase(idx)}>
         {info.name} : {value}
       </button>
     );
   }
 }

 class CounterApp extends React.Component {
   state = {
     counters: [
       {info: {name: "counter1", step: 5}, value: 0},
       {info: {name: "counter2", step: 10}, value: 0},
       {info: {name: "counter3", step: 15}, value: 0}
     ]
   }

   increase = idx => {
     const counters = [...this.state.counters];
     const target = counters[idx];
     counters[idx] = {
       ...target, 
       value: target.value + target.info.step
     }

     this.setState({counters});
   }

   render() {
     return (
       <div>
         {this.state.counters.map((data, idx) =>
           <Counter key={idx} idx={idx}
             data={data} increase={this.increase} />
         )}
       </div>
     );
   }
 }

Performance

Reagent


 (def counters (r/atom [{:info {:name "count1" :step 5} :value 0}
                        {:info {:name "count2" :step 10} :value 0}
                        {:info {:name "count3" :step 15} :value 0}]))

 (defn counter [idx {{:keys [name step]} :info value :value}]
   (let [increase #(+ step value)
         on-click #(swap! counters update-in [idx :value] increase)]
     [:button {:on-click on-click}
      (str name " : " value)]))

 (defn counter-app []
   [:div
    (map-indexed (fn [idx data]
                   ^{:key idx} [counter idx data]) @counters)])

Conclusion

React Reagent
HTML JSX (Syntax)  Hiccup (Data)
Component function, Class function
Pass data props (this.props) object arguments
Change state this.setState() change R-atom
Change state (parent) Pass a function calling this.setState() via props change R-atom
Performance showComponentUpdate()
PureComponent
Automatic

Redux

React & State

  • Managing complex state with React is hard
  • Another layer for managing state is required
  • Redux, Relay, MobX...

Redux - Concept

  • Uni-directional data flow
  • Single store
  • CQRS / Event Sourcing

Redux

traditional MVC

Single source of truth

  • Store whole application state within a single tree
  • Makes it easier to debug or inspect an application

 console.log(store.getState())
 /* 
 {
   visibilityFilter: 'SHOW_ALL',
   todos: [
     {
       text: 'Consider using Redux',
       completed: true,
     },
     {
       text: 'Keep all state in a single tree',
       completed: false
     }
   ]
 }
 */

State is Read-Only

  • No setter for the state
  • The only way to change the state is to emit an Action
  • Actions are just plain object
  • Event sourcing -> easy to keep track of state history
 
 store.dispatch({
   type: 'COMPLETE_TODO',
   index: 1
 })

 store.dispatch({
   type: 'SET_VISIBILITY_FILTER',
   filter: 'SHOW_COMPLETED'
 })

Pure function & Immutable State

  • Reducers are just pure function
  • Take the previous state and action
  • Return new state
 
 function todos(state = [], action) {
   switch (action.type) {
     case 'ADD_TODO':
       return state.concat([{ text: action.text, completed: false }])
     case 'TOGGLE_TODO':
       return state.map(
         (todo, index) =>
           action.index === index
             ? { text: todo.text, completed: !todo.completed }
             : todo
       )
     default:
       return state
   }
 }

Why Immutable?

  • Performance
    • Change detection : shallow equality check
    • Great fit for React's paradigm
  • Time-Travel Debugging
    • Easy to store every changes of the state
  • Hot Module Replacement
  • Easy to test

Immutability 

in Javascript

Example

 
 const state = {
   artist: {
     name: {
       first: 'Michael',
       last: 'Jackson'
     },
     born: '1958-08-29'
   },
   genre: ['pop', 'soul', 'disco', 'rock'],
   albums: []
 }

Plain Javascript

 
 function updateLastName(state, lastName) {
   return {
     ...state,
     artist: {
       ...state.artist,
       name: {
         ...state.artist.name,
         last: lastName
       }
     }
   };
 }

 const state1 = updateLastName(state, 'Jordan');
 const state2 = updateLastName(state, 'Jordan');

 console.log(state1 === state2); // false

Lodash-fp / Ramda

 
 import _ from 'lodash/fp'

 function updateLastName(state, lastName) {
   return _.set(state, ['artist', 'name', 'last'], lastName);
 }

 const state1 = updateLastName(state, 'Jordan');
 const state2 = updateLastName(state, 'Jordan');

 console.log(state1 === state2); // false

Immutable.js

 
 // Immutable 객체로 변경
 const stateIM = Immutable.Map(state);

 function updateLastName(state, lastName) {
   return state.setIn(['artist', 'name', 'last'], lastName);
 }

 // 비교
 const state1 = updateLastName(staetIM, 'Jordan');
 const state2 = updateLastName(staetIM, 'Jordan');
 
 console.log(state1 === state2);     // false
 console.log(state1.equal(state2));  // true

 // 일반 JS로 변경
 const stateJS = stateIM.toJS();

ClojureScript

 
 (def state 
   {:artist {:name {:first "Michael"
                    :last "Jackson"}
             :born "1958-08-29"}
    :genre ["pop" "soul" "disco" "rock"]
    :albums []})

 

 (defn update-last-name [state last-name]
   (assoc-in state [:artist :name :last] last-name))

 (def state1 (update-last-name state "Jordan"))
 (def state2 (update-last-name state "Jordan"))
 
 (js/console.log (= state1 state2)) ;; true

https://hackernoon.com/functional-programming-in-javascript-is-an-antipattern-58526819f21e

https://hackernoon.com/functional-programming-in-javascript-is-an-antipattern-58526819f21e

Reframe

Reframe

  • Release in early 2015 (older than Redux)
  • Based on the Reagent (using R-atom)
  • Similarities with Redux
    • Unidirectional data flow
    • Single state / Event sourcing
  • Differences with Redux
    • Event + Effect (vs Action)
    • Subscription
    • Everything is data (effect, interceptor)

Action

  • Plain object with 'type' property
  • Passed to reducers to change the state
  • Use middleware to manage side effects

 {
   type: 'ADD_TODO',
   text: 'Learn Reframe'
 }

 {
   type: 'TOGGLE_TODO',
   id: 10
 }

Event

  • Vector (first element is the name of the event)
  • Passed to the event handler -> Emit effects
  • Can not affect to the state of the application

 [:add-todo "Learn Reframe"]

 [:toggle-todo 10]

Effect

  • Map data describing side effects
  • Passed to the effect handlers

 {:db {:todos [{:id 1 
                :text "Learn Reframe"
                :completed false}]}
  :http {:method :post
         :url "http://my.app/todos"
         :on-success [:process-response]
         :on-fail [:failed-todos]}}

Subscription

Redux (+ Reselect)


 import { createSelector } from 'reselect'

 const getShowing = (state) => state.showing

 const getTodos = (state) => state.sortedTodos

 export const getVisibleTodos = createSelector(
   [getShowing, getTodos],
   (showing, todos) => {
     switch (showing) {
       case 'ALL':
         return todos
       case 'DONE':
         return todos.filter(t => t.completed)
       case 'ACTIVE':
         return todos.filter(t => !t.completed)
     }
   }
 )

Subscription

Reframe

 
 (ns todoapp.subs
   (:require [re-frame.core :refer [reg-sub]]))

 (reg-sub :showing #(:showing %))

 (reg-sub :todos #(vals (:sorted-todos %)))

 (reg-sub :visible-todos
   :<- [:showing]
   :<- [:todos]
   (fn [[showing todos] _]
     (filter (case showing
               :done :done
               :active (complement :done)
               :all identity) todos)))

Redux vs Reframe

Redux

Reframe

Conclusion

Redux

Reselect

Reframe

Javascript

Immutable.js

lodash.js

ClojureScript

React

Recompose

Reagent

npm / webpack

Babel / react-create-app

Leiningen

Figwheel

And...

  • Simple syntax
  • macro
  • Clojure libraries
  • Interactive Programming
  • clojure.spec
  • core.async

But...

  • Steep learning curve
  • JVM...
  • Debugging...

Thank you :)

clojurescript-reagent-reframe (ENG)

By DongWoo Kim

clojurescript-reagent-reframe (ENG)

Comparing "Javascript - React - Redux" with "ClojureScript - Reagent - Reframe"

  • 837