Zero to Om - 第4幕

到目前为止,该应用程序相当无聊它只显示数据But we want to actually use it! In this post we will take a look at how the app reacts (no pun intented) to user input源代码可以在上找到GitHub上

注意:我强烈建议你阅读上一篇文章首先,如果你还没有这样做。


管理国家是棘手的每个框架都有自己的机制来检测和处理状态变化这是Om的。

内心状态

在本节中,我们将了解如何管理组件内的状态。

组件状态

在上一篇文章中,我们已经了解到组件可以具有内部的瞬态状态在我们的例子中待办事项项目组件使用了符号:edit-text分配当前文本字段的值这个片段应该刷新你的记忆:

(defn todo-item [todo owner]
  (reify
    om/IInitState
    (init-state [_]
      {:edit-text (:title todo)})

    om/IRenderState
    (render-state [_ state]
      ...
        (dom/input
          #js {:className "edit"
               :value (om/get-state owner :edit-text)
               :onChange #(change % todo owner)
               ...}))))))

现在,让我们看看事件处理程序的onChange

(defn change [e todo owner]
  (om/set-state! owner :edit-text (-> e .-target .-value)))

处理程序改变了:edit-textstate到输入字段的值。- >是一个允许在没有额外嵌套的情况下编写语句的宏(即(.-检查(.-目标e)))但在一个“线程”上面的代码段显示了更新组件内部状态的两个选项之一:

  • OM /设置状态!:设置一个新值
  • OM /更新状态!:将更新函数应用于当前值

两者都会触发Om重新渲染。

申请国

我们之前已经了解到整个应用程序状态都保存在一个原子中根原子但我们只将一个子集传递给一个组件(例如去做待办事项项目)我们如何使子集与根状态保持同步?

游标! From Om's documentation:

游标将一个大的可变原子分成较小的子原子,这些子原子与根原子中保持的状态保持同步。

基本上你可以想象一个游标有点像指向一部分根状态的指针组件可以对其光标应用更改,应用程序状态将更改When data in the application state changes - for example from an HTTP request - Om will re-render all components that depend on the changed value因此绑定是双向的。

要从光标读取值,您需要取消引用它(例如@我的光标) - 除了渲染阶段(即内部)给予渲染状态)它可以像常规值一样对待要更改游标值,您有两个选择:

  • OM /更新!:设置一个新值
  • OM /事务处理!:将更新函数应用于当前值

让我们看看它在我们的应用程序中是如何工作的。

待办事项项目component包含一个复选框字段,用于切换项目的状态它的的onChange事件在这里处理:

