Clojurescript 공부노트

  • Clojurescript는 조금 까다롭다
    • Clojure를 알아야하고
    • 웹 개발 환경/도구를 알아야한다
      • node
      • javascript
        • interop
      • css
      • framework
        • react
    • 기타 등등

https://github.com/zalando/tech-radar https://opensource.zalando.com/tech-radar/

clojurescript

ref

cljsjs

library

  • emotion-js
  • malli
  • karma + @testing-library/react

reitit

"reitit"는 핀란드어로 "routes"을 뜻합니다.

interop

(set! js/a 3)

(def arr (array 1 2 3))
;; #js [1 2 3]

(def obj (js-obj "x" 1 "y" 2))
;; #js {:x 1, :y 2}

(aget arr 1)
(aset obj "z" 3
(js/document.body.lastChild.innerHTML.chatAt 7)

(js/Date "2016-05-19")
(js/Date. "2016-05-19")


(clj->js )
(js->clj )
;; :keywordize-keys true
;; ref: js->clj
;;      - https://cljs.github.io/api/cljs.core/js-GTclj
;; ref: https://clojuredocs.org/clojure.walk/keywordize-keys
(-> evt .-target .-value)
(.. evt -target -value)

goog.object

If you do need to access object properties dynamically, then all ClojureScript core provides are aget and aset, so what other option do we have? If you look a bit further you’ll find that ClojureScript comes bundled with the Google Closure Library, which is a bit like the missing “standard library” for JavaScript. The goog.object namespace has a few functions specifically for dealing with object properties.

There’s goog.object/get and goog.object/set, corresponding with aset and aget, but also goog.object/add, which is like a safer version of set, in that it will only set a property if it’s not present yet, or otherwise throw an exception.

ref

reference

shadow-cljs

:dev{{-before/after}}-load{{-async}}

  • shadow-cljs lifecycle
    • https://code.thheller.com/blog/shadow-cljs/2019/08/25/hot-reload-in-clojurescript.html
(defn ^:dev/before-load stop []
  (js/console.log "stop"))

(defn ^:dev/after-load start []
  (js/console.log "start"))

(defn ^:dev/before-load-async stop [done]
  (js/console.log "stop")
  (js/setTimeout
    (fn []
      (js/console.log "stop complete")
      (done)))

(defn ^:dev/after-load-async start [done]
  (js/console.log "start")
  (js/setTimeout
    (fn []
      (js/console.log "start complete")
      (done))))

init

  • deps.edn
  • shadow-cljs.edn
  • package.json
  • resources/public/index.html
;; file: deps.edn

{:paths ["src" "resources"]

 :deps
 {reagent/reagent {:mvn/version "1.2.0"}
  re-frame/re-frame {:mvn/version "1.4.2"}

  markdown-clj/markdown-clj {:mvn/version "1.11.7"}
  metosin/reitit {:mvn/version "0.6.0"}
  org.babashka/sci {:mvn/version "0.8.41"}
  thheller/shadow-cljs {:mvn/version "2.26.2"}
  }

 :aliases
 {:dev
  {:extra-deps {cider/cider-nrepl {:mvn/version "0.44.0"}}}}}
;; file: shadow-cljs.edn

{:deps {:aliases [:dev]}
 :dev-http {8080 "resources/public"}
 :builds
  {:app {:target :browser
         :output-dir "resources/public/js"
         :asset-path "/js"
         :module-hash-names false
         :modules {:app {:entries [app.core]
                         :init-fn app.core/Main}}
         :dev {:closure-defines {app.env/DEBUG true}}
         :build-hooks [(shadow.cljs.build-report/hook)]}}}
{
  "//": ["file: package.json"],
  "name": "helloworld",
  "version": "0.0.1",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^3.4.0"
  },
  "devDependencies": {
    "shadow-cljs": "2.26.2"
  },
  "scripts": {
    "start": "npx shadow-cljs --dependency cider/cider-nrepl:0.44.0 watch :app",
    "styles-watch": "npx tailwindcss -i src/styles/styles.css -o resources/public/css/styles.css --watch",
    "styles": "npx tailwindcss -i src/styles/styles.css -o resources/public/css/styles.css",
    "build": "npx shadow-cljs release :app && npm run styles",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
<!-- file: index.html -->

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Hello World</title>
  </head>
  <body>
    <div id="app">
      Loading....
    </div>
    <!-- JS -->
    <script type="text/javascript" src="./js/app.js"></script>
  </body>
</html>
;; optional
;; file: bb.edn

{:tasks
 {
  hello {:doc "helloworld"
         :requires ([babashka.process :as process])
         :task 
         #_(process/shell "clj -M -m app.core --interactive")
         (process/shell "clj -M -m app.core")}
 }}

example

npx shadow-cljs -d cider/cider-nrepl:0.44.0 watch :app

shadow-cljs - config: /Users/pyoung/test-cljs/shadow-cljs.edn
shadow-cljs - starting via "clojure"
shadow-cljs - HTTP server available at http://localhost:8080
shadow-cljs - server version: 2.26.2 running at http://localhost:9630
shadow-cljs - nREPL server started on port 49953
shadow-cljs - watching build :app

mirror

:repositories
{
  {"google" {:url "https://maven-central-asia.storage-download.googleapis.com/repos/central/data/"}}
}

compile option

html

<!-- file: resources/public/index.html -->

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Hello World</title>
  </head>
  <body>

    <div id="app">
      Loading....
    </div>

    <!-- JS -->
    <script type="text/javascript" src="./js/app.js"></script>
    </script>
  </body>
</html>
;; file: src/app/core.cljs

(defn ^:export main []
  (->> "app"
    (js/document.getElementById)
    (render-app-element)))

hiccup

Fast library for rendering HTML in Clojure

javascript

신텍스 하이라이트

highlight.js

(defn get-highlight-code [code]
  [:pre
    [:code {:dangerouslySetInnerHTML
             {:__html (.-value (js/hljs.highlight "clojure" code))}}]])
npm/cljsjs로 설치할 수 있긴한데, npm/cljsjs버전은 용량이 큰거같아 사이트에서 다운받고 작은거로 가자

## npm
"highlight.js": "^11.9.0",

## deps.edn
cljsjs/highlight {:mvn/version "11.7.0-0"}

codemirror

Closure

  • https://clojurescript.org/about/closure

    • clojurescript는 google의 closure 도구를 이용하여 clojurescript파일을 javascript파일로 만듬.
    • 이 도구는, closure compilerclosure library 등을 포함하고 있으며, clojurescript의 코어는 closure library기능을 이용하여 만들어져 있음.
    • 따라서 closure의 addDependency, provide, require를 이용하여, 컴파일시 어떤게 포함될지에 대한 의존성 관리를 할 수 있음.
    • closure compiler를 이용하여, 공백제거, 코멘트제거 및 이름축약 등, 최적화된 javascript파일을 얻을 수 있다.
  • ClojureScript 컴파일 옵션

주의점.

Ref.

sourcemap

cljs -> js로 변환이 되는데, js에서 어떤 변수/함수가 cljs에서 어떤 변수/함수인지 알 수 있게 해주는 것.

node

Node.js® is an open-source, cross-platform JavaScript runtime environment.

  • 홈페이지

  • Javascript Engine

    • .js -> V8 엔진 (컴파일) -> 기계 코드 (X86_64, MIPS, ARM 등) -> CPU에서 실행
    • 종류
      • V8 is the name of the JavaScript engine that powers Google Chrome
        • Lars Bak 창시
        • C++로 작성
        • Node.js에서 사용
      • Firefox has SpiderMonkey
      • Safari has JavaScriptCore (also called Nitro)

ECMAScript

  • ESModule
  • CommonJS

패키지 매니저

NPM

  • npm(Node Package Manager)
  • npx(Node Package Executer)
    • npx는 npm 버전 5.2.0부터 포함된 명령

package.json

기타

yarn에 실망한 개발자 Zoltan Kochan이 2017년 pnpm을 세상에 내놓습니다.
 pnpm은 yarn과 npm에 비해 빠릅니다. 그 이유는 패키지를 복사해서 사용하는 대신에 hard link를 사용하기 때문이죠. 또한 공간도 덜 차지합니다. 패키지의 버전 하나는 한 개의 복사본만 존재합니다. 그리고 그 파일의 링크를 node_modules 폴더에 추가합니다.

ref: https://medium.com/zigbang/패키지-매니저-그것이-궁금하다-5bacc65fb05d

Ref

NPM Package - 생활코딩

css

tailwindcss

npx tailwindcss init

tailwind.config.js

;; file: package.json
{
  "name": "app",
  "version": "0.0.1",
  "scripts": {
    "start": "npx shadow-cljs -d cider/cider-nrepl:0.44.0 watch :app",
    "styles-watch": "npx tailwindcss -i src/styles/styles.css -o resources/public/css/styles.css --watch",
    "styles": "npx tailwindcss -i src/styles/styles.css -o resources/public/css/styles.css",
    "build": "npx shadow-cljs release :app && npm run styles",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "shadow-cljs": "2.26.2"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^3.4.0"
  }
}

react

react개인적으로 정리. 자세한건 facebook공식 사이트에 있으니, 한눈에 필요한 것만 빠르게 윤곽을 잡을 수 있는게 필요!

1. 시작, 왜 React가 나왔는가?

facebook이 사이트를 개발하면서 데이터 변경과 ui변경을 동기화 시키는데 어려움을 격음.

1.1 어떻게 동기화를 시킬 것인가.

데이터를 변경할때마다 다시 새로 그리면 비용이 큼으로, 바뀌어야 하는 부분만 변경하고 싶음.브라우저는 DOM을 이용하여 표현하는데, DOM이 눈에 보이도록 렌더링될때에는 비용(시간)이 많이 듬.Virtual DOM이라고 눈에 보이지 않는 DOM을 만들어, 변경사항을 적용하고, 최소한에 변경사항을 눈에 보이도록 실제DOM에 반영하면 어떨까.

1.2 React는 어떠한 데이터 흐름을 가지며 어떻게 UI(dom)을 갱신하는가.

가상 Dom의 변경사항을 검사하여, 실제 Dom에 적용한다(변경에 필요한 최소한의 dom만 갱신)단방향 데이타 흐름.여기서 Dom갱신, 데이터 흐름에 주의해야 한다 .dom갱신부터 보자

컴포넌트컴포넌트 생성 constructor
 ---- 렌더전 초기화  UNSAFE_componentWillMount
렌더링 render
렌더후 초기화 componentDidMount

갱신.

Reconciliation 작업은 Virtual DOM과 DOM을 비교해 DOM을 갱신하는 작업이다

Dom Element

  • Dom속성 : Camel-case
  • 이벤트 헨들러 : Camel-case
  • style 속성 : camelCase
<div className='a'>
<label htmlFor='name'>

reconciliation

  • https://d2.naver.com/helloworld/9297403
  • https://www.robinwieruch.de/redux-mobx-confusion
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
// constructor HelloMessage(name) { } 이런식으로 해야 나중에 햇갈리지 않지 이런 인터페이스는 에러!!
class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}
ReactDOM.render(<HelloMessage name="Jane" />, mountNode);
class HelloMessage extends React.Component {
  render() {
    return React.createElement("div", null, "Hello ", this.props.name );
  }
}

ReactDOM.render(React.createElement(HelloMessage, { name: "Jane" }), mountNode);

Props 속성- immutable- passed in from parent- can not change it- can be defaulted & validated

state

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  render() {
    return (<div>
    <h1>Hello, world!</h1>
    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
내부 상태(state) 저장
this.state.comment = "hello";
// Wrongthis.setState({comment: "hello"});
//Correctthis.state =
// only constructorstate와 props 는 비동기적으로 업데이트 됨으로
// Wrongthis.setState({counter:this.state.counter + this.props.increment});
// Correctthis.setState((prevState,props)=>({counter:prevState.counter + props.increment}));
componentDidMount() { }
componentWillUnmount() { }
this.state = {posts: [],comments: []};
componentDidMount() {
    fetchPosts().then(response=>{this.setState({posts: response.posts    });  });
    fetchComments().then(response=>{this.setState({comments: response.comments    });  });
}

this.hello 처럼 변수를 만들어 무언가를 저장할 수 있다(보여지는 파트가 아닌것) State 상태- private- maintained by component- mutableReactDOM.render() 로 렌더된 결과를 바꾼다. state: this.setState() - 새로운 값으로 변경 - component renderstate - lifecycle - https://facebook.github.io/react/docs/state-and-lifecycle.htmllifting-state-up : https://facebook.github.io/react/docs/lifting-state-up.htmlprops.children

Welcome

Thank you for visiting our spacecraft!

{props.left} } right={ } /> 결과적으로 이걸 이해https://facebook.github.io/react/docs/thinking-in-react.htmlstate와 props를 적절히 활용. - 좀 더 깔끔한 방법은 없을까? - FilterableProductTable의 콜백을 계속 물고와서, SearchBar에서 input의 변경 콜백과 연결시켜주는 것

mixin예전에 있었던 기능으로 복잡함.

https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.htmlhttps://en.wikipedia.org/wiki/MixinIn object-oriented programming languages, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes

자바스크립트 코드로 mixin배우려니 빡쳐서 찾아보니http://blog.saltfactory.net/ruby/understanding-mixin-using-with-ruby.htmljs에서 mixin가지고 노는것은 미친짓.

fb에선 mixin이 암시적인 의존성을 가진다고 생각. 이름 충돌, 복잡성증가

HOC(Higher Order Component)

HOCs are not part of the React APIconst EnhancedComponent = higherOrderComponent(WrappedComponent);HOCs are common in third-party React libraries, such as Redux's connect and Relay's createContainer.python decorator랑 비슷하게 구현.

this.props.key : https://facebook.github.io/react/docs/lists-and-keys.html

list의 자식엘리먼트(li)같은걸 코드로 생성할때 const todoItems = todos.map((todo) =>

  • {todo.text}
  • );이런식으로 uniqid를 쥐어준다.(유니크하지 않으면 나중에 렌더링시 순서가 꼬일 수 있다)ㅡㅡ

    this.props.ref

    In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch. When to Use Refs# * Managing focus, text selection, or media playback. * Triggering imperative animations. * Integrating with third-party DOM libraries.

    class CustomTextInput extends React.Component {
      constructor(props) {
        super(props);this.focus=this.focus.bind(this);
      }
      focus() {
        this.textInput.focus();
      }
      render() {
        return(
          <div>
            <inputtype="text"ref={(input) => { this.textInput = input; }} />
            <inputtype="button"value="Focus the text input"onClick={this.focus}/>
          </div>
        );
      }
    }
    
    

    tool

    https://github.com/facebook/react-devtoolshttps://facebook.github.io/react/docs/perf.html

    virtual dom speed

    props internal component state

    parent = data => child

    • data flow one way from top to down
    • immutable

    PropTypes static typechecking

    SFC: Stateless Functional Component

    props 와 state

    • state는 독립적인 컴포넌트의 상태고, prop은 외부(부모) 컴포넌트에게 받은 속성이다
    • Redux 프로젝트에서 prop는 애플리케이션에서 전역으로 관리하는 상태라고 생각하면 된다.
    • 요약하면 컴포넌트 자체의 상태(색상, 애니메이션 등)는 state로 처리하고, 전체적으로 관리해야 하는 상태(서버와 동기화하는 데이터 등)는 부모 컴포넌트에서 prop으로 받아 처리한다. 물론 prop을 갱신하면 App 컴포넌트가 전반적으로 갱신되기 때문에 state를 갱신하는 것보다는 비교적 느리다. 효과적으로 state를 사용하면 좋지만, prop으로 처리해야 하는 작업을 state로 처리하면 오히려 코드 작성이 더 어려워진다. state와 prop의 차이에 관한 더 자세한 내용은 "props vs state"를 참고한다. React에서 구분하는 prop와 state의 차이에 관해서는 "Thinking in React"의 'Step 3: Identify The Minimal (but complete) Representation Of UI State'를 참고한다.

    Ref.


    redux

    • Redux 프로젝트에서 prop는 애플리케이션에서 전역으로 관리하는 상태라고 생각하면 된다.
    • https://github.com/reactjs/redux

    핫 코드 리로드를 위해

    flux store와는 달리, 로직과 상태를 분리

    타임 트래블

    • action이 store로 전달시, 기존 상태 복사 후 복사본을 수정.
    • 앱에서 사용하는 모든 state를 하나의 tree로 관리 - 하나의 트리라서 관리 문제 발생!

    flux

    • https://facebook.github.io/flux/
    • flux store
      1. 상태 변환을 위한 로직
      2. 현재 어플리케이션 상태

    Etc

    reagent

    https://github.com/reagent-project/reagent

    • cljsjs/react
    • cljsjs/react-dom
    (defn hello-component [name]
      [:p "Hello, " name "!"])
    
    (defn say-hello []
      [hello-component "world"])
    
    class Hello extends React.Component {
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    }
    
    class SayHello extends React.Component {
      render() {
        return <Hello name"world" />;
      }
    }
    
    • 특성 props state

      • parent 컴포넌트에 의해 값이 변경 될 수 있는가? 예 아니오
      • 컴포넌트 내부에서 변경 될 수 있는가? 아니오 예
      • ref: https://velopert.com/921
    • reagent.core/atom

      • deref
    • reagent.core/create-class

      • If you don’t use ES6 classes, you may use the create-react-class module instead. See Using React without ES6 for more information.
    (r/create-class
     {:component-did-mout ...
      :reagent-render ...})
    

    Element

       (r/create-element "div"
                         #js{:className "foo"}
                         "Hello "
                         (r/as-element [:strong "world"]))
    
    • r/adapt-react-class
    (def div-adapter (r/adapt-react-class "div"))
    
    (defn adapted []
      [div-adapter {:class "foo"}
       "Hello " [:strong "world"]])
    
    this (r/current-component)
    (r/props this) ;; this.props
    (r/children this) ;; this.props.children
    
    • r/next-tick

    • r/wrap

      • The new wrap function combines two parts – a simple value, and a callback for when that value should change – into one value, that happens to look as an atom.
      • The first argument to wrap should be the value that will be returned by @the-result.
      • The second argument should be a function, that will be passed the new value whenever the result is changed (with optional extra arguments to wrap prepended, as with partial).
    • reagent.core/cursor

    • Values and references

      • So what’s the difference between wraps and cursors? Why have both?
      • A wrap is just a value that happens to look like an atom. It doesn’t change unless you tell it to. It is a very lightweight combination of value and a callback to back-propagate changes to the value. It relies only on Reagent’s equality test in :should-component-update to avoid unnecessary re-rendering.
      • A cursor, on the other hand, will always be up-to-date with the value of the source atom. In other words, it acts a reference to part of the value of the source. Components that deref cursors are re-rendered automatically, in exactly the same way as if they deref a normal Reagent atom (unnecessary re-rendering is avoided by checking if the cursor's value has changed using identical?).

    :ref

    :<>

    re-frame

    (repo / README)

    https://day8.github.io/re-frame/

    • 간락한 개념.
    DB ->
      뷰 ->
        Reagent ->
          React ->
            렌더링.
            <- 이벤트 발생.
          <- 이벤트 큐잉
        <- 이벤트 처리 (인터셉터와 함께), 이펙트 발생.
      <- 이펙터 처리.
    DB
    
    • 키워드로 보는 개념.
    app-db ->
      [:hiccup] ->
        (r/render-component component dom-root) ->
        (rf/subsribe query-id) ->
          React ->
            Dom ->
              Render
              <- (rf/dispatch event)
              <- [event ...]
            <- (rf/reg-event-fx event-id [interceptor ...] (fn [cofx event]))
              <- rf.cofx/inject-db :before
              <- (rf/->interceptor :before)
              <- (fn [cofx event] ...)
              <- (rf/->interceptor :after)
              <- rf.fx/do-fx :after
              <- [effect ...]
        <- (rf/reg-fx effect-id (fn [event-value] ...)
      <- (rf/reg-sub query-id (fn [db] ...))
      <- (rf/reg-sub query-id (fn [db] ...))
    app-db
    

    용어설명

    kind reg-*

    register handler
    :event-db
    :event-fx
    :event-ctx
    :sub
    :sub-raw
    :fx
    :cofx
    
    drawing board
    reg-view
    reg-interceptor
    
    (rf/reg-event-db
       :event-id
       (fn [db event]
         (assoc db :some :thing)))
    
    (rf/reg
      {:kind  :event-db
       :id    :event-id
       :fn    (fn [db event]
                (assoc db :some :thing))})
    
    (rf/reg
      [{:kind :event-db ...}
       {:kind :event-db ...}
       {:kind :event-fx ...}
       {:kind :sub ...])
    

    Registrar

    Each entry stored in the registrar will become a map instead of just a handler.

    Map keys:
    
    :kind - somehow redundant
    :id
    :doc
    :line
    :ns
    :doc
    :fn the handler
    
    `re-com` is a library which supplies reusable Reagent widgets. And widgets, like a datepicker, are the simplest kind of components.
    `re-com` components are reusability because they take an input stream of data and they
    
    `def-view`: TODO
    

    DB : (app-db / db)

    {...}
    

    event

    [event-id & args]
    

    Event (event)

    Effect (effect / fx)

    • https://github.com/Day8/re-frame/blob/master/docs/API.md
    • 내장 이펙트
    [:db `db`]
    
    [:dispatch `event`]
    
    [:dispatch-n [(nil | `event`) ...]]
    
    [:dispatch-later [ (nil | {:ms `int` :dispatch `event`}) ...]]
    
    [:deregister-event-handler `event-id`]
    
    [:deregister-event-handler [`event-id` ...]]
    

    Coeffect (coeffect / cofx)

    • http://tomasp.net/coeffects/
    • the data your event handler requires from the world in order to do its computation (aka side-causes)
    • 내장 코-이펙트
    {
     :local-store `local-store`
     :db `db` ;; `current-app-state`
    }
    

    contextmap (context, ctx)

    {:coeffects {:event [:some-id :some-param]
                 :db    <original contents of app-db>}
    
     :effects   {:db    <new value for app-db>
                 :dispatch  [:an-event-id :param1]}
    
     :queue     <a collection of further interceptors>
     :stack     <a collection of interceptors already walked>}
    

    raise

    (rf/dispatch `event`)
    
    (rf/dispatch-sync `event`)
    

    queueing

    [event1 event2 event3 ...]
    

    event-routeter

    (rf/reg-event-fx
      `event-key`
      (fn [`cofx` `event`]
        `effect`))
    ;;=> {...} => [[`effect-key` `effect-value`] ...]
    
    (rf/reg-event-fx
      `event-key`
      [`interceptor` ...]
      (fn [`cofx` `event`]
        `effect`))
    ;;=> {...} => [[`effect-key` `effect-value`] ...]
    
    (reg-event-db
      `event-key`
      (fn [`db` `event`]
        `new-db`))
    ;;=> {:db `new-db`} => [[:db `new-db`]]
    
    (reg-event-db
      `event-key`
      (fn [{:key [db]} `event`]
        (assoc db :a 10)))
    
    (reg-event-fx
      `event-key`
      (fn [{:key [db]} `event`]
        {:db (assoc db :a 10)}))
    
    (reg-event-ctx
      `event-key`
      (fn [`context-map` `event`]
        `context-map`)
    

    effect-routeter

    [[`effect-key` `effect-value`] ...]
    

    apply effect

    • built-in : :db, :dispatch, ``
    (rf/reg-fx
      `effect-key`
      (fn [`effect-value`]
         ...))
    

    interceptor

    • built-in
      • debug
      • trim-v
    • built-in factory
      • enrich
      • path
      • after
      • inject-cofx
    • built-in more : std_interceptors.clj
    {:id      `cofx-id`
     :before  (fn [context] ...)
     :after   (fn [context] ...)}
    
    (rf/inject-cofx `cofx-id` arg)
    
    (rf/->interceptor
     :id     `cofx-id`
     :before handle-context
     :after  handle-context)
    
    (reg-cofx
       `cofx-id`
       (fn [`cofx` arg]
          `cofx`))
    

    reg-sub / subscribe

    {:name "Bruce"}
    
    (rf/reg-sub
      :get-name
      (fn [db _]
        (name db))) => "Bruce"
    
    (rf/reg-sub
      :greeting
      (fn [db _]
        (rf/subscribe :get-name))
      (fn [name _]
        (st "Hello, " name))) => "Hello, Bruce"
    
    @(rf/subscribe :greeting) => "Hello, Bruce"
    
      1. (reg-sub
           :test-sub
           (fn [db [_]] db))
    
      ;; The value in app-db is passed to the computation function as the 1st argument.
    
      2. (reg-sub
           :a-b-sub
           (fn [q-vec d-vec]
             [(subs/subscribe [:a-sub])
              (subs/subscribe [:b-sub])])
           (fn [[a b] [_]] {:a a :b b}))
    
      ;;  Two functions provided. The 2nd is computation function, as before. The 1st
      ;;  is returns what `input signals` should be provided to the computation. The
      ;;  `input signals` function is called with two arguments: the query vector
      ;;  and the dynamic vector. The return value can be singleton reaction or
      ;;  a sequence of reactions.
    
      3. (reg-sub
           :a-b-sub
           :<- [:a-sub]
           :<- [:b-sub]
           (fn [[a b] [_]] {:a a :b b}))```
    (let [value @(rf/subsribe `query-id`)])
    
    
    
    (rf/reg-sub
      `query-id`
      (fn [db event] ; computation-fn
        value))
    
    (rf/reg-sub
      `query-id`
      (fn [query-vec dynamic-vec]
        [(subscribe [:a-sub])
         (subscribe [:b-sub])])
      (fn [[a b] query-vec]
        ....))
    

    Ref

    Boot

    • 프로젝트 관리 프로그램이 있어야겠다. boot를 사용하자.

    • 새 프로젝트 생성은 boot-new를 이용하자.

        boot -d boot/new new -t app -n sample
      

    Fileset

    TODO(pyoung) boot fileset설명. :resource-paths : .jar에 포함되는 파일.

    Task

    (serve)

    fileset을 기본으로하는 http 서버

    	(serve :port 8080 :httpkit true)
    

    (cljs)

    figwheel