React

时间:2015-07-10 18:42:00

标签: javascript animation reactjs

我对React感到沮丧,因为视图层次结构破坏了我的动画。

我有一个<Layout>组件,用于设置制表符 - 导航布局,其子项放置在布局的内容部分中。它是一个纯组件,具有titlesetTabcurrentTab的道具。

如果我从视图层次结构角度和单向数据流中考虑这个问题,我会将布局设置为每个视图的子项,以便视图可以设置标题,当前选项卡和setTab可以委托给其父母。

这基本上就是我在做的事情:

var A = React.createClass({
  propTypes: {
    setTab: React.PropTypes.func.isRequired
  },
  render: function() {
    return (
      <Layout title="A Page" currentTab="A" setTab={this.props.setTab}>
        This is A
      </Layout>           
    )
  }
});

现在你可以使用React Router或其他东西作为选项卡,但是现在,我只有一个简单的顶级组件切换选项卡及其状态:

var App = React.createClass({
  getInitialState: function() {
    return {
      currentTab: 'A'
    }
  },
  setTab: function(name) {
    this.setState({currentTab:name})
  },
  render: function() {
    var component = false
    if (this.state.currentTab == 'A') {
      return <A setTab={this.setTab}/>
    } else {
      return <B setTab={this.setTab}/>
    }
  }
});

在您想要开始添加动画之前,这一切都很好用。

在我的<Layout>组件中,我有一个高亮显示区块,我想在标签之间切换时从左到右动画。

        <div className="tabbar">
          <div className={"highlight " + this.props.currentTab}></div>
          <div className="tabs">
            <div className="tab" onClick={() => {this.props.setTab('A')}}>A</div>
            <div className="tab" onClick={() => {this.props.setTab('B')}}>B</div>
          </div>
        </div>

这是该动画的CSS:

.highlight {
    border-bottom: 3px solid red;
    width: 50%;
    position: absolute;
    top: 0;
    bottom: 0;
    transition: left .25s ease-in-out;
}
.highlight.A {
    left: 0;
}
.highlight.B {
    left: 50%;
}

我也尝试使用React.addons.CSSTransitionGroup设置版面标题的动画,但我认为问题是相同的:

这里的问题似乎是<Layout>组件每次都被完全重新渲染,因为渲染树的根(<A/><B/>)正在发生变化。

现在的问题是,如何反转视图层次结构,以便<Layout>不会重新渲染,以便动画可以工作?

注意:在一个不那么做作的例子中,布局也可以从<A/>获得其他道具,例如onClick处理程序。

这是一个证明我沮丧的JSFiddle:

https://jsfiddle.net/ccorcos/3rupb0og/

1 个答案:

答案 0 :(得分:0)

确定。我觉得有些东西可行,但我不确定这是不是最好的做法,所以在得出结论这是答案之前,我真的很感激一些反馈。

我通过组件层次结构提出了stitch作为拼接函数的方法的概念。这将允许我从子组件设置父组件的状态。

function stitch() {
  return {
    to: (f) => {
      this.func = f
    },
    from: () => {
      this.func.apply(null, arguments)
    }
  }
}

所以现在我的<Layout>看起来像这样:

var Layout = React.createClass({
  propTypes: {
    currentTab: React.PropTypes.string.isRequired,
    onTab: React.PropTypes.func.isRequired,
    stitchTitle: React.PropTypes.string.isRequired
  },
  getInitialState: function() {
    return {
      title: ''
    }
  },
  componentWillMount: function() {
    this.props.stitchTitle((title) => {
      this.setState({title})
    })
  },
  // ...
}

视图如下:

var A = React.createClass({
  propTypes: {
    setTitle: React.PropTypes.func.isRequired
  },
  componentWillMount: function() {
    this.props.setTitle('Page A')
  },
  //...
}

我们所要做的就是将它们一起缝合

var App = React.createClass({
  getInitialState: function() {
    return {
      currentTab: 'A'
    }
  },
  setTab: function(name) {
    this.setState({currentTab:name})
  },
  componentWillMount: function() {
    this.titleStitch = stitch()
  },
  render: function() {
    var view = false
    if (this.state.currentTab == 'A') {
      view = <A setTitle={this.titleStitch.from}/>
    } else {
      view = <B setTitle={this.titleStitch.from}/>
    }
    return (
      <Layout currentTab={this.state.currentTab} onTab={this.setTab} stitchTitle={this.titleStitch.to}>
        {view}
      </Layout>  
    )
  }
});

确实有效!这是JSFiddle:

https://jsfiddle.net/ccorcos/bLtuLdwh/