如何避免在React中使用setProps

How to avoid using setProps in React?

本文关键字:setProps React 何避免      更新时间:2024-07-10

我正在使用react & redux开发应用程序,我需要setProps,但它已被弃用。

看看下面的错误:

警告:不赞成使用setProp(…)和replaceProps(…)。相反在顶层再次调用render。

未捕获的不变冲突:setProps(…):您在上调用了setProps具有父级的组件。这是一个反模式,因为道具会在渲染时进行反应性更新。相反,请更改所有者的render方法将正确的值作为道具传递给组件它是在哪里创建的。

那么我该如何设置道具呢?基本上是管理一些选项卡,看看下面的代码:

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;
  }
}

您似乎在没有牢牢掌握React的情况下就开始使用Redux。我不建议你这么做,因为你现在似乎有点困惑。

React中的思考是一个很好的指南,我建议您在使用Redux之前,先了解一下React中国家所有权的概念。

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

这里的重要部分是props是指由父母接收的。如果一个组件想要更改自己的道具,这是一个问题的症状:

  1. 您应该对此组件使用state,因此可以调用setState
  2. 或者您的组件需要访问像onChange这样的回调道具,这样它就可以"请求"更改值

在第一种情况下,您的代码看起来是这样的,并且它将是有效的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>
     );
  }
});

无论哪种方法是好是坏,它们用于不同的目的。当状态与应用程序的其他部分无关时,第一个更方便,当其他远程组件碰巧也需要它时,第二个更方便。确保您了解这两种方法的权衡。

即使使用第二种方法,也必须有的东西来保持状态。它可以是另一个组件,或者(这就是Redux的用武之地)它可以是一个单独的数据存储,就像Redux存储一样。在这种情况下,不是从父组件提供onChange,而是使用connect()绑定它注入的onChange道具以调度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。您可以:

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

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

集装箱:

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>
     );
  }
});

表示组件(如Timeline组件)通常不应该管理应用程序状态。道具必须接收的所有数据和回调。

我建议您阅读有关Presentational和Container组件的文章:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sijqpzk93