React React-Redux Redux-Persist проблема производительности - PullRequest
1 голос
/ 19 июня 2020

Я новичок в том, чтобы одновременно реагировать и сокращать.

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

Проблема в том, что каждая отправка сохранения ответа занимает более 2 секунд, когда я визуализирую 300 элементов .

Это мой компонент, который я использую для каждого из вопросов (элементов).

import React from 'react';
import {setAnswer} from "../actions";
import {connect} from 'react-redux'
import {Container,Row,Col} from 'react-bootstrap'
import {Radio, RadioGroup,FormControlLabel} from '@material-ui/core'


class FormItem extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            answer: this.props.answers[this.props.item.nr] ? this.props.answers[this.props.item.nr] : 0
        }
        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(event){
        const target = event.target;
        this.setState({
            answer:target.value
        })
        this.props.setAnswer(this.props.item.nr,target.value)
    }

    render() {
        return (
            <Row className={"item-row" + (this.state.answer != 0 ? " answered" : "")}>
                <Col md={"5"}><div>{this.props.item.text}</div></Col>
                <Col md={"7"}>
                    <RadioGroup name={"item"+this.props.answers.nr} value={this.state.answer} onChange={this.handleChange}>
                        <Row>
                            <Col className={"text-center"}><FormControlLabel value="1" control={<Radio color="primary" />} /></Col>
                            <Col className={"text-center"}><FormControlLabel value="2" control={<Radio color="primary" />} /></Col>
                            <Col className={"text-center"}><FormControlLabel value="3" control={<Radio color="primary" />} /></Col>
                            <Col className={"text-center"}><FormControlLabel value="4" control={<Radio color="primary" />} /></Col>
                            <Col className={"text-center"}><FormControlLabel value="5" control={<Radio color="primary" />} /></Col>
                        </Row>
                    </RadioGroup>
                </Col>
            </Row>
        );
    }
}


const mapStateToProps = (state) => {
    return {
        answers : state.answers
    }
}

const mapDispatchToProps = () => {
    return {
        setAnswer
    }
}

export default connect(mapStateToProps,mapDispatchToProps())(FormItem);

Проблема в том, что если я визуализирую все 300 элементов на странице одновременно , отправка действия setAnswer при смене радио занимает 2,5 секунды. Если я визуализирую только 30 изображений за раз, это работает неплохо. 300 - это не так уж много, поэтому я предполагаю, что я что-то делаю неправильно, так как это сильно влияет на производительность.

Спасибо!

1 Ответ

1 голос
/ 19 июня 2020

Вы можете вынуть часть вопроса и поместить ее в чистый компонент, слишком много кода для go, и вы не знаете, почему вы должны дублировать состояние редукции в локальное состояние, но вот пример, где question - это чистый компонент (с использованием React.memo), которому передается обработчик изменений, который никогда не изменяется (с использованием React.useCallback), и вопрос (из state.items).

Поскольку Question - это чистый компонент, он будет отображается только при изменении свойства, и только элемент, ответ на который изменяется, будет изменен, поэтому будет повторно отображен только вопрос:

Вот функциональный пример

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const NUMBER_OF_QUESTIONS = 300;
const initialState = {
  items: new Array(NUMBER_OF_QUESTIONS)
    .fill('')
    .reduce((result, _, id) => {
      result[id] = { id, answer: '' };
      return result;
    }, {}),
};
//action types
const CHANGE_ANSWER = 'CHANGE_ANSWER';
//action creators
const changeAnswer = (id, answer) => ({
  type: CHANGE_ANSWER,
  payload: { id, answer },
});
const reducer = (state, { type, payload }) => {
  if (type === CHANGE_ANSWER) {
    const { id, answer } = payload;
    return {
      ...state,
      items: {
        ...state.items,
        [id]: { ...state.items[id], answer },
      },
    };
  }
  return state;
};
//selectors (not even using reselect)
const selectItems = (state) => Object.values(state.items);
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      //middleware handling SET_NEXT action will
      //  dispatch removed and added for each item that
      //  has been removed or added
      () => (next) => (action) => next(action)
    )
  )
);
//*********************************************
//               End of set up code
//*********************************************

//make Question a pure component using React.memo
const Question = React.memo(function Question({
  changeQuestion,
  question: { id, answer },
}) {
  const r = React.useRef(0);
  r.current++;
  return (
    <li>
      {id} rendered {r.current}
      <input
        type="text"
        value={answer}
        onChange={(e) => changeQuestion(id, e.target.value)}
      />
    </li>
  );
});
const App = () => {
  const questions = useSelector(selectItems);
  const dispatch = useDispatch();
  const onChange = React.useCallback(
    (id, val) => dispatch(changeAnswer(id, val)),
    [dispatch]
  );
  return (
    <ul>
      {questions.map((question) => (
        <Question
          key={question.id}
          question={question}
          changeQuestion={onChange}
        />
      ))}
    </ul>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>


<div id="root"></div>

Вот пример с компонентами класса, использующими PureComponent для вопроса:

const { Provider } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const NUMBER_OF_QUESTIONS = 300;
const initialState = {
  items: new Array(NUMBER_OF_QUESTIONS)
    .fill('')
    .reduce((result, _, id) => {
      result[id] = { id, answer: '' };
      return result;
    }, {}),
};
//action types
const CHANGE_ANSWER = 'CHANGE_ANSWER';
//action creators
const changeAnswer = (id, answer) => ({
  type: CHANGE_ANSWER,
  payload: { id, answer },
});
const reducer = (state, { type, payload }) => {
  if (type === CHANGE_ANSWER) {
    const { id, answer } = payload;
    return {
      ...state,
      items: {
        ...state.items,
        [id]: { ...state.items[id], answer },
      },
    };
  }
  return state;
};
//selectors (not even using reselect)
const selectItems = (state) => Object.values(state.items);
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      //middleware handling SET_NEXT action will
      //  dispatch removed and added for each item that
      //  has been removed or added
      () => (next) => (action) => next(action)
    )
  )
);
//make question pure component by extending React.PureComponent
class Question extends React.PureComponent {
  rendered = 0;
  render() {
    const {
      changeQuestion,
      question: { id, answer },
    } = this.props;
    this.rendered++;
    return (
      <li>
        {id} rendered {this.rendered}
        <input
          type="text"
          value={answer}
          onChange={(e) =>
            changeQuestion(id, e.target.value)
          }
        />
      </li>
    );
  }
}
class AppComponent extends React.Component {
  render() {
    const questions = this.props.questions;
    return (
      <ul>
        {questions.map((question) => (
          <Question
            key={question.id}
            question={question}
            changeQuestion={this.props.changeAnswer}
          />
        ))}
      </ul>
    );
  }
}
const App = ReactRedux.connect(
  (state) => ({
    questions: selectItems(state),
  }),
  { changeAnswer }
)(AppComponent);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>


<div id="root"></div>
...