0到Om - 3

在第三篇文章中,我们将了解应用程序的初始化和呈现方式源代码可以在上找到GitHub

注意:我强烈推荐阅读上一篇文章如果你还没有这么做。

为了增加理解,我们将跳过两个文件,而不是从上到下遍历它们。


国家

大多数应用程序,至少是有趣的应用程序,都有某种状态在我们的例子中我们是存储两个数据点:

  • 的行动计划
  • 一个视图过滤(即“所有”、“积极”或“完成”)

关于Om的令人兴奋的事情是我们把所有的状态放在一个地方:

(def app-state(原子{:显示::todos[]}))

符号应用程序状态绑定到原子地图:视图过滤器(展示) is set to 'all' and the待办事项是一个空的集合。

As you see, a map can be defined by curly brackets:{ key1 val1 . .keyN valN }很高兴知道:元素数量不均匀会导致编译错误)可选地,您可以设置一个,对因为一个逗号之间被视为在Clojurescript的空白。

一个原子是一个所谓的的参考价值,也就是说,它主要指不可变的数据Why couldn't we just use a simple symbol and make it point to new data after a change? Well, an原子能够在更改其值时注册观察者交换!(或重置!)Om利用这个重新呈现组件状态的改变。

通过将所有应用程序状态推送到系统的边缘,其余代码仍然可用,因为现在只有一个可变对象把国家放在一个特定的地方降低复杂性,因此更容易思考。

Now we'll meet Om for the first time! Finally, huh? So first we need to load it:

(ns todomvc.app
  (:require [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]
            [...])

宏 - 因为它们是一种特殊的功能 - 无法加载标准需要但是通过require-macros然而,如果宏在同一个命名空间,在这种情况下,添加:include-macros true有同样的效果。

要启动应用程序,我们需要使用该功能OM /根然后告诉它

  • (a)呈现什么,
  • (b)我们的状态是什么,
  • (c)以及在哪里挂钩DOM。
(om/root 
  todo-app                                           ;; (a)
  app-state                                          ;; (b)
  {:target (.getElementById js/document "todoapp")}) ;; (c)

Om会从这里拿走它它启动一个渲染循环,当状态改变时,它将在一个批处理中更新组件(非常通过利用浏览器有效地实现requestAnimationFrame)。

现在燃烧的问题:是什么待办事项应用程序内吗?

组件'todo-app'

待办事项应用程序内是一个带有两个参数的函数:状态和底层的React组件所有者(Om为你创建)。

(defn todo-app [{:keys [todos] :as state} owner]
  ;; ...
)

你可能想知道为什么第一个参数看起来很奇怪这是ClojureScript工作中的一个强大功能:解构它基本上允许您绑定符号的部分数据结构在这种情况下[:keys [todos]]是一个快捷方式[todos:todos]——绑定的关键的价值待办事项从国家的象征待办事项:如是可选的,允许为整个结构定义别名,状态在这种情况下如果您想了解更多相关信息,我建议您阅读Clojure:解构

我们来看看函数的主体:

(defn todo-app [{:keys [todos] :as state} owner]
  (reify
    om/IRender
    (render [_]
      ;; create DOM elements ...
    )

你会注意到的第一件事就是使具体化评估一个新对象,实现了所有指定的协议协议是“一组命名的命名方法和他们的签名”。

在这里,om / IRender协议已实施它有一个渲染函数——明显呈现HTML组件只要它检测到组件状态发生变化,它就会被Om调用。注意:第一个参数是组件本身的实例,它被忽略_因为它不需要。

有几个协议在Om地图或多或少直接反应的生命周期步骤:

基本上有:

  • 创建阶段(“诞生”),可用于进行初始化工作,
  • 一个更新的阶段状态变化作出反应和呈现的组件,
  • 和清理阶段(“死亡”)。

唯一的协议你应该从来没有试图实现IShouldUpdate因为这是Om真正闪耀的地方:它在旧状态和新状态之间进行比较,以决定是否重新渲染组件而且由于ClojureScript中的数据结构是不可变的,它只需要一个简单的相等检查 - 与React的昂贵的逐件比较相比,这是非常快的这就是Om目前在基准测试中压缩纯React.js应用程序的主要原因。

让我们回到渲染从上面的功能:

(dom/div nil
  (header)
  (dom/input 
    #js {:id "new-todo" :ref "newField"
         :placeholder "What needs to be done?"
         :onKeyDown #(enter-new-todo % state owner)})
  (listing state)
  (footer state)))))

它在Om的帮助下创建了实际的DOM元素om.dom名称空间导入前这些DOM功能直接映射到DOM API的反应,这意味着dom / divReact.DOM.div和有相同的参数:

  • 道具一个JavaScript对象,修改组件的输出/行为
  • 所有其他参数(可选)儿童组件

在这种情况下,根元素有四个子元素:输入字段以及标题,列表和页脚 - 只有三个函数如你看到的,如果没有使用道具。

让我们仔细看看文本输入:

(dom/input                      
  #js {:id "new-todo"
       :placeholder "What needs to be done?"    
       :onKeyDown #(enter-new-todo % state owner)})

正如我们之前看到的,道具设置一个id占位符值将被呈现为HTML标签属性。onKeyDown注册一个事件处理程序,在用户按下一个键后触发 - 调用进入新-待办事项的事件,状态所有者

注意: #(...)是一个字面函数Basically it's a short version of(fn(evt)(enter-new-todo evt国家所有者))所以%函数的第一个参数,% 2第二个等等。

回去渲染从上面的函数页脚不是特别有趣的,让我们看一看清单代替:

(defn listing [{:keys [todos showing editing] :as state}]
  (dom/section #js {:id "main" :style (hidden (empty? todos))}
    (dom/input
      #js {:id "toggle-all" :type "checkbox"
           :onChange #(toggle-all % state)
           :checked (every? :completed todos)})
    (apply dom/ul #js {:id "todo-list"}
      (list-items todos showing editing))))

待办事项列表是裹着<节>与id主要它是隐藏在没有行动计划(通过使用隐藏的我们最后看到的效用函数)在我们找到一个复选框,选中只有在每一个todo项标记为“完成”改变它调用事件处理程序此外一个< ul >元素已创建并呈现它从中接收的子元素列表项函数。

注意:应用功能——与记者JavaScript函数——有必要转换组件的顺序列表项到个人的功能参数DOM / UL

(defn list-items [todos showing editing]
  (om/build-all item/todo-item todos
    {:key :id
     :fn (fn [todo]
           (cond-> todo
             (= (:id todo) editing)        (assoc :editing true)
             (not (visible? todo showing)) (assoc :hidden true)))}))

列表项使用Om的构建所有组件的函数来创建一个序列,在这种情况下待办事项项目第二个参数是一个序列的每个组件接收,在这里待办事项第三个参数是一个地图的选项:

  • :关键是一个关键字,用于在状态中查找将用作React键的值(id在我们的情况下)这使得渲染更高效因为在重新渲染时,React知道哪个DOM节点属于哪个序列元素。
  • :FN是一个在渲染之前应用于状态的函数所使用的功能使用时髦的外观气孔导度- >宏哪个线程表达方式去做通过给定的形式对首先,它添加了标志编辑任何待办事项相同的id的现在正在编辑(如果有的话)其次,它增加了旗帜隐藏的如果项目的完成状态不匹配展示视图过滤(使用效用函数看得见吗?)。

现在,让我们来看看待办事项项目组件。

组件“待办事宜”

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

    om/IRenderState
    (render-state [_ state]
      (let [class (cond-> ""
                    (:completed todo) (str "completed")
                    (:editing todo)   (str "editing"))]
        (dom/li 
          #js {:className class :style (hidden (:hidden todo))}
          (dom/div 
            #js {:className "view"}
            (dom/input
              #js {:className "toggle" :type "checkbox"
                   :checked (and (:completed todo) "checked")
                   :onChange #(complete todo)})
            (dom/label
              #js {:onDoubleClick #(edit % todo owner)}
              (:title todo))
            (dom/button
              #js {:className "destroy"
                   :onClick #(destroy todo)}))
          (dom/input
            #js {:className "edit"
                 :value (om/get-state owner :edit-text)
                 :onBlur #(submit % todo owner)
                 :onChange #(change % todo owner)
                 :onKeyDown #(key-down % todo owner)}))))))

这看起来很复杂,但实际上非常简单的渲染IRenderState有一个额外的参数比较IRender:国家它创建一个标签有两个孩子:一个div#视图和文本输入#编辑取决于的价值——这是由气孔导度- >——其中一个是显示。

在视图内部div一个复选框(将项目标记为已完成),一个标签(显示待办事项)和一个按钮(以销毁它)被添加声明的事件处理程序基本上是自解释的。

该组件还实现了IInitState协议:初始化状态返回包含组件初始化本地状态的映射这与全局或传入状态不同,因为它通常只是瞬态的,特定于组件的信息在文本输入中,您可以看到它正在被阅读OM / GET状态我们不直接使用todo的标题因为用户可能中止编辑过程中,然后我们就失去了原来的价值。编辑文本只是一个临时的标题。


That's it for now, kids! In第4幕我们将会看到用户如何与应用程序交互。


评论的Disqus