Это пример того, как вы можете сделать это, используя текущий код.Я не согласен с тем, как это делается, но для начала это должно помочь вам начать исследовать более «реактивный» способ кодирования.
// This is a composable function, we will pass it in to setInterval.
// we need access to the component, so we will pass it in and then
// return the function signature that setInterval wants to call
const changeAdvertComposer = function changeAdvertComposer(component) {
// we start at -1 so the first call asks for 0
let index = -1;
// return the function that setInterval will call
return function changeAdvert() {
const adverts = component.props.adverts;
if (!adverts || !adverts.length) {
// we have no advertisements so lets exit
return;
}
index++;
// reset our index if needed
if (index >= adverts.length) {
index = 0;
}
// increment and grab our object
const advert = adverts[index];
// grab our state or a default failover structure
const state = component.state.advert || { bg: '', text: '' };
// set our state
component.setState({
advert: {
bg: advert.photo || state.bg,
text: advert.text || state.text,
}
});
}
};
export ExampleAdvertManager extends Component {
// setup some validations on our props
static propTypes = {
getAdverts: PropTypes.func.isRequired,
adverts: PropTypes.arrayOf(PropTypes.shape({
photo: PropTypes.string,
text: PropTypes.string
}))
}
constructor(props) {
super(props);
// we will store the state in the interval object so we can
// check for null (simple way to find out if loading)
this.state = {
advert: null
};
// we will store the ref to our interval here
this._intervalRef = null;
}
componentDidMount() {
// we are loaded let's call our data action loader
this.props.getAdverts();
}
componentWillUpdate() {
// do we have any ads?
const adlength = this.props.adverts ? this.props.adverts.length : 0;
if (adlength && !this._intervalRef) {
// we have ads and we have not setup the interval so lets do it
this._intervalRef = setInterval(changeAdvertComposer(this), 4000);
} else if (!adlength && this._intervalRef) {
// we have no ads but we have an interval so lets stop it
clearInterval(this._intervalRef);
this._intervalRef = null;
}
}
componentWillUnmount() {
// we are unloading, lets clear up the interval so we don't leak
if (this._intervalRef) {
clearInterval(this._intervalRef);
this._intervalRef = null;
}
}
render() {
if (this.stage.advert) {
// render here
}
// we don't have data yet
return null; // or some loading view
}
}
И, возможно, в этом примере я перешел за борт, ямы занимаемся этим слишком долго и действительно зависим от возможности компоновки для модульного тестирования.Мне трудно не думать таким образом.Я не сделал setState компонуемым, хотя ... кроличья нора идет намного глубже.
Действительно, я бы сделал интервальный таймер своим собственным компонентом, который рендерится нулем и запускает обратные вызовы для моего компонента.Просто делает все чище.Это будет выглядеть примерно так:
class TimerComponent extends PureComponent {
static propTypes = {
onInterval: PropTypes.func.isRequired,
interval: PropTypes.number.isRequired,
immediate: PropTypes.bool,
}
static defaultProps = {
immediate: true,
}
componentDidMount() {
this._intervalRef = setInterval(this.props.onInterval, this.props.interval);
if (this.props.immediate) {
this.props.onInterval();
}
}
componentWillUnmount() {
clearInterval(this._intervalRef);
}
render() {
return null;
}
}
// We will still use the composable function, but in a differnt way.
// The function stays the same
const changeAdvertComposer = function changeAdvertComposer(component) {
// we start at -1 so the first call asks for 0
let index = -1;
// return the function that setInterval will call
return function changeAdvert() {
const adverts = component.props.adverts;
if (!adverts || !adverts.length) {
// we have no advertisements so lets exit
return;
}
index++;
// reset our index if needed
if (index >= adverts.length) {
index = 0;
}
// increment and grab our object
const advert = adverts[index];
// grab our state or a default failover structure
const state = component.state.advert || { bg: '', text: '' };
// set our state
component.setState({
advert: {
bg: advert.photo || state.bg,
text: advert.text || state.text,
}
});
}
};
export ExampleAdvertManager extends Component {
// setup some validations on our props
static propTypes = {
getAdverts: PropTypes.func.isRequired,
adverts: PropTypes.arrayOf(PropTypes.shape({
photo: PropTypes.string,
text: PropTypes.string
}))
}
constructor(props) {
super(props);
// we will store the state in the interval object so we can
// check for null (simple way to find out if loading)
this.state = {
advert: null
};
}
componentDidMount() {
// we are loaded let's call our data action loader
this.props.getAdverts();
}
render() {
if (this.stage.advert) {
return (
<div>
<TimerComponent interval={4000} onInterval={changeAdvertComposer(this)} />
{
// render what you want to do from state
}
</div>
);
} else if (this.props.adverts) {
return (
<TimerComponent key="interval" interval={4000} onInterval={changeAdvertComposer(this)} />
);
}
// we don't have data yet
return null; // or some loading view
}
}
Надеюсь, это поможет.
Пример кода
Запуск демоверсии