如何避免在React中使用setProps?

时间:2016-04-14 21:42:10

标签: javascript reactjs redux

我正在使用react & redux处理应用程序,我需要setProps但不推荐使用它。

看看下面的错误:

  

警告:不推荐使用setProps(...)和replaceProps(...)。代替,   在顶层再次调用渲染。

     

未捕获的不变违规:setProps(...):您打开了setProps   具有父级的组件。这是一种反模式,因为道具会   渲染时得到反应性更新。相反,改变所有者的   render方法将正确的值作为props传递给组件   在哪里创建。

那么如何设置道具?基本上是管理一些标签,看看我的代码:

export default React.createClass({
  render: function() {
    return (
         <div className="demo-tabs">
             <Tabs activeTab={this.props.activeTab}
                   onChange={(tabId) => this.setProps({ activeTab: tabId })} ripple>
                 <Tab>Stack</Tab>
                 <Tab>GitHub</Tab>
                 <Tab>Twitter</Tab>
             </Tabs>
             <section>
                 <div className="content">Content for the tab: {this.props.activeTab}</div>
             </section>
         </div>
     );
  }
});

容器

import React from 'react';
import TimeLine from './timeline';
import { connect } from 'react-redux';
import { getStackoverflow } from 'api/timeline';

const TimeLineContainer = React.createClass({

    componentWillMount: function() {
      getStackoverflow();
    },

    render: function() {
        return (
            <TimeLine {...this.props} />
        )
    }
});

const stateToProps = function(state) {
    return {
        timeline: state.timelineReducer.timeline
    }
}

const dispatchToProps = function(dispatch) {
    return {
        onClick: () => {console.log('timeline was clicked')}
    }
}

export default connect(stateToProps, dispatchToProps)(TimeLineContainer)

减速

var timelineInitialState = {
  github:         [],
  gist:           [],
  stackoverflow:  [],
  twitter:        [],
  activeTab:      2
}

export default function(state = timelineInitialState, action) {
  switch (action.type) {
    case 'GET_GITHUB':
      //TODO: implement.
      break;
    case 'GET_GIST':
      //TODO: implement.
      break;
    case 'GET_STACKOVERFLOW':
      var stackoverflowState = Object.assign({}, state)
      stackoverflowState.stackoverflow = action.stackoverflow;
      return stackoverflowState;
      break;
    case 'GET_TWITTER':
      //TODO: implement.
      break;
    default:
      return state;
  }
}

2 个答案:

答案 0 :(得分:45)

看起来你在没有先掌握React的情况下潜入Redux。我不建议这样做,因为你现在看起来有些困惑。

Thinking in React是一个很好的指南,我建议您先使用它,然后在使用Redux之前熟悉React中的状态所有权。

在React中,随时间变化的事物(“状态”)总是由某个组件“拥有”。有时它是使用此状态进行渲染的相同组件。有时需要同步许多组件,因此状态被“提升”到可以管理它们的某个父组件,并通过props传递该信息。每当状态发生变化时,它们都会得到更新。

这里的重要部分是props意味着由父母接收。如果组件想要更改自己的道具,则表明存在问题:

  1. 您应该使用state作为此组件,因此您可以致电setState
  2. 或者您的组件需要访问onChange之类的回调支柱,以便它可以“询问”要更改的值
  3. 在第一种情况下,您的代码看起来像这样,它将是有效的React:

    export default React.createClass({
      getInitialState: function() {
        return { activeTab: 0 }
      },
    
      render: function() {
        return (
             <div className="demo-tabs">
                 <Tabs activeTab={this.state.activeTab}
                       onChange={(tabId) => this.setState({ activeTab: tabId })} ripple>
                     <Tab>Stack</Tab>
                     <Tab>GitHub</Tab>
                     <Tab>Twitter</Tab>
                 </Tabs>
                 <section>
                     <div className="content">Content for the tab: {this.state.activeTab}</div>
                 </section>
             </div>
         );
      }
    });
    

    但是,您可以继续使用内置<Tabs>组件的模式,并将状态提升得更高。然后,您的组件需要接受onChange道具,它将传递给<Tabs>。现在它不知道如何状态更新,并且不保持状态:

    export default React.createClass({
      render: function() {
        return (
             <div className="demo-tabs">
                 <Tabs activeTab={this.props.activeTab}
                       onChange={this.props.onChange} ripple>
                     <Tab>Stack</Tab>
                     <Tab>GitHub</Tab>
                     <Tab>Twitter</Tab>
                 </Tabs>
                 <section>
                     <div className="content">Content for the tab: {this.props.activeTab}</div>
                 </section>
             </div>
         );
      }
    });
    

    这两种方法都不是更好或更差,它们用于不同的目的。当状态与app的其余部分无关时,第一个更方便,当其他远程组件碰巧也需要它时,第二个是方便的。确保你理解两种方法的权衡。

    即使采用第二种方法,某些东西也必须保持状态。它可能是另一个组件,或者(这是Redux的用武之地)它可能是一个单独的数据存储,如Redux商店。在这种情况下,不是从父组件提供onChange,而是使用connect()绑定它注入的onChange prop来调度Redux操作:

    const mapStateToProps = (state) => {
      return {
        activeTabId: state.activeTabId
      }
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        onChange: (tabId) => dispatch({ type: 'SET_ACTIVE_TAB', tabId })
      }
    }
    

    在你的减速器中,你可以处理这个动作:

    const activeTabId = (state = 0, action) => {
      switch (action.type) {
      case 'SET_ACTIVE_TAB':
        return action.tabId
      default:
        return state
      }
    }
    
    const reducer = combineReducers({
      activeTabId,
      // other reducers
    })
    
    const store = createStore(reducer)
    

    在这两种情况下我们都不需要setProps。你可以:

    • 让组件拥有状态并使用setState
    • 让它接受activeTabIdonChange道具,并使用setState
    • 树中较高的组件管理它们
    • 您可以将状态处理完全从组件移到Redux之类的东西,但您的组件仍然会接受activeTabIdonChange作为道具。

答案 1 :(得分:2)

从Container提供onTabChange回调到Timeline组件。

容器:

const dispatchToProps = function(dispatch) {
    return {
        onClick: () => {console.log('timeline was clicked')},
        onTabChange: (tabId) => { setActiveTabAction(tabId) }
    }
}
const stateToProps = function(state) {
    return {
        timeline: state.timelineReducer.timeline,
        activeTab: state.timelineReducer.activeTab
    }
}

时间轴组件:

export default React.createClass({
  render: function() {
    return (
         <div className="demo-tabs">
             <Tabs activeTab={this.props.activeTab}
                   onChange={this.props.onTabChange} ripple>
                 <Tab>Stack</Tab>
                 <Tab>GitHub</Tab>
                 <Tab>Twitter</Tab>
             </Tabs>
             <section>
                 <div className="content">Content for the tab: {this.props.activeTab}</div>
             </section>
         </div>
     );
  }
});

演示组件(如时间轴组件)通常不应管理应用程序状态。他们必须通过道具收到的所有数据和回调。

我建议你阅读有关演示文稿和容器组件的文章: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sijqpzk93