Reagent文档: React Features
- 片段(Fragments)
- 上下文(Context)
- 错误边界(Error boundaries)
- 函数组件(Function components)
- 钩子(Hooks)
- 门户(Portals)
- 服务端渲染(Hydrate)
- 组件类
这是Reagent文档的中文翻译,Reagent 是一个为 ClojureScript 构建的轻量级 React.js 接口。
即使 Reagent 没有直接提供函数来使用它们,大多数 React 功能也应该可以在 Reagent 中使用。
片段(Fragments)
JSX 示例:
function example() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
Reagent 示例:
(defn example []
[:<>
[child-a]
[child-b]
[child-c]])
Reagent 语法遵循 React 片段的简短语法。
上下文(Context)
(defonce my-context (react/createContext "default"))
(def Provider (.-Provider my-context))
(def Consumer (.-Consumer my-context))
(rdom/render
[:> Provider {:value "bar"}
[:> Consumer {}
(fn [v]
(r/as-element [:div "Context: " v]))]]
container)
上下文示例项目 更好地解释了如何将属性转换为 JS 对象,并展示了如何在上下文中使用 Cljs 值。
或者,您可以使用 静态 contextType 属性。
(defonce my-context (react/createContext "default"))
(def Provider (.-Provider my-context))
(defn show-context []
(r/create-class
{:context-type my-context
:reagent-render (fn []
[:p (.-context (reagent.core/current-component))])}))
;; 或者用元数据在 form-1 组件上:
;;
;; (def show-context
;; ^{:context-type my-context}
;; (fn []
;; [:p (.-context (reagent.core/current-component))]))
(rdom/render
[:> Provider {:value "bar"}
[show-context]]
container)
上下文值也可以使用 useContext 钩子获得:
(defn show-context []
(let [v (react/useContext my-context)]
[:p v]))
(rdom/render
[:> Provider {:value "bar"}
[:f> show-context]]
container)
测试包含使用旧版 React 生命周期上下文 API 的示例(context-wrapper 函数):测试
错误边界(Error boundaries)
您可以使用 getDerivedStateFromError(自 React 16.6.0 和 Reagent 0.9 起)和 ComponentDidCatch 生命周期方法与 create-class 一起使用:
(defn error-boundary [comp]
(let [error (r/atom nil)]
(r/create-class
{:component-did-catch (fn [this e info])
:get-derived-state-from-error (fn [e]
(reset! error e)
#js {})
:reagent-render (fn [comp]
(if @error
[:div
"Something went wrong."
[:button {:on-click #(reset! error nil)} "Try again"]]
comp))})))
或者,可以使用 React 状态而不是 RAtom 来跟踪错误状态,这在新的 getDerivedStateFromError 方法中更明显:
(defn error-boundary [comp]
(r/create-class
{:constructor (fn [this props]
(set! (.-state this) #js {:error nil}))
:component-did-catch (fn [this e info])
:get-derived-state-from-error (fn [error] #js {:error error})
:render (fn [this]
(r/as-element
(if-let [error (.. this -state -error)]
[:div
"Something went wrong."
[:button {:on-click #(.setState this #js {:error nil})} "Try again"]]
comp)))}))
根据 React 文档,getDerivedStateFromError 应该在错误后更新状态,也可以用来更新 RAtom,因为在 Reagent 中,即使对于静态方法,Ratom 也可在函数闭包中使用。ComponentDidCatch 可用于副作用,如记录错误。
函数组件(Function components)
JavaScript 函数是有效的 React 组件,但 Reagent 默认将在 Hiccup 向量中引用的 ClojureScript 函数转换为类组件。
然而,某些 React 功能,如钩子(Hooks),仅适用于函数式组件。有几种方法可以在 Reagent 中使用函数作为组件:
直接使用 r/create-element 调用 ClojureScript 函数不会在任何 Reagent 包装器中包装组件,并将创建函数式组件。在这种情况下,您需要在函数内使用 r/as-element 将 Hiccup 样式标记转换为元素,或者直接返回 React 元素。您也不能在这里使用 Ratoms,因为 Ratom 实现要求组件被 Reagent 包装。
:r> 快捷方式可用于创建类似于 r/create-element 的组件,子 Hiccup 形式会自动转换为 React 元素。
使用 adapt-react-class 或 :> 也会调用 create-element,但这还会自动将 ClojureScript 参数转换为 JS 对象,如果组件是 ClojureScript 函数,则通常不需要这样做。
新的方法是配置 Reagent Hiccup 编译器来创建函数式组件:阅读编译器文档
:f> 快捷方式可用于从 Reagent 组件(函数)创建函数式组件,其中 RAtoms 和钩子(Hooks)都有效。
钩子(Hooks)
;; 这是与 :f> 一起使用的,所以这里的 Hooks 和 RAtoms 都有效
(defn example []
(let [[count set-count] (react/useState 0)]
[:div
[:p "You clicked " count " times"]
[:button
{:on-click #(set-count inc)}
"Click"]])))
(defn root []
[:div
[:f> example]])
1.0 版本之前的解决方案
注意:此部分仍然指的是在类组件中使用钩子(Hooks)的解决方法,请阅读上一节以创建函数式组件。
钩子不能在类组件中使用,Reagent 实现会从每个函数(即 Reagent 组件)创建一个类组件。
然而,您可以在 Reagent 中使用使用钩子的 React 组件,或在 Reagent 中使用 hx 组件。此外,由于 React 函数组件只是一个恰好返回 React 元素的函数,而 r/as-element 正好做到这一点,因此很容易从 Reagent 创建 React 组件:
;; 这是 React 函数组件。这里不能使用 Ratoms!
(defn example []
(let [[count set-count] (react/useState 0)]
(r/as-element
[:div
[:p "You clicked " count " times"]
[:button
{:on-click #(set-count inc)}
"Click"]])))
;; Reagent 组件
(defn reagent-component []
[:div
;; 注意 :> 用作 React 组件的函数
[:> example]])
如果您需要将 RAtom 状态传递给这些组件,请在 Reagent 组件中解引用它们,并将值(如果需要,还有更新它们的函数)作为属性传递给 React 函数组件。
门户(Portals)
(defn reagent-component []
(let [el (.. js/document (getElementById "portal-el"))]
(react-dom/createPortal (r/as-element [:div "foo"]) el)))
服务端渲染(Hydrate)
(react-dom/hydrate (r/as-element [main-component]) container)
组件类
为了与 React 库互操作,你可能需要将组件类作为参数传递给其他组件。如果你有一个 Reagent 组件(一个函数),你可以使用 r/reactify-component,它会从该函数创建一个类。
如果父组件需要带有某些自定义方法或属性的类,你需要小心,可能应该使用 r/create-class。在这种情况下,你不想使用 r/reactify-component 和一个函数(即使该函数返回一个类),因为 r/reactify-component 会将该函数包装在另一个组件类中,父组件看不到正确的类。
;; 正确的方式
(def editor
(r/create-class
{:get-input-node (fn [this] ...)
:reagent-render (fn [] [:input ...])}))
[:> SomeComponent
{:editor-component editor}]
;; 通常不正确的方式
(defn editor [parameter]
(r/create-class
{:get-input-node (fn [this] ...)
:reagent-render (fn [] [:input ...])}))
[:> SomeComponent
{:editor-component (r/reactify-component editor)}]
在后一种情况中,:editor-component 是一个 Reagent 包装类组件,它没有 getInputNode 方法,并且使用 create-class 创建的组件进行渲染,该组件具有该方法。
如果你需要添加静态方法或属性,你需要自己修改 create-class 的返回值。该函数处理内置的静态方法(:childContextTypes :contextTypes :contextType :getDerivedStateFromProps :getDerivedStateFromError),但不处理其他方法。
(let [klass (r/create-class ...)]
(set! (.-static-property klass) "foobar")
(set! (.-static-method klass) (fn [param] ...))
klass)