0

我正在使用带有 scalajs-react 0.11.1 的二极管 1.0.0。

用例:

  • 带有子组件列表的父组件
  • 子模型片段包含Pot用于异步获取的图像
  • 子组件在挂载时获取图像并Pot更新Empty其模型片段

使用幼稚的方法,这会导致以下情况(事件的顺序可能不同):

  1. 父级被渲染。
  2. 孩子 1 被渲染。
    1. Child 1 发送它的GetImageAction. 模型片段Pot更新为Pending.
    2. 模型已更新,导致父级重新渲染。
    3. 所有的孩子都被重新渲染。
    4. 孩子 2 ... n 仍然有一个Empty Pot,所以他们GetImageAction再次触发他们的 s。
  3. 现在孩子 2 被渲染。
    1. 模型已更新,导致父级重新渲染。
    2. 等等。

这会导致GetImageAction调用和重新渲染的巨大树。

一些问题:

  1. 将模型用于此目的是错误的吗?使用组件状态会更好吗?
  2. 当只需要更新孩子时,如何避免重新渲染父母?我无法弄清楚我是否/如何可以shouldComponentUpdate用于此目的。

更新 1

我现在为每个子组件添加一个 React 键。这摆脱了关于唯一键的 React 警告,但不幸的是并没有解决上述问题。即使他们的shouldComponentUpdate方法返回,孩子也会被重新渲染false

来自ParentComponent.render()

  items.zipWithIndex.map { case (_, i) =>
    proxy.connector.connect(
      proxy.modelReader.zoom(_.get(i)), s"child_$i": js.Any).
      apply(childComponent(props.router, _))
  }

更新 2

我尝试在父组件中实现侦听器功能,但不幸的是,子组件仍然被卸载并重新安装。这是我的父组件的代码:

package kidstravel.client.components

import diode.data.{Empty, Pot}
import diode.react.ModelProxy
import diode.react.ReactPot._
import diode.{Action, ModelR}
import japgolly.scalajs.react.extra.router.RouterCtl
import japgolly.scalajs.react.vdom.prefix_<^._
import japgolly.scalajs.react.{BackendScope, ReactComponentB, _}
import kidstravel.client.KidsTravelMain.Loc
import kidstravel.client.services.{KidsTravelCircuit, RootModel}

case class TileProps[T](router: RouterCtl[Loc], proxy: ModelProxy[T])

/**
  * Render sequence of models as tiles.
  */
trait Tiles {

  // The type of the model objects.
  type T <: AnyRef

  /**
    * Override to provide the action to obtain the model objects.
    * @return An action.
    */
  def getAction: Action

  /**
    * Returns the tile component class.
    * @return
    */
  def tileComponent: ReactComponentC.ReqProps[TileProps[T], _, _, _ <: TopNode]

  case class Props(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]])

  class Backend($: BackendScope[Props, Pot[Seq[T]]]) {

    private var unsubscribe = Option.empty[() => Unit]

    def willMount(props: Props) = {
      val modelReader = props.proxy.modelReader.asInstanceOf[ModelR[RootModel, Pot[Seq[T]]]]
      Callback {
        unsubscribe = Some(KidsTravelCircuit.subscribe(modelReader)(changeHandler(modelReader)))
      } >> $.setState(modelReader())
    }

    def willUnmount = Callback {
      unsubscribe.foreach(f => f())
      unsubscribe = None
    }

    private def changeHandler(modelReader: ModelR[RootModel, Pot[Seq[T]]])(
        cursor: ModelR[RootModel, Pot[Seq[T]]]): Unit = {
      // modify state if we are mounted and state has actually changed
      if ($.isMounted() && modelReader =!= $.accessDirect.state) {
        $.accessDirect.setState(modelReader())
      }
    }

    def didMount = $.props >>= (p => p.proxy.value match {
      case Empty => p.proxy.dispatch(getAction)
      case _ => Callback.empty
    })

    def render(props: Props) = {
      println("Rendering tiles")
      val proxy = props.proxy
      <.div(
        ^.`class` := "row",
        proxy().renderFailed(ex => "Error loading"),
        proxy().renderPending(_ > 100, _ => <.p("Loading …")),
        proxy().render(items =>
          items.zipWithIndex.map { case (_, i) =>
            //proxy.connector.connect(proxy.modelReader.zoom(_.get(i)), s"tile_$i": js.Any).apply(tileComponent(props.router, _))
            //proxy.connector.connect(proxy.modelReader.zoom(_.get(i))).apply(tileComponent(props.router, _))
            //proxy.wrap(_.get(i))(tileComponent(_))
            tileComponent.withKey(s"tile_$i")(TileProps(props.router, proxy.zoom(_.get(i))))
          }
        )
      )
    }
  }

  private val component = ReactComponentB[Props]("Tiles").
    initialState(Empty: Pot[Seq[T]]).
    renderBackend[Backend].
    componentWillMount(scope => scope.backend.willMount(scope.props)).
    componentDidMount(_.backend.didMount).
    build

  def apply(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]]) = component(Props(router, proxy))

}
4

1 回答 1

1

这很可能是由于connect在渲染方法内部调用。这将强制卸载/重新安装所有子组件。最好在connect挂载父组件时调用,然后在渲染中使用结果。

或者,您可以完全跳过connect并直接在父组件内实现更改侦听器。当项目集合更改时,更新强制重新渲染更新所有已更改组件的状态。使用shouldComponentUpdate允许 React 确定哪些组件确实发生了变化。

于 2016-09-01T07:54:40.773 回答