ClojureScript

A better JavaScript for React/Redux

김동우 @NHNEnt

About Me

  • 2006 ~ : Full Stack
  • 2012 ~ : 방황 
  • 2015 ~ : Front-end

Clojure는...

2016년부터 (외롭게) 공부중

지금은..

Toast File (React + Redux)

Spacemacs!

Clojure

LISP

  • 현존하는 가장 오래된 언어 중 하나 (1958)
  • LISP가 처음 소개한 개념들
    • 조건문, 가비지 컬렉터, 동적 타이핑
    • 긴 변수명, 리터럴 데이터 구조
    • REPL, 일급함수, 재귀함수, 매크로
  • Code As Data - 동형성 (homoiconic)
  • LISt Procesor : 문법 X, 리스트 O
    • 전체 코드 : 표현식(Expression)의 트리

Lisp: 해커의 언어

Eric S. Raymond 

Paul Graham

Alan Kay

Lisp 연대

1세대

  • FORTRAN (1957)
  • LISP (1958)
  • ALGOL 58 (1958)
  • COBOL (1959)

2세대

  • Pascal (1970)
  • C (1972)
  • SmallTalk (1972)
  • Scheme (1972)

80년대

  • C++ (1980)
  • Common Lisp (1984)
  • Emacs Lisp (1985)
  • Objective-C (1986)

90년대

  • Python (1991)
  • Racket (1994)
  • Java (1995)
  • JavaScript (1995)

최근

  • Scala (2003)
  • Clojure (2007)
  • Go (2009)
  • Swift (2014)

Clojure: Modern Lisp 

LISP (1958)

John McCarthy

Clojure (2007)

Rich Hichey

Clojure

  • 함수형 언어 : 불변 데이터형 + 일급 함수
  • LISP++ : Code-as-data -> map, vector
  • 다형성(Polymorphism) 지원
  • 동적(Dynamic) 언어
  • JVM 환경에서 실행 (공생 언어)

Why Clojure?

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

Why Clojure?

  • 함수형 언어 : 멀티 스레딩 프로그래밍의 어려움 해결
  • Lisp : 간결한 문법. Homoiconic
  • JVM 환경에서 실행 : 기존 Java 코드와 호환
  • STM 지원 : 동시성 문제 해결
  • 성능 : 메모리를 공유하는 불변 데이터 구조 사용
  • 지원 : 커뮤니티와 다양한 도구들

Persistent Data Structure

Example


  (defn flatten-and-remove-nils
    "`interceptors` might have nested collections, and contain nil elements.
    return a flat collection, with all nils removed.
    This function is 9/10 about giving good error messages."
    [id interceptors]
    (let [make-chain  #(->> % flatten (remove nil?))]
      (if-not debug-enabled?
        (make-chain interceptors)
        (do    ;; do a whole lot of development time checks
          (when-not (coll? interceptors)
            (console :error "expected a collection of interceptors, got:" interceptors))
          (let [chain (make-chain interceptors)]
            (when (empty? chain)
              (console :error "given an empty interceptor chain"))
            (when-let [not-i (first (remove interceptor/interceptor? chain))]
              (if (fn? not-i)
                (console :error "got a function instead of an interceptor" not-i)
                (console :error "expected interceptors, but got:" not-i)))
            chain)))))

How to save the princess in...

How to save the princess in...

JavaScript

JavaScript

  • 1995년 Brendan Eich가 디자인
  • 동적 / 약한 타입의 언어
  • 멀티 패러다임 언어 : 객체지향 + 절차형 + 함수형
  • 인터프리터 언어 (JIT)

Influenced by...

C 문법 (if, while, for, switch …)
statement 와 expression 구분
Java 이름, 문법 (new…)
Core 및 라이브러리 API (Math, Date…)
Scheme Closure, Lexical Scope
일급함수, 동적 타입, (eval?)
Self Prototype 상속

Most Popular Language

Most Popular Language

  • 브라우저 환경에서 사용할 수 있는 유일한 언어
  • Node.js : 백엔드 개발
  • Electron : 데스크탑 어플리케이션 개발
  • React Native : 네이티브 모바일 어플리케이션 개발

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

  • ​처음에 단순한 스크립트 언어로 시작
  • 웹: 하위호환성을 무시할 수 없음
  • ES2015 부터 기존의 많은 문제점 개선
  • 현재 아주 빠른 속도로 발전중
  • 2017 Most loved Language 11위 (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

  • Clojure 코드를 Javascript 로 변환하는 컴파일러
  • Clojure 로 작성됨
    • 컴파일을 위해 JVM 환경 필요 (크로스 컴파일러)
  • Google Closure Library 사용
  • Google Closure Compiler (GCC) 사용

Closure Library

  • Google 서비스에 사용되는 오픈소스 라이브러리 셋
  • 브라우저 호환성, 네임스페이스, OOP, Math, String, DOM 등 다양한 기능 제공
  • ClojureScript 컴파일러가 생성하는 코드에서 사용됨
  • Closure Compiler 에 최적화됨

Closure Compiler

  • Java 로 작성됨 (실행시 JVM 필요)
  • Javascript 코드를 최적화해서 새로운 코드를 생성
  • JSDoc 을 최적화 및 검사에 활용
  • 문법, 타입 오류, 위험한 코드 검사
  • Minify & Mangle
  • Dead Code Elimination
  • Code Spliting

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

Literal Syntax

 // Object
 {
   name: 'Kim',
   age: 32
 }

 // Array
 [1, 2, 3, 4, 5]


 ;; Vector 
 [1 2 3 4 5]

 ;; List
 '(1 2 3 4 5)

 ;; Map
 {:name "Kim"
  :age 32}

 ;; Set
 #{1 2 3 4 5}

JS

CLJS

Function


 // function declaration
 function hello(name) {
   return 'Hello, ' + name;
 }

 // function expression
 const hello = function(name) {
   return 'Hello, ' + name;
 }

 // arrow function (ES6)
 const hello = (name) => 'Hello, ' + name;

 // call
 hello('Kim');
 
 



 ;; defn
 (defn hello [name]
   (str "Hello, " name))


 ;; def + fn
 (def hello 
   (fn [name]
     (str "Hello, " name)))

 ;; def + #()
 (def hello #(str "Hello, " %))

 ;; call
 (hello "Kim")

JS

CLJS

Mutable vs Immutable


 const state = {count: 0}

 state.count = state.count + 1
 ;; var
 (def state {:count 0})

 (def state-a (assoc state :count 1)} 

 (def state-b (update state :count inc)} 

 ;; atom 
 (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 / CRA 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
  • Virtual-DOM (React Element)
  • DOM을 직접 다루지 않고, 가상 DOM의 구조를 반환
  • Reconcile 엔진이 변경을 최적화해서 DOM에 반영

Component Based

  • Component를 합성하여 UI 구성
  • State 와 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는 수정할 수 없음

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

Immutable State

  • this.state 직접 변경 금지
  • this.setState() 를 호출해서 명시적 변경

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 비교만으로 변경여부를 알 수 있음
 
 class MyComponent extends React.Component {
   shouldComponentUpdate(nextProps, nextState) {
     const isPropsChanged = shallowEqual(this.props, nextProps);
     const isStateChanged = shallowEqual(this.state, nextState);

     return isPropsChanged || isStateChanged;
   }
   render() {
     return <div className={this.props.className}>foo</div>;
   }
 }
 
 class MyComponent extends React.PureComponent {
   render() {
     return <div className={this.props.className}>foo</div>;
   }
 }

Reagent

  • 함수만으로 컴포넌트를 작성
  • vector, map 만으로 (Hickup) DOM 구조 표현
  • State 관리를 위한 r/atom 제공 (atom 확장)
    • 변경될 때마다 deref 하는 컴포넌트를 자동으로 Re-render
  • Value 비교를 통한 성능 최적화

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.PureComponent {
   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
Data 전달 props (this.props) 객체 arguments
State 변경 this.setState() r/atom 변경
State 변경 (부모) this.setState() 호출하는 함수를 props로 전달 r/atom 변경
성능 showComponentUpdate()
PureComponent
자동 최적화

Redux

React의 State 관리

  • React만으로는 State 관리가 어려움
  • State를 관리하는 별도의 레이어가 필요
  • Redux, Relay, MobX 등을 사용

Redux - Concept

  • 단방향 데이터 흐름
  • 단일 스토어
  • CQRS / Event Sourcing

Redux

기존 MVC

Single source of truth

  • 전체 어플리케이션의 상태를 단일 트리 형태로 저장
  • 전체 상태를 관리하고, 서버 등의 환경과 공유하기 쉬움
  • 개발도구에서 상태가 변경된 모든 이력을 확인

 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

  • State를 직접 변경할 수 없음 (setter X)
  • 오직 Action(데이터 객체) 을 발생시켜서 State를 변경
  • Event Sourcing -> 전체 변경내역을 관리하기 용이
  • 하나의 Action에 하나의 변경 -> 복잡한 상호관계가 없음
 
 store.dispatch({
   type: 'COMPLETE_TODO',
   index: 1
 })

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

Pure function & Immutable State

  • Reducer는 순수 함수 (Side Effect 없음)
  • 각 Action에 맞게 항상 새로운 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
    • 상태의 변경을 확인할 때, 레퍼런스만으로 Shallow 비교가 가능
    • React가 추구하는 방식과 잘 어울림
  • Time-Travel Debugging
    • 상태 변경의 모든 이력을 저장하기 쉬움
    • Time-Travel 디버깅 가능
  • Hot Module Replacement
  • 테스트가 용이

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

  • Redux 와 비슷한 시기에 등장 (2015)
  • Reagent 기반 (Ratom 사용)
  • Redux와의 유사성
    • 단방향 데이터 흐름
    • 단일 State / Event Sourcing
  • Redux와의 차이점
    • Event + Effect (vs Action)
    • Subscription
    • 모든 것은 Data (Effect, Interceptor) 

Action

  • type 을 갖는 순수 객체
  • Reducer로 바로 전달되면 Store를 변경시킴
  • 다른 Side Effect 위해서는 Middleware 사용

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

 {
   type: 'TOGGLE_TODO',
   id: 10
 }

Event

  • Vector 형식의 데이터
  • Event Handler 가 처리 -> Effect 발생
  • 어플리케이션의 상태에 영향을 끼칠 수 없음

 [:add-todo "Learn Reframe"]

 [:toggle-todo 10]

Effect

  • Side Effect 를 위한 데이터 (Map)
  • Effect Handler가 실제 Effect 를 발생

 {: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...

  • 간결한 문법
  • macro
  • Clojure 라이브러리
  • Interactive Programming
  • clojure.spec
  • core.async

But...

  • 어려워...요..
  • JVM...
  • 디버깅...

Companies

감사합니다 :)

clojurescript-reagent-reframe

By DongWoo Kim

clojurescript-reagent-reframe

  • 1,577