【问题标题】:React react-redux redux-persist performance issueReact react-redux redux-persist 性能问题
【发布时间】:2020-06-19 10:06:03
【问题描述】:

我对同时进行 react 和 redux 很陌生。

我正在构建这个应用程序,其中包含由数百个问题组成的问卷调查。我想实时保留问题的答案,以便用户可以随时回来继续。

问题是,当我渲染 300 个元素时,每次发送持久化答案需要超过 2 秒。

这是我用于每个问题(项目)的组件。

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 并不是一个很大的数字,所以我假设我做错了什么,因为性能受到了很大影响。

谢谢!

【问题讨论】:

    标签: reactjs performance react-redux redux-persist


    【解决方案1】:

    你可以把问题部分拿出来放在一个纯组件中,代码有点多,不知道为什么要将redux状态复制到本地状态,但这里有一个问题的例子一个纯组件(使用 React.memo),它获取一个永远不会更改的更改处理程序(使用 React.useCallback)和问题(来自 state.items)。

    因为 Question 是一个纯组件,它只会在 prop 更改时呈现,并且只有正在更改答案的项目才会更改,因此只有 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>

    【讨论】:

    • 非常感谢您的详细回复!你的组件 Question 和我的组件 FormItem 一样,我尝试通过在父组件中设置的 prop 更新 redux 状态但性能是一样的。我在您的代码中看到的主要区别是您使用的是函数组件而不是像我这样的类组件。那么您认为这会导致性能问题吗?我仍然无法弄清楚我的代码有什么问题。
    • @VladDogarescu 用类组件示例更新了问题,您可以在问题中扩展 PureComponent。
    • 好吧,我终于想通了。我的表现很差,因为我使用 mapStateToProps 将所有答案注入到每个项目中,而不是将与该项目有关的特定答案设置为道具。感谢你的付出!如果您将其写在答案中,我可以将其标记为正确,因为这是影响性能的具体因素,可以帮助其他人。
    猜你喜欢
    • 2018-06-06
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-11
    • 2019-10-22
    • 2018-06-04
    相关资源
    最近更新 更多