(defn complete [todo]
  (om/transact! todo :completed #(not %)))

办理!函数接收(1)组件的光标去做到todo项目,(2)关键:已完成指定要更改的数据部分和(3)简单地否定当前值的函数文字。

另一个例子是主要的待办事项应用程序内零件它还包含一个复选框,但这一个切换所有项目的状态:

(defn toggle-all [e state]
  (let [checked (-> e .-target .-checked)]
    (om/transact! state :todos
      (fn [todos] (vec (map #(assoc % :completed checked) todos))))))

这里复选框的值绑定到检查办理!更新功能使用地图返回一系列todo项目完成键设置为检查不要忘记:我们正在处理不可变数据地图ASSOC不要修改数据但返回新值。

注意:VEC我们确保返回值始终是一个向量,因为Om的游标只能用于关联数据结构(即ClojureScript映射和索引顺序数据结构,如向量)。

要了解有关游标的更多信息,我建议您阅读Om的Cursor文档

外国

通常,我们想管理的州不会直接涉及该组件我们上面学到的两种机制还不够。

父组件

有时,更改应用程序状态是不可行的,因为子组件通常只有一个光标指向状态的一部分然后其中一位父母应负责任但他们如何与他人沟通?

通常,在JavaScript中,这是由回调或事件冒泡处理的但是在ClojureScript中我们有更好的东西:类似队列的通道该Clojure博客完美地描述它:

渠道的一个关键特征是它们是封锁的在最原始的形式中,无缓冲的通道充当会合点,任何读者都将等待作者,反之亦然。

基本上,这使我们可以轻松地同步两个或多个异步操作 - 同时编写顺序代码这一切都可以追溯到Tony Hoare的通信顺序进程(CSP)从20世纪70年代开始它最近通过在该实施中获得了普及去编程语言

频道由图书馆提供core.async,只有宏的力量才有可能首先,我们需要在文件的开头加载库:

(ns todomvc.app
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require        [cljs.core.async        :refer [put! <! chan]])

待办事项应用程序内组件使用将贴装启动应用程序后创建新频道的生命周期功能:

(defn todo-app [{:keys [todos] :as state} owner]
  (reify
    om/IWillMount
    (will-mount [_]
      (let [comm (chan)]
        (om/set-state! owner :comm comm)
        (go (while true
              (let [[type value] (<! comm)]
                (handle-event type state value))))))

(CHAN)返回一个新的无缓冲通道然后使用密钥将其添加到组件的状态:COMM最后,一个块被打开:在里面我们找到一个无限循环,从通道中读取一个值(<!comm)并将其解构为带有两个项目的向量,[类型值]

重要的是要注意,从通道读取是一个阻塞操作,等待数据永远通过通道发送But wouldn't the app completely freeze since JavaScript is single-threaded? Well, that's why we need to wrap all<!打电话:它是一个宏,允许在整个全局事件循环中包含小的,包含的事件循环内部的阻塞而全局的阻止而继续运行。

注意:COMMchannel现在传递给需要它的每个函数或组件为避免混淆:前一篇文章中的所有代码片段都省略了这一点。

回到我们的例子从通道读取的数据将传递给手柄事件

(defn handle-event [type state val]
  (case type
    :destroy (destroy-todo state val)
    :edit    (edit-todo state val)
    :save    (save-todos state)
    :cancel  (cancel-action state)
    nil))

该功能使用案件根据值选择要执行的表单类型它处理所有事件待办事项项目组件可能要发送让我们看一下当你点击待办事项上的红色'x'时触发的一个事件处理程序:

(defn destroy [todo comm]
  (put! comm [:destroy @todo]))

它用放!发送矢量[:destroy @todo]通过渠道请注意@预先取消引用光标 - 所以发送值,而不是光标然后通过我们在上面看到的无限循环读取它,传递给手柄事件最后作为参数结束摧毁,待办事项

(defn destroy-todo [state {:keys [id]}]
  (om/transact! state :todos
    (fn [todos] (vec (remove #(= (:id %) id) todos)))))

在这里,我们看到状态游标和todo的id用于更新应用程序状态The todos are replaced by a copy of all todos, excluding any item that has the passed-in id请记住,函数文字也可以写成(fn [todo](=(:id todo)id))

其他功能在手柄事件基本上所有的工作都是一样的,现在我们还没有看到过。

这只是划分了渠道可能性的表面要了解有关频道的更多信息,我建议阅读ClojureScript core.async todos并尝试这个源代码示例

DOM节点

There comes a time in every app when you need to work with an actual DOM node, eg reading and changing its value通过使用REF关键字,a参考名称可以分配给一个节点稍后,例如在事件处理程序中,可以通过此名称访问该节点。

在我们的应用程序中,我们需要将其用于“新”文本字段待办事项应用程序内零件这里有新增加的REF键:

(dom/input
  #js {:id "new-todo" 
       :ref "newField" 
       :onKeyDown #(enter-new-todo % state owner)}
       ...}
  ...

随着REF到位,事件处理程序进入新-待办事项可以访问节点:

(defn enter-new-todo [e state owner]
  (when (== (.-which e) ENTER_KEY)
    (let [new-field      (om/get-node owner "newField")
          new-field-text (string/trim (.-value new-field))]
      (when-not (string/blank? new-field-text)
        (let [new-todo {:id (guid)
                        :title new-field-text
                        :completed false}]
            (om/transact! state :todos #(conj % new-todo)))
        (set! (.-value new-field) "")))
    false))

当用户点击回车键时,它会创建一个新的待办事项:使用OM / GET节点与参数所有者(潜在的React组件)和新野(引用名称)可以检索文本字段(本机浏览器DOM节点)调用。-值在节点上读取其文本内容如果它不为空,则创建一个新项目并将其保存到应用程序状态OM /事务处理!最后,输入的值被重置。

另一个例子是“编辑”文本字段待办事项项目组件,现在已添加REF键:

(dom/input
  #js {:ref "editField" :className "edit" ...}
  ...

双击todo标签编辑调用事件处理程序:

(defn edit [e todo owner comm]
  (let [todo @todo
        node (om/get-node owner "editField")]
    (put! comm [:edit todo])
    (doto owner
      (om/set-state! :needs-focus true)
      (om/set-state! :edit-text (:title todo)))))

There's a lot going on! The去做符号绑定到解除引用的游标和节点到输入的DOM节点矢量[:编辑待办事项]被写入频道放!(并由。处理手柄事件我们之前看到的功能),将应用程序置于“编辑”模式最后,更新组件状态以反映待办事项的编辑。

注意: 多托需要所有者并使用它作为第一个参数调用每个后续表单的函数。

如果您想了解更多关于refs的信息,我会满足您的需求:更多关于Refs


This concludes today's act! We have seen how state is managed in Om我们没有调查一切示例应用程序的行,但您看到了最重要的部分随意浏览完整的源代码探索其余部分。

第5幕我们将学习如何正确发布应用程序同时,这里有一些深入了解Om的链接:


评论由Disqus