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
- https://clojurebridgelondon.github.io/
- "Modern Frontend on ClojureScript and React in 2023" by Yuri Khmelevsky
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)bb.edn
;; 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
-
예제
- https://highlightjs.org/static/demo/
- 깔쌈해보이는것
- base16-framer
- atom-one-dark
- atom-one-light
- monokia-sublime
- base16-material-darker
-
highlightBlock
- Deprecated since version 11.0: Please use highlightElement() instead.
-
https://ask.clojure.org/index.php/9068/how-to-use-highlight-js-in-a-shadow-cljs-project
(defn get-highlight-code [code]
[:pre
[:code {:dangerouslySetInnerHTML
{:__html (.-value (js/hljs.highlight "clojure" code))}}]])
- dangerouslySetInnerHTML
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 compiler
와closure library
등을 포함하고 있으며, clojurescript의 코어는closure library
기능을 이용하여 만들어져 있음. - 따라서 closure의
addDependency
,provide
,require
를 이용하여, 컴파일시 어떤게 포함될지에 대한 의존성 관리를 할 수 있음. closure compiler
를 이용하여, 공백제거, 코멘트제거 및 이름축약 등, 최적화된 javascript파일을 얻을 수 있다.
주의점.
:optimizations :none
시goog/base.js
을 추가시켜줘야 한다.
Ref.
- https://adambard.com/blog/clojurescript-boot-fireplace/
- https://lambdaisland.com/episodes/javascript-libraries-clojurescript
- http://upgradingdave.com/blog/pages/006_boot_cljs.html
- https://github.com/magomimmo/modern-cljs
- https://github.com/binaryage/cljs-oops
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)
- V8 is the name of the JavaScript engine that powers Google Chrome
ECMAScript
- ESModule
- CommonJS
패키지 매니저
NPM
- npm(
N
odeP
ackageM
anager) - npx(
N
odeP
ackageE
xecuter)- 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
css
- css
- library
- bootstrap
- belma
- Utility-First
- material-ui
- storybook.js
- CSS-in-JS
- styled-components
- unocss
- clojurescript
- shadow-css
- CSS-in-CLJ(S)
- ornament
- Clojure Styled Components
- shadow-css
tailwindcss
-
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"
}
}
- jit
- ref
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!
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) =>
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
- 상태 변환을 위한 로직
- 현재 어플리케이션 상태
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
-
https://reagent-project.github.io/news/news050.html
-
reagent.core/create-element
-
reagent.core/as-element
- turns Reagent’s hiccup forms into React elements,
(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"]])
- https://reagent-project.github.io/news/news060-alpha.html
reagent.core/track
- rswap!
getInitialState
componentWillMount
reagent/dom-node
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
:<>
- react fragment
re-frame
- ClojureScript로 작성된 re-frame은 SPA만들때 쓰는 패턴입니다.
- re-frame은 내부적으로 reagent를 활용합니다.
- Reaktor Talks: Esko Lahti, Developing Web Applications With Reagent/re-frame
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
- https://github.com/Day8/re-frame/blob/master/docs/SubscriptionFlow.md
(ns re-frame.subs)
{: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
-
https://docs.google.com/drawings/d/1ptKAIPfb_gtwwSqYmt-JGTkwPVm_6LeWjjm-FcWznBs/edit
-
https://github.com/Day8/re-frame/blob/master/docs/FAQs/DoINeedReFrame.md
-
Router
-
Subscription & EventHandler
-
Logger
-
on-click.event handle
-
WIP - EventError Handling
-
Custom event-handler define
Boot
Fileset
TODO(pyoung)
boot fileset설명.
:resource-paths
: .jar
에 포함되는 파일.
Task
(serve)
fileset을 기본으로하는 http 서버
(serve :port 8080 :httpkit true)
(cljs)
- cljs.edn
- compiler-options
(cljs)
는 fileset(:resource-paths
,:source-paths
포함)에 있는*.cljs.edn
을 찾아*.js
를 생성한다.:ids
:ids
는 boot.core/by-path를 사용한다. boot.core/by-path는 path인식시 윈도우즈(\), xnix(/)를 다르게 인식한다.- ref: https://github.com/boot-clj/boot-cljs/pull/118
figwheel
- 예전에는 Figwheel을 많이 사용했지만, 현재는 shadow-cljs를 많이 사용한다.
- https://figwheel.org/