Coffeescript 反应:如何向父组件发送信息?

Coffeescript 反应:如何向父组件发送信息?,coffeescript,functional-programming,reactjs,Coffeescript,Functional Programming,Reactjs,我创建了一个非常简单的导航视图控制器,它模仿了iOS UINavigationController的概念。它基本上只是维护路线的历史记录,并设置推送到另一条路线或弹出的动画 (我希望你不介意咖啡脚本;) 这工作得很好,正如预期的那样。下面是一个使用NavVC递归推送和弹出页面的简单示例 Page = createView displayName: 'Page' mixins: [React.addons.PureRenderMixin] propTypes: title: R

我创建了一个非常简单的导航视图控制器,它模仿了iOS UINavigationController的概念。它基本上只是维护路线的历史记录,并设置推送到另一条路线或弹出的动画

(我希望你不介意咖啡脚本;)

这工作得很好,正如预期的那样。下面是一个使用NavVC递归推送和弹出页面的简单示例

Page = createView
  displayName: 'Page'
  mixins: [React.addons.PureRenderMixin]
  propTypes:
    title: React.PropTypes.string.isRequired
    push: React.PropTypes.func.isRequired
    pop: React.PropTypes.func
  push: ->
    @props.push(Math.random().toString(36).substr(2,100))
  render: ->
    div
      className: 'page'
      div
        className: 'title'
        onClick: @push
        @props.title
      cond @props.pop,
        => div
          className: 'back'
          onClick: @props.pop
          "< BACK"

App = createView
  renderScene: (route, push, pop, popFront) ->
    Page
      key: route
      title: route
      pop: pop
      push: push
  render: ->
    NavVC
      rootScene: 'Hello NavVC'
      renderScene: @renderScene


React.render App(), document.body
假设我们有一些按钮,它们是NavVC的兄弟姐妹/父母,它们为我们进行按下和弹出操作

App = createView
  getInitialState: -> {}
  renderScene: (route, push, pop, popFront) ->
    @setState({push, pop})
    Page
      key: route
      title: route
  push: ->
    @state.push?(Math.random().toString(36).substr(2,100))
  render: ->
    div
      className: 'app'
      NavVC
        rootScene: 'Hello NavVC'
        renderScene: @renderScene
      cond @state.pop,
        => div
          className: 'left'
          onClick: @state.pop
          '<'
      div
        className: 'right'
        onClick: @push
        '>'

React.render App(), document.body
您可以在以下内容中查看此示例的工作版本

但这种解决方案在某些非常根本的方面似乎是错误的。它不是声明性的,每次涉及两个渲染,并且打破了单向数据流模式。我非常希望在这两个用例中使用相同的组件——子推和弹出,或者父/兄弟推和弹出,或者可能是子推和父弹出的混合。我似乎无法想出一个函数模式来实现这一点,而不打破一些优秀编程的基本规则


编辑1:

所以我想我已经找到了一个正确的方向:EventEmitters。按钮发出事件,NavVC监听这些事件。我们可以通过App组件连接这些事件,NavVC将在挂载时侦听这些事件,并在卸载时注销这些侦听器。这保持了单向数据流


不过,仍然存在一个问题。NavVC需要能够告诉后退按钮是否可弹出,以便后退按钮知道是否可以显示。。。我还没弄明白。好的。这是一个巨大的痛苦,但我找到了一个令人满意的解决方案——尽管如此,我还是希望得到一些意见

我创建了一个代理组件的想法,它适合于可以由父级或兄弟级控制的空间

App = createView
  componentWillMount: ->
    @PushProxy = createProxy(@renderPush)
    @PopProxy = createProxy(@renderPop)
  renderPush: (push) ->
    div
      className: 'right'
      onClick: -> push(Math.random().toString(36).substr(2,100))
      '>'
  renderPop: (pop) ->
    cond pop,
      => div
        className: 'left'
        onClick: pop
        '<'
  renderScene: (route, push, pop, popFront) ->
    Page
      key: route
      title: route
  render: ->
    console.log "render app"
    div
      className: 'app'
      @PushProxy()
      @PopProxy()
      NavVC
        rootScene: 'Hello NavVC'
        renderScene: @renderScene
        pushProxy: @PushProxy
        popProxy: @PopProxy
这个方法仍然需要setTimeout为零,但它经过优化,不需要渲染过多的内容,所以我认为我可以接受它

rememberLast = (f) ->
  lastArg = (->)
  lastResult = false
  (arg) ->
    if lastArg isnt arg
      lastResult = f(arg)
    lastArg = arg
    return lastResult

defer = (f) ->
  (arg) ->
      window.setTimeout ->
        f(arg)
      , 0

createProxy = (renderComponent) ->  
  value = undefined
  listeners = {}
  listen = (f) ->
    id = Math.random().toString(36).substr(2,100)
    listeners[id] = f
    if value isnt undefined then f(value)
    {stop: -> delete listeners[id]}
  dispatch = (x) ->
    value = x
    for id, f of listeners
      f(x)

  Proxy = createView
    displayName: 'Proxy'
    getInitialState: ->
      value: undefined
    componentWillMount: ->
      @listener = listen (value) =>
        @setState({value})
    componentWillUnMount: ->
      @listener.stop()
    render: ->
      if @state.value isnt undefined
        console.log "proxy render"
        renderComponent(@state.value)
      else
        false

  Proxy.dispatch = rememberLast defer dispatch
  return Proxy
查看演示它的实际操作,并查看日志语句,以查看它的渲染是否达到最佳效果

我很想听听别人的反应,如果这是正确的想法。也许有一种更具原则性的方法可以做到这一点

    window.setTimeout => 
      @setState({push, pop})
    , 0
App = createView
  componentWillMount: ->
    @PushProxy = createProxy(@renderPush)
    @PopProxy = createProxy(@renderPop)
  renderPush: (push) ->
    div
      className: 'right'
      onClick: -> push(Math.random().toString(36).substr(2,100))
      '>'
  renderPop: (pop) ->
    cond pop,
      => div
        className: 'left'
        onClick: pop
        '<'
  renderScene: (route, push, pop, popFront) ->
    Page
      key: route
      title: route
  render: ->
    console.log "render app"
    div
      className: 'app'
      @PushProxy()
      @PopProxy()
      NavVC
        rootScene: 'Hello NavVC'
        renderScene: @renderScene
        pushProxy: @PushProxy
        popProxy: @PopProxy
NavVC = createView
  # clip
  render: ->
    console.log "render navvc"
    route = @state.stack[@state.stack.length - 1]
    pop = @pop if @state.stack.length > 1
    popFront = @popFront if @state.stack.length > 1
    @props.pushProxy.dispatch(@push)
    @props.popProxy.dispatch(pop)
    div
      className: 'navvc'
      Transition
        transitionName: @state.transition
        @props.renderScene(route, @push, pop, popFront)
rememberLast = (f) ->
  lastArg = (->)
  lastResult = false
  (arg) ->
    if lastArg isnt arg
      lastResult = f(arg)
    lastArg = arg
    return lastResult

defer = (f) ->
  (arg) ->
      window.setTimeout ->
        f(arg)
      , 0

createProxy = (renderComponent) ->  
  value = undefined
  listeners = {}
  listen = (f) ->
    id = Math.random().toString(36).substr(2,100)
    listeners[id] = f
    if value isnt undefined then f(value)
    {stop: -> delete listeners[id]}
  dispatch = (x) ->
    value = x
    for id, f of listeners
      f(x)

  Proxy = createView
    displayName: 'Proxy'
    getInitialState: ->
      value: undefined
    componentWillMount: ->
      @listener = listen (value) =>
        @setState({value})
    componentWillUnMount: ->
      @listener.stop()
    render: ->
      if @state.value isnt undefined
        console.log "proxy render"
        renderComponent(@state.value)
      else
        false

  Proxy.dispatch = rememberLast defer dispatch
  return Proxy