clojurescript 零零碎碎
控制台查看db信息
在浏览器中开启自定义日志后可以在console中查看re-frame管理的db里的内容,加格式化。
开启方式:https://github.com/binaryage/cljs-devtools/blob/master/docs/faq.md#why-some-custom-formatters-were-not-rendered
image.png
在console里使用如下命令查看
re_frame.db.app_db.state
效果:
re_frame.db.app_db.state
用kee-frame和re-frame修改db的值
目标给db的【demo】下增加一个key,值是一个ajax的返回结果
引入两个包
(require '[kee-frame.core :as k])
(require '[re-frame.core :as re-frame])
- 使用
re-frame/reg-event-db
修改db
(defn- concat-key [keys]
(concat [:demo] keys))
(defn- data->db [db keys value]
(assoc-in db (concat-key keys) value))
(k/reg-event-fx
::demo-detail-ajax
(fn [{:keys [db]} [id]]
{:http-xhrio {:uri ("http://localhost:3000/api/demo?id=" id)
:method :get
:timeout 10000
:on-failure [:common/ajax-get-failuer]
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::set-db-demo-detail]}}))
(re-frame/reg-event-db
::set-db-demo-detail
(fn [db [_ res]]
(data->db db [:demo-detail] (:data res))))
- 上面的操作用kee-frame实现
(k/reg-chain-named
::demo-detail-ajax
(fn [ctx [param]]
{:http-xhrio {:uri ("http://localhost:3000/api/demo?id=" (:id param)
:method :get
:timeout 10000
:on-failure [:common/ajax-get-failuer]
:response-format (ajax/json-response-format {:keywords? true})}})
:将返回结果数据存入db
(fn [{:keys [db]} [_ res]]
{:db (data->db db [:demo-detail] (:data res))}))
区别:
- re-frame里发起请求和存储db需要两个事件来实现,而kee-frame需要一个,第二个还可以起中文的名字,便于在控制台查看。
- re-frame/reg-event-db返回的是(assoc db :key value),而kee-frame/reg-chain返回的是{:db (assoc db :key value}
- kee-frame的这种返回类似于re-frame/reg-event-fx的另一种形式
(defn h ;; maybe choose a better name like `delete-item`
[coeffects event] ;; `coeffects` holds the current state of the world.
(let [item-id (second event) ;; extract id from event vector
db (:db coeffects)] ;; extract the current application state
{:db (dissoc-in db [:items item-id])})) ;; effect is "change app state to ..."
(re-frame.core/reg-event-fx ;; a part of the re-frame API
:delete-item ;; the kind of event
h)
这个处理github上是有的,这个h函数换可以简化一下
(defn h
[{:keys [db]} [_ item-id]] ;; <--- new: obtain db and id directly
{:db (dissoc-in db [:items item-id])}) ;; same as before
antd的组件中使用组件
一些组件需要的属性的参数类型是ReactNode, 比如Input的prefix属性
这需要 使用Reagent的as-element
函数
(as-element form) Turns a vector of Hiccup syntax into a React element. Returns form unchanged if it is not a vector.
[:> ant/Input
{:prefix (r/as-element [:> ant/Icon {:type "user"}])}]
修改页面atom元素赋值和修改
初学者包括小菜鸡我是不是会碰到个问题,从db里订阅的数据渲染页面时第一次没有,第二次以后就都正常了,或者你想修改一下这个值重新提交到接口上,发现值改不了。
可能你的代码是这样的
(defn page []
(let [ data @(re-frame/subscribe [:db-key])]
(fn []
[:div
[ant/input {:default-value (:name @data)
:type "text"
:on-change #(swap! item assoc item-str (-> % .-target .-value))}]])))
亦或是这样的:
(defn page []
(fn []
(let data @(re-frame/subscribe [:db-key])
[:div [ant/input {:default-value (:name @data)
:type "text"
:on-change #(swap! item assoc item-str (-> % .-target .-value))}]])))
恭喜,这两种方式完美踩雷,我们大神说因为我没有看reagent
的Guide一二三,好吧,确实没看。
-
现状和原因
-
第一种写法的现状:页面的input里第一次无法赋值
-
第一种写法的原因:页面加载一次,但是第一次订阅不到db里的数据,因为db里还没有数据
-
第二种写法的现状:页面input上有值了,但是不能修改,提示react的value不能被修改
-
第二种写法的原因:从db里订阅的值data订阅之后就与订阅没有关系了,说白了,也就不是atom了,所以不能修改。
-
方案:
(def db-value
(reaction @(re-frame/subscribe [:db-key])))
(def change-value (atom nil))
(defn page []
(fn []
(reset! change-value @(if (nil? @db-value)
(atom (deref (rf/subscribe [:db-key])))
db-value))
[:div
[ant/input {:default-value (:name @db-value)
:type "text"
:on-change #(swap! item assoc item-str (-> % .-target .-value))}]]))
当然,如你所知,写法有很多种,比如这个change-value你可以在fn里用let定义,并且比较推荐。
kee-frame/reg-controller 有没有好好看文档?
(defn reg-controller
"Put a controller config map into the global controller registry.
Parameters:
`id`: Must be unique in controllere registry. Will appear in logs.
`controller`: A map with the following keys:
- `:params`: A function that receives the route data and returns the part that should be sent to the `start` function. A nil
return means that the controller should not run for this route.
- `:start`: A function or an event vector. Called when `params` returns a non-nil value different from the previous
invocation. The function receives whatever non-nil value that was returned from `params`,
and returns a re-frame event vector. If the function does nothing but returning the vector, the surrounding function
can be omitted.
- `:stop`: Optional. A function or an event vector. Called when previous invocation of `params` returned non-nil and the
current invocation returned nil. If the function does nothing but returning the vector, the surrounding function
can be omitted."
[id controller]
(when-not (s/valid? ::spec/controller controller)
(e/expound ::spec/controller controller)
(throw (ex-info "Invalid controller" (s/explain-data ::spec/controller controller))))
(when (get @state/controllers id)
(console :warn "Overwriting controller with id " id))
(swap! state/controllers update id merge controller))
- kee-frame引入controller来优化路由路由管理,监听路由变化,所以在路由发生变化时程序里的所有controler都会竖起耳朵,当然最好是只有这个变化和自己有关,才去响应。这就要在params里加handler的判断。
-
:params
参数是一个函数包含路由信息,尤其是获取handler里的路由地址,当然包含参数。如果返回nil的话,后面start就不会执行,其他情况就会触发:start
。 -
:start
一个函数或者一组事件的集合,函数里可以处理参数等,如果是一堆event的话,可以在函数里发起dispatch或者直接返回[:event1 ::event2]
等等。
来个基础的例子:
(k/reg-controller :bind-user-page
{:params (fn [params]
(when (= :bind-user-page (get-in params [:data :name])) true))
:start (fn []
(prn "?????????===")
(re-frame/dispatch [:choose-hospital]))})
携带query参数的例子:
;;路由带参跳转不同页面
(k/reg-controller
:路由带参跳转不同页面
{:params (fn [route-data]
(when (-> route-data :data :name (= :weixin-redirect))
(:query-string route-data)))
:start (fn [_ params]
(re-frame/dispatch [:weixin-redirect
(reduce-kv (fn [m k v]
(assoc m (keyword k) v))
{}
(into {} (map #(clojure.string/split % #"=") (clojure.string/split params #"&"))))]))})
如果想要每次路由变化,都触发某个controller的start,只需要在params里返回identity
即可。
clojurescript 和 javascript交互
有时候我们的clojurescript需要和javascript交互。
比如用js打个log,比如用js获取window的location数据等,举个列子
(.log js/console "打个log")
(.-location js/window)
(.-search (.-location js/window))
cljs还提供了cljs和js互转的函数clj->js
和js->clj
看看例子:
(def js-object (clj->js :a 1 :b [1 2 3] :c #{"d" true :e nil}))
输出结果
{
"a": 1,
"b": [1, 2, 3],
"c": [null, "d", "e", true]
}
也可以简化用#js
(def js-object #js {:a 1 :b #js [1 2 3] :c #js ["d" true :e nil]})
# 输出
{
"c": {
"e": null,
"d": true
},
"b": [1, 2, 3 ],
"a": 1
}
js有时候需要转成cljs,用js->clj
实现,比如:
(defn get-form
"返回又From.create创建的 `form` 这个函数只能在form内部调用, 因为使用了reaget/current-component."
[]
(-> (r/current-component)
(r/props)
(js->clj :keywordize-keys true)
(:form)))
大神在4clojure博客上也有js操作DOM和引入highcharts的使用的例子。
更多交互的操作参考ClojureScript: JavaScript Interop
用figwheel编译工程,启动服务
三步启动 figwheel:Using the Figwheel REPL within nREPL
在emacs里启动cider-jack-in-cljs
选择figwheel
(use 'figwheel-sidecar.repl-api)
(start-figwheel!)
(cljs-repl)
在alk项目里的示例:
在emace里按照如下三不启动:
- cider-jack-in-cljs
- lein
- figwheel
启动后到user这个namespace
user> (use 'figwheel-sidecar.repl-api)
nil
user> (start-figwheel!)
Figwheel: Starting server at http://0.0.0.0:3449
Figwheel: Watching build - patient-app
Compiling build :patient-app to "target/cljsbuild/public/patientjs/app.js" from ["src/cljs/patient" "src/cljc" "env/dev/cljs/patient"]...
Successfully compiled build :patient-app to "target/cljsbuild/public/patientjs/app.js" in 5.142 seconds.
Figwheel: Starting CSS Watcher for paths ["resources/public/css"]
Figwheel: Starting nREPL server on port: 7002
nil
user> (cljs-repl)
Launching ClojureScript REPL for build: patient-app
Figwheel Controls:
(stop-autobuild) ;; stops Figwheel autobuilder
(start-autobuild id ...) ;; starts autobuilder focused on optional ids
(switch-to-build id ...) ;; switches autobuilder to different build
(reset-autobuild) ;; stops, cleans, and starts autobuilder
(reload-config) ;; reloads build config and resets autobuild
(build-once id ...) ;; builds source one time
(clean-builds id ..) ;; deletes compiled cljs target files
(print-config id ...) ;; prints out build configurations
(fig-status) ;; displays current state of system
(figwheel.client/set-autoload false) ;; will turn autoloading off
(figwheel.client/set-repl-pprint false) ;; will turn pretty printing off
Switch REPL build focus:
:cljs/quit ;; allows you to switch REPL to another build
Docs: (doc function-name-here)
Exit: :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
To quit, type: :cljs/quit
nil
cljs.user> (fig-status)
cljs.user> (fig-status)
Figwheel System Status
----------------------------------------------------
Watching builds: [patient-app]
Client Connections
patient-app: 1 connection
----------------------------------------------------
nil
cljs.user> (build-once doctor-app)
Figwheel: Building once - doctor-app
Compiling build :doctor-app to "target/cljsbuild/public/doctorjs/app.js" from ["src/cljs/doctor" "src/cljc" "env/dev/cljs/doctor"]...
Successfully compiled build :doctor-app to "target/cljsbuild/public/doctorjs/app.js" in 14.038 seconds.
nil
cljs.user> (fig-status)
Figwheel System Status
----------------------------------------------------
Watching builds: [patient-app]
Client Connections
patient-app: 0 connections
doctor-app: 1 connection
----------------------------------------------------
nil
cljs.user>
用浏览器链接后可以在repl里查看状态
(fig-status)
还有其他操作,控制台有提示
user> (cljs-repl)
Launching ClojureScript REPL for build: dev
Figwheel Controls:
(stop-autobuild) ;; stops Figwheel autobuilder
(start-autobuild [id ...]) ;; starts autobuilder focused on optional ids
(switch-to-build id ...) ;; switches autobuilder to different build
(reset-autobuild) ;; stops, cleans, and starts autobuilder
(reload-config) ;; reloads build config and resets autobuild
(build-once [id ...]) ;; builds source one time
(clean-builds [id ..]) ;; deletes compiled cljs target files
(print-config [id ...]) ;; prints out build configurations
(fig-status) ;; displays current state of system
Switch REPL build focus:
:cljs/quit ;; allows you to switch REPL to another build
Docs: (doc function-name-here)
Exit: Control+C or :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
To quit, type: :cljs/quit
cljs.user>