Как избежать использования setProps в React?

Я работаю над приложением, используя react & redux, и мне нужны setProps, но он устарел.

Посмотрите на ошибку ниже:

Предупреждение: setProps (...) и replaceProps (...) устарели. Вместо, снова вызовите рендеринг на верхнем уровне.

Неактивное инвариантное нарушение: setProps (...): вы вызвали setProps on компонент с родителем. Это анти-шаблон, поскольку получать реактивное обновление при рендеринге. Вместо этого измените 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;
  }
}

Ответ 1

Похоже, вы погрузились в использование Redux, не получив при этом твердого сопротивления React. Я бы не рекомендовал делать это, потому что теперь вы немного смущены.

Мышление в действии - отличное руководство, и я рекомендую вам сначала пройти его и устроиться с идеей государственной собственности в React, прежде чем использовать Redux.

В React вещи, которые меняются с течением времени ( "состояние" ), всегда "принадлежат" некоторым компонентам. Иногда это тот же компонент, который использует это состояние для рендеринга. Иногда многие компоненты должны быть синхронизированы, поэтому состояние получает "подъем" к некоторому родительскому компоненту, который может управлять ими всеми, и передавать эту информацию через реквизиты. Всякий раз, когда изменяется состояние, все они обновляются.

Важная часть здесь состоит в том, что props предполагается получить родителем. Если компонент хочет изменить свои собственные реквизиты, это является симптомом проблемы:

  • Либо вы должны были использовать state для этого компонента, поэтому вы можете вызвать setState
  • Или ваш компонент должен получить доступ к обратному вызову, например 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 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,
  • пусть он принимает реквизиты activeTabId и onChange и управляет ими из компонента, выше в дереве, который будет использовать setState, или
  • вы можете полностью переместить обработку состояния из компонентов в нечто вроде Redux, но ваши компоненты все равно будут принимать activeTabId и onChange как реквизиты.

Ответ 2

Предоставить обратный вызов 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>
     );
  }
});

Презентационные компоненты (например, ваш компонент Timeline) обычно не должны управлять состоянием приложения. Все данные и обратные вызовы, которые они должны получать посредством реквизита.

Я предлагаю вам прочитать статью о компонентах презентации и контейнера: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sijqpzk93