【发布时间】:2015-12-12 16:07:44
【问题描述】:
我创建了一个非常简单的导航视图控制器,以 iOS UINavigationController 的概念为蓝本。它基本上只是维护路线的历史,并动画推送到另一条路线或弹出。
(我希望你不介意咖啡脚本;)
createView = (spec) -> React.createFactory(React.createClass(spec))
{div, span, input, img, button} = React.DOM
cond = (condition, result, otherwise) -> if condition then result?() else otherwise?()
Transition = React.createFactory(React.addons.CSSTransitionGroup)
NavVC = createView
displayName: 'NavVC'
mixins: [React.addons.PureRenderMixin]
propTypes:
rootScene: React.PropTypes.string.isRequired
renderScene: React.PropTypes.func.isRequired
getInitialState: ->
transition: 'navvc-appear'
stack: [@props.rootScene]
push: (route) ->
@setState
stack: React.addons.update(@state.stack, {$push: [route]})
transition: 'navvc-push'
pop: ->
if @state.stack.length is 1
console.warn("You shouldn't pop off the root view of a NavVC!")
else
@setState
stack: React.addons.update(@state.stack, {
$splice:[[@state.stack.length - 1, 1]]
})
transition: 'navvc-pop'
popFront: ->
@setState
stack: [@state.stack[0]]
transition: 'navvc-pop'
render: ->
route = @state.stack[@state.stack.length - 1]
pop = @pop if @state.stack.length > 1
popFront = @popFront if @state.stack.length > 1
div
className: 'navvc'
Transition
transitionName: @state.transition
@props.renderScene(route, @push, pop, popFront)
这效果很好,符合预期。下面是一个使用 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
它在JSFiddle 中发挥作用。
现在这是我的问题。当子组件进行推送和弹出时,这非常有用。但是如果父组件或兄弟组件正在推送或弹出呢?
假设页面本身完全不知道 NavVC:
Page = createView
displayName: 'Page'
mixins: [React.addons.PureRenderMixin]
propTypes:
title: React.PropTypes.string.isRequired
render: ->
div
className: 'page'
div
className: 'title'
@props.title
假设我们有一些按钮是 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
这会给我们一个错误。
Invariant Violation:setState(...):在现有状态转换期间无法更新(例如在
render内)。渲染方法应该是 props 和 state 的纯函数。
当 App 渲染时,NavVC 将调用 renderScene,它会调用 setState,这需要立即重新渲染。目前我想出的唯一解决方案是将 setState 推迟到渲染周期之后:
window.setTimeout =>
@setState({push, pop})
, 0
您可以在以下JSFiddle 中查看此示例的工作版本。
但是这个解决方案在一些非常基本的方面似乎是错误的。它不是声明性的,它每次涉及两次渲染,并且打破了单向数据流模式。我非常想在这两个用例中使用相同的组件——孩子推送和弹出,或者父/兄弟推送和弹出,或者可能是孩子推送和父弹出的混合。我似乎无法想出一个功能模式来完成此任务,而不会破坏一些非常基本的良好编程规则。
编辑 1:
所以我想我已经找到了朝着正确方向迈出的一步:EventEmitters。按钮发出事件,NavVC 监听这些事件。我们可以通过 App 组件连接这些事件,NavVC 将在挂载时监听这些事件,并在卸载时取消注册这些监听器。这样可以保持单向数据流!
一个问题仍然存在。 NavVC 需要能够告诉后退按钮是否可弹出,以便后退按钮知道它是否可以显示......我还没有弄清楚。
【问题讨论】:
标签: coffeescript functional-programming reactjs