Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
566 views
in Technique[技术] by (71.8m points)

scala - What is the suggested way to instantiate a js.Object for API wrappers

For the following javascript API wrapper:

@JSName("React")
object React extends js.Object {
  def createClass(init: ReactClassInit): ReactClassBuilder = ???
}

What is the suggested what to instantiate the following trait

trait ReactClassInit extends js.Object {
  val render: js.ThisFunction0[js.Dynamic, js.Any]
}

Currently I am doing the following:

val * = js.Dynamic.literal
val init = *(render = new js.ThisFunction0[js.Dynamic, js.Any] {
  override def apply(thisArg: js.Dynamic): js.Any = {
    React.DOM.div(null, "Hello ", thisArg.props.name)
  }
}).asInstanceOf[ReactClassInit]
val HelloMessage = React.createClass(init)

What I don't like about this approach is that there is no type-safety ensuring that ReactClassInit is instantiated properly.

(Here is all of the code to put things into a better context)

//Work in progress React wrapers
@JSName("React")
object React extends js.Object {
  def createClass(init: ReactClassInit): ReactClassBuilder = ???
  def renderComponent(cls: ReactClassInstance, mountNode: HTMLElement) = ???
  val DOM: ReactDOM = ???
}

trait ReactDOM extends js.Object {
  def div(huh: js.Any, something: js.String, propsOrWhat: js.Any) = ???
}

trait ReactClassInstance extends js.Object

trait ReactClassBuilder extends js.Object {
  def apply(args: js.Object): ReactClassInstance
}

trait ReactClassInit extends js.Object {
  val render: js.ThisFunction0[js.Dynamic, js.Any]
}



@JSExport
object ReactTodo {
  //Some helpers I use.
  val * = js.Dynamic.literal

  @JSExport
  def main() {
    helloJonT()
  }


  //Ideal Typed Example
  def helloJonT() {
    val init = *(render = new js.ThisFunction0[js.Dynamic, js.Any] {
      override def apply(thisArg: js.Dynamic): js.Any = {
        React.DOM.div(null, "Hello ", thisArg.props.name)
      }
    }).asInstanceOf[ReactClassInit]
    val HelloMessage = React.createClass(init)
    React.renderComponent(HelloMessage(*(name = "Jon").asInstanceOf[js.Object]), document.getElementById("content"))
  }

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Currently, the recommended approach is very close to what you are doing, except that the use of js.Dynamic.literal should be encapsulated in the companion object of your trait (ReactClassInit in your case). You can provide a type-safe apply method in that companion object like this:

trait ReactClassInit extends js.Object {
  val render: js.ThisFunction0[js.Dynamic, js.Any]
}
object ReactClassInit {
  def apply(render: js.ThisFunction0[js.Dynamic, js.Any]): ReactClassInit = {
    js.Dynamic.literal(
      render = render
    ).asInstanceOf[ReactClassInit]
  }
}

which you can then use with:

val init = ReactClassInit(render = { (thisArg: js.Dynamic) =>
  React.DOM.div(null, "Hello ", thisArg.props.name)
})

Of course this is still globally unsafe. But there is only one point in your code where you use a cast, and more importantly it is close to the definition of the type. So it is more likely that if you update one, you will update the other.

I know this is not a completely satisfactory solution. But so far in our design of Scala.js we have not yet found a really good solution to this problem.

Two side notes:

1) I strongly advise against using new js.ThisFunctionN { def apply }! It is an accident that this notation works at all. Simply use a lambda like I showed in my example. If the target type is typed as a js.ThisFunctionN already (like in my code), it'll work just like that. If, as was the case in your code, the target type is js.Any (or Any), you'll need to ascribe your lambda with : js.ThisFunction (without digit) to make sure that the compiler treats it as a this-function and not a (non-this-)function, but that's all. To make it clearer, here is how it would have looked with your code:

val init = *(render = { (thisArg: js.Dynamic) =>
  React.DOM.div(null, "Hello ", thisArg.props.name)
}: js.ThisFunction).asInstanceOf[ReactClassInit]

2) You probably want your function to be typed as returning Any (or _) instead of js.Any:

trait ReactClassInit extends js.Object {
  val render: js.ThisFunction0[js.Dynamic, Any]
}

Typically when you use js.Any in the result type of js.(This)Function, you mean any value, not any JS value. And Scala's type inference works best with Any in that location.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...