文章出處

前言

 本文是學習Thinking in React這一章后的記錄,并且用Reagent實現其中的示例。

概要

  1. 構造恰當的數據結構
  2. 從靜態非交互版本開始
  3. 追加交互代碼

一、構造恰當的數據結構

Since you’re often displaying a JSON data model to a user, you’ll find that if your model was built correctly, your UI (and therefore your component structure) will map nicely.

 VDom讓我們可以將Model到View的映射交出,而更專注于數據和數據結構本身,即是折騰數據和數據結構才是我們的主要工作。因此我們要設計出與View中組件結構對應的數據結構,然后將不符合該數據結構的數據做一系列轉換,然后將數據交給React就好了。
 居上所述那么可以知道,數據結構就依賴View的結構,那么如何設計View的結構呢?是采用Top-down還是Bottom-up的方式呢?對于小型應用我們直接采用Top-down即可,對于大型應用則采用Bottom-up更合適。(根據過往經驗將大規模的問題域拆分成多個小規模的問題域,然后對小問題域采用Top-down方式,若無法直接采用Top-down方式則繼續拆分,然后將多個小問題域的值域組合即可得到大問題域的值域)
 無論是Top-down還是Bottom-up方式,都要將View構建為樹結構(這很符合DOM結構嘛)。因此得到如下結構

FilterableProductTable
|_SearchBar
|_ProductTable
  |_ProductCategoryRow
  |_ProductRow

 而數據則從頂層View組件往下流動,各層提取各自數據進行渲染。

二、從靜態非交互版本開始

It’s best to decouple these processes because building a static version requires a lot of typing and no thinking, and adding interactivity requires a lot of thinking and not a lot of typing.

 從設計(他人或自己)那得到設計稿或HTML模板,我們就可以開始著手重構模板、添加交互效果和填充業務邏輯和服務端交互等功能了。且慢,我們先不著急動手,而是要先分清工作步驟,才能有條不紊地包質保量工作哦!

  1. 目標:得到符合React規范的View結構
  2. 目標:得到最低標準的可交互的React應用
  3. 目標:補充業務邏輯,細化交互
  4. 目標:連接遠程數據源,細化交互
(ns demo.core
  (:require [reagent.core :as re])

(def products [
  {:category "Sporting Goods", :price "$49.99", :stocked true, :name "Football"}
  {:category "Sporting Goods", :price "$9.99", :stocked true, :name "Baseball"}
  {:category "Sporting Goods", :price "$29.99", :stocked false, :name "Basketball"}
  {:category "Electronics", :price "$99.99", :stocked true, :name "iPod Touch"}
  {:category "Electronics", :price "$399.99", :stocked false, :name "iPhone 5"}
  {:category "Electronics", :price "$199.99", :stocked true, :name "Nexus 7"}
])


(declare <filterable-product-table>
         <search-bar>
         <product-table>
         <product-category-row>
         <product-row>)

(declare get-rows)

(defn <filterable-product-table>
  [products]
  [:div
    [<search-bar>]
    [<product-table> products]])

(defn <search-bar>
  []
  [:form
    [:input {:placeholder "Search..."}]
    [:input {:type "checkbox"}]
    "Only show products in stock."])

(defn <product-table>
  [products]
  [:table
    [:thead
      [:tr
        [:th "Name"]
        [:th "Price"]]]
    [:tbody
      (get-rows products)]])

(defn assemble-rows
  [products]
   (reduce
    (fn [{:keys [cate] :as rows-info} product]
      (let [curr-cate (:category product)
            curr-rows (if (not= curr-cate cate)
                        (list ^{:key curr-cate}[<product-category-row> curr-cate])
                        (list))
            rows (cons ^{:key (:name product)} [<product-row> product] curr-rows)]
        (-> rows-info
          (assoc :cate curr-cate) ;; 更新cate值
          (update
            :rows
            (fn [o rows]
              (concat rows o))
            rows)))) ;; 更新rows值
    {:cate nil :rows '()}
    products))

(defn get-rows
  [products]
  (-> (assemble-rows products)
    :rows
    reverse))

(defn <product-category-row>
  [cate]
  [:tr
    [:td {:colSpan 2} cate]])

(defn <product-row>
  [product]
  [:tr
    [:td (when (:stocked product) {:style {:color "red"}})
      (:name product)]
    [:td (:price product)]])


 這一步我們并沒有提供交互功能,因此只會用到prop傳遞數據,絕對不會用到state的。而交互的意思是,對View的操作會影響應用數據,從而刷新View。

三、追加交互代碼

 交互實質上就是觸發View狀態變化,那么就必須提供一種容器來暫存當前View的狀態,而這個在React就是state了。

(ns demo.core
  (:require [reagent.core :as re])

(def products [
  {:category "Sporting Goods", :price "$49.99", :stocked true, :name "Football"}
  {:category "Sporting Goods", :price "$9.99", :stocked true, :name "Baseball"}
  {:category "Sporting Goods", :price "$29.99", :stocked false, :name "Basketball"}
  {:category "Electronics", :price "$99.99", :stocked true, :name "iPod Touch"}
  {:category "Electronics", :price "$399.99", :stocked false, :name "iPhone 5"}
  {:category "Electronics", :price "$199.99", :stocked true, :name "Nexus 7"}
])


(declare <filterable-product-table>
         <search-bar>
         <product-table>
         <product-category-row>
         <product-row>)

(declare get-rows
         validate-product)


(defn <filterable-product-table>
  [products]
  (let [search-text (re/atom "")
        stocked? (re/atom false)
        on-search-text-change #(reset! search-text (.. % -target -value))
        on-stocked?-change #(reset! stocked? (.. % -target -checked))]
    (fn []
      [:div
        [<search-bar> on-search-text-change on-stocked?-change]
        [<product-table> (filter (partial validate-product @search-text @stocked?) products)]])))

(defn validate-product
  [search-text stocked? product]
  (and (if stocked? (:stocked product) true)
       (as-> search-text %
         (re-pattern (str "(?i)" %))
         (re-find % (:name product)))))

(defn <search-bar>
  [on-search-text-change on-stocked?-change]
  [:form
    [:input {:placeholder "Search..."
             :onChange on-search-text-change}]
    [:input {:type "checkbox"
             :onChange on-stocked?-change}]
    "Only show products in stock."])

(defn <product-table>
  [products]
  [:table
    [:thead
      [:tr
        [:th "Name"]
        [:th "Price"]]]
    [:tbody
      (get-rows products)]])

(defn assemble-rows
  [products]
   (reduce
    (fn [{:keys [cate] :as rows-info} product]
      (let [curr-cate (:category product)
            curr-rows (if (not= curr-cate cate)
                        (list ^{:key curr-cate}[<product-category-row> curr-cate])
                        (list))
            rows (cons ^{:key (:name product)} [<product-row> product] curr-rows)]
        (-> rows-info
          (assoc :cate curr-cate) ;; 更新cate值
          (update
            :rows
            (fn [o rows]
              (concat rows o))
            rows)))) ;; 更新rows值
    {:cate nil :rows '()}
    products))

(defn get-rows
  [products]
  (-> (assemble-rows products)
    :rows
    reverse))

(defn <product-category-row>
  [cate]
  [:tr
    [:td {:colSpan 2} cate]])

(defn <product-row>
  [product]
  [:tr
    [:td (when (:stocked product) {:style {:color "red"}})
      (:name product)]
    [:td (:price product)]])

注意:reagent中使用state時,需要定義一個返回函數的高階函數來實現。

(defn <statefull-cmp> [data]
  (let [local-state (re/atom nil)
        on-change #(reset! local-state (.. % -target -value))]
    (fn []
      [:div
        [:input {:onChange on-change}]
        [:span @local-state]])))

(re/render [<statefull-cmp> 1]
           (.querySelector js/document "#app"))

總結

 尊重原創,轉載請注明轉自:http://www.cnblogs.com/fsjohnhuang/p/7692956.html ^_^肥仔John

參考

https://reactjs.org/docs/thinking-in-react.html


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()