Skip to content

Latest commit

 

History

History
675 lines (618 loc) · 17.2 KB

clojurescript.org

File metadata and controls

675 lines (618 loc) · 17.2 KB

ClojureScript + Reagent + Reframe

ClojureScript + Reagent + Reframe

vs

Javascript + React + Redux

Clojure

  • 함수형 언어 : 불변(Immutable) 데이터 형 + 일급 함수
  • 발전된 Lisp : code-as-data 패러다임을 map과 vector까지 확장
  • 다형성(Polymorphism) 지원
  • 동적(Dynamic) 언어
  • JVM 환경에서 실행 (기생 언어)

Why Clojure?

by Official Rational

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

by Uncle Bob

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

Javascript

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

Influenced by

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

Most Popular Language

  • 브라우저 환경에서 사용할 수 있는 유일한 언어
    • 일명, 웹 환경에서의 어셈블리 언어
    • (하지만.. WebAssembly가 나온다면?)
  • Node.js : 백엔드 개발
  • Electron : PC 어플리케이션 개발
  • React Native : 네이티브 모바일 어플리케이션 개발

Most blamed Langauge

Compile to Javascript

Google Web Toolkit (2006)

Java를 Javascript로 변환. Google 개발

CoffeeScript (2009)

간결한 Javascript. Ruby, Python 등의 영향

Dart (2011)

개션된 Javascript. 기본 라이브러리 강화. Google 개발

ClojureScript (2011)

Clojure를 Javascript로 변환

TypeScript (2012)

Javascript의 Superset. 타입시스템 도입

Elm (2012)

정적 타입, 순수 함수형 언어. No runtime Exception

PureScript (2013)

정적 타입, 순수 함수형 언어. Haskell 영향

BuckleScript (2016)

Ocaml 을 Javascript 로 변환

Reason (2016)

Ocaml과 호환. BuckleScript를 이용. Facebook 개발

ClojureScript

  • Clojure 코드를 Javascript 로 변환시켜 주는 컴파일러
  • Clojure 로 작성됨 -> 컴파일을 위해 JVM 환경 필요 (크로스 컴파일러)
  • 구글 ClosureCompiler 사용 -> 생성된 Javascript 파일을 최적화

Clojure vs ClojureScript

  • 확장자 : clj (Clojure) / cljs (ClojureScript ) / cljs (All)
  • Numbers : 자바스크립트 Number 와 동일
  • 단일 문자형 없음, 모두 문자열
  • Refs, Transactions, Agents 지원 안됨
  • 정규식 지원 방식은 Javascript 와 동일
  • 매크로 작성시 런타임 환경과 별도로 컴파일 되어야 함
    • Computat ergo est: 클로저스트립트에서 매크로 작성시 주의점

Google Closure Compiler

  • ClojureCompiler ClosureCompiler (스펠링 주의)
  • 기존의 Javascript 코드를 최적화해서 새로운 Javascript 코드를 생성
    • Dead Code Elimination
    • Minify & Mangle
    • Code Spliting
  • JSDoc 을 활용해서 코드 최적화에 필요한 정보를 얻음

Google Clojure Library

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

JS vs CLJS (Similarities)

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

JS vs CLJS (differences)

Mutable vs Immutable

JavaScript

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

ClojureScript

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

;; updating the state
(swap! state #(update % :count inc))

Reference vs Value

Javascript

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

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

console.log(personA === personB);

ClojureScript

(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))

Modules vs Namespace

Javascirpt

  • CommonJS / ES6 Module
// my/math.js
export function sum(a, b) {
  return a + b;
}

// my/app.js
import {sum} from './math';
const result = sum(10 + 20);

ClojureScript

  • namespace
;; my/math.cljs
(ns my.math)
(defn sum [a b]
  (+ a b))

;; my/app.cljs
(ns my.app
  (:require [my.math :refer [sum]]))
(def result (sum 10 20))

OOP(Class) vs Protocol

Generator vs core.async

Tooling

JavascriptClojureScript
Package Managemantnpm / yarnLeiningen / Boot
ScaffoldingYeoman / …Leiningen / Boot
Compile (Transpile)Babel / TypeScriptLeiningen / Boot
BuildWebpack / RollupLeiningen / Boot
Interactive ProgrammingWebpack Dev ServerFigwheel / REPL
Static Code AnalizerESLintKibit

Interop

Global Variables / APIs

(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 Call / Property Access

;; 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

Javascript Modules

  • CLJSJS
  • npm-deps

Who’s using ClojureScript?

React

Declarative

  • Imperative -> Declarative
  • Virtual-DOM (Reconcilation)
  • DOM을 직접 다루지 않고, 가상 DOM의 구조를 반환

Component-Based

  • Component를 합성하여 UI 구성
  • Template(X) -> JSX (Javascript Data Structure)

Learn Once, Write Anywhere

  • React Native
  • React VR

React - Functional Approch

Component = (Pure) Function

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

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

  • JSX -> Javascript
<div>
  <TodoInput />
  <ul>
    {todos.map(todo => <TodoItem todo={todo} />)}
  </ul>
  <div>Completed: {completedTodo.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: ",
    completedTodo.length
  )
);

Immutable Props

  • Props는 Read-Only

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

Immutable State

  • this.state 직접 변경 불가
  • setState() 를 통한 명시적 변경

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

Immutability for Performance

  • shouldComponentUpdate()
  • Deep Equal 비교를 할 필요 없이 Shallow 비교
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }
  render() {
    return <div className={this.props.className}>foo</div>;
  }
}
class MyComponent extends React.PureComponent {
  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

React - Sample Code

Reagent : Simple React Wrapper

  • Minimalistic interface between ClojureScript and React.
  • JSX 대신 Hickup 형식의 문법 사용
  • State 관리를 위한 Ratom 제공

React vs Reagent

Simple Component

React

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

Reagent

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

Nested Component

React

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

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

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

Reagent

(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])])

State

React

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 = {
    visibleType: 'all',
    todos: [
      {id: 1, text: 'Learn ClojureScript'},
      {id: 2, text: 'Learn Reagent'},
      {id: 3, text: 'Learn Reframe'}
    ]
  }

  deleteTodo = (targetId) => {
    const {filter, todos} = this.state;
    this.setState({
      filter,
      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>
    );
  }
}

Reagent

(def visible-type (r/atom :all))
(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])])

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} />
    );
  }
}

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

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 {{name :name step :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)])

Redux

React의 State 관리

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

Redux - 3 Core Principles

1. Single source of truth

  • 단일 스토어 사용 : 어플리케이션의 모든 상태는 단일 트리 형태로 저장됨
  • 전체 상태를 관리하기 쉬움
  • 전체 상태를 서버 등의 다른 환경과 공유하기 쉬움
  • 개발도구에서 상태가 변경된 모든 이력을 남길 수 있고, Undo/Redo의 작업이 간편함

2. State is Read-Only

  • State를 직접 변경할 수 없음 (setter X)
  • 오직 Action 을 발생시켜서만 State를 변경함.
  • 하나의 Action에 하나의 변경만 있기 때문에, 복잡한 상호관계가 없음.

3. Changes are made with pure functions

  • reducer는 순수 함수여야 함
  • state 는 불변 객체
  • state 변경이 이루어질 때마다 새로운 state 트리 반환

Why Immutable?

Performance

  • 상태의 변경을 확인할 때, 레퍼런스만으로 Shallow 비교가 가능
  • React가 추구하는 방식과 잘 어울림

Time-Travel Debugging

  • 상태 변경의 모든 이력을 저장할 수 있음
  • Time-Travel 디버깅 가능

Easy Test

  • 순수 함수는 테스트가 쉽다

Redux - Sample Code

Immutability in Javascript

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, 'J');
const state2 = updateLastName(state, 'J');
console.log(state1 === state2); // false

Lodash / Ramda

import _ from 'lodash/fp'

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

const state1 = updateLastName(state, 'J');
const state2 = updateLastName(state, 'J');
console.log(state1 === state2); // false

ImmutableJS

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

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

// 비교
const state1 = updateLastName(staetIM, 'J');
const state2 = updateLastName(staetIM, 'J');
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 "J"))
(def state2 (update-last-name state "J"))
(js/console.log (= state1 state2)) ;; true

Reframe