更新单个子组件

时间:2016-08-31 08:20:01

标签: scala.js scalajs-react diode

我正在使用Diode 1.0.0和scalajs-react 0.11.1。

用例:

  • 包含子组件列表的父组件
  • Child的模型片段包含Pot用于异步获取的图像
  • 子组件在装入时抓取图像,PotEmpty,更新其模型片段

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

  1. 呈现父级。
  2. 呈现儿童1。
    1. 儿童1发送其GetImageAction。模型片段Pot已更新为Pending
    2. 模型已更新,导致父级重新呈现。
    3. 所有孩子都被重新渲染。
    4. 儿童2 ... n仍然有Empty Pot,因此他们会再次触发GetImageAction
  3. 现在呈现Child 2。
    1. 模型已更新,导致父级重新呈现。
  4. 这会导致一个巨大的GetImageAction调用和重新渲染树。

    有些问题:

    1. 为此目的使用该模型是错误的吗?使用组件状态会更好吗?
    2. 如果只需要更新孩子,如何避免重新呈现父母?我无法弄清楚是否/如何使用shouldComponentUpdate来达到此目的。
    3. 更新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))
      
      }
      

1 个答案:

答案 0 :(得分:1)

这很可能是因为在render方法中调用了connect。这将强制卸载/重新安装所有子组件。例如,当挂载父组件时,最好调用connect,然后在渲染中使用结果。

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