Вложенные триггеры обновлений состояния избыточности визуализируются на каждом уровне - PullRequest
0 голосов
/ 28 октября 2019

Описание: я пытаюсь обновить состояние вложенного объекта. При обновлении вложенного объекта я вижу, что также вызывается метод рендеринга его родственного компонента.

Ожидаемое поведение: из всех статей, через которые я прошел, указывается, возвращать ли я новый объект только с обновленным вложенным объектом,реакция достаточно умна, чтобы отрисовывать только обновленный объект.

Пример:

reducer.tsx

// imports

const initialState = {
  firstLevel: {
    secondLevel: {
      jobLevel: {
        id: 'L52';
      },
      expectedSalary: {
        amount: '100000';
      }
    }
  }
}

export interface State {
  firstLevel: {
    secondLevel: {
      jobLevel: {
        id: string;
      },
      expectedSalary: {
        amount: string;
      }
    }
  }
}

export const mainState = (state: State = initialState, action) => {
  switch(action.type) {
    case 'NESTED_UPDATE':
      return {
        ...state,
        firstLevel : {
          secondLevel: {
            ...state.firstLevel.secondLevel,
            expectedSalary: {
              amount: '120000'
            }
          }
        }
      }
  }

}

Таким образом, в приведенном выше состоянии редукса я не изменяю состояние, чтобы начать с. Я возвращаю новый объект, который изменился в> firstLevel.secondLevel.expectedSalary объекте. Пока firstLevel.secondLevel.jobLevel объект не изменен.

Компоненты:

Main.tsx

// imports
class MainSection extends Component<> {
  // constructor and more logic

  render() {
    return (
      <div>
        <JobLevel {...props.firstLevel.secondLevel} />
      </div>
      <div>
        <ExpectedSalary 
          {...props.firstLevel.secondLevel}
          updateSecondLevel={this.props.updatedSecondLevel} //I know its included in props, for readability
        />
      </div>
  }
}

export default MainSection;

MainContainer.jsx

import {connect} from 'react-redux';
import {SplitActions, CreateRuleState} from '../types';
import RuleSplitSection from '../components/RuleSplitSection';
import Actions from '../actions/RuleDrawerActions';
import {calculateSplitRuleLineWidth} from '../utils/RuleUtil';

export const mapStateToProps = (state) => {
    return state;
};

export const mapDispatchToProps = dispatch => {
    return {
        updateSecondLevel: () => dispatch(Actions.updateSecondLevel())
    };
};

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Main as any);

Подкомпоненты: JobLevel.jsx

class JobLevel extends Component<> {
  // constructor and more logic

  render() {
    return (
      // debug point 1
      <div>
        // assume TextField is another redux util component which takes some props as below.
        <TextField
          value={this.props.jobLevel}
          disabled // read only
        />
      </div>
  }
}

ExpectedSalary.jsx

class ExpectedSalary extends Component<> {
  // constructor and more logic

  updateExpectedSalarySecondLevel = evt => {
    const expectedSalaryObj = {amount: evt.target.value}
    this.props.updateSecondLevel('expectedSalary', expectedSalaryObj);
  } 


  render() {
    return (
      // debug point 2 
      <div>
        // assume TextField is another redux util component which takes some props as below.
        <TextField 
          onChange={this.updateExpectedSalarySecondLevel}
          value={this.props.expectedSalary}
        />
      </div>
  }
}

Сценарий: в приведенном выше случае, когда я что-то набираю в текстовом поле ExpectedSalary, я жестко кодирую значение в '120000' на данный момент. Мое понимание было, когда я обновил ExpectedSalary subobject, реакция достаточно умна, чтобы просто перерисовать подкомпонент ExpectedSalary. Но от отладки я вижу, что он останавливается на debug point 1 and 2.

Мне было интересно, почему ??? Теперь, чтобы избежать повторного рендеринга JobLevel, мне нужно использовать shouldComponentUpdate и проверить глубокое копирование, как показано ниже:

    shouldComponentUpdate(nextProps) {
        return this.props.jobLevel.id !== nextProps.jobLevel.id;
    }

Поэтому мои вопросы будут такими: 1. Переход в метод рендеринга JobLevel является частью реакции жизни-цикл? 2. Если да, нужно ли этого избегать? Это дорогой процесс?

Дайте мне знать, это comments, если требуется дополнительная ссылка на код. Я исключил файл Actions из простого кода от скуки ввода здесь.

1 Ответ

0 голосов
/ 28 октября 2019

По умолчанию реакция перерисовывает все компоненты при каждой перерисовке. Вам нужно пометить компонент как Pure и иметь стабильный реквизит или реализовать componentDidUpdate, как вы сделали, чтобы избежать повторного рендеринга. react-redux делает это для connect ed компонентов.

В вашем случае есть пара вещей, необходимых, чтобы избежать повторного рендеринга JobLevel. Первый - это расширение PureComponent вместо Component, а второй - только пропуск необходимых реквизитов на сайт вызова.

import {PureComponent} from 'react';

class JobLevel extends PureComponent<> {
  // constructor and more logic
  ...
}

И сейчас, когда вы рендерите JobLevel, выраспространяются все ...props.firstLevel.secondLevel, которое включает в себя ключ expectedSalary, который меняется. Вам нужно будет только пропустить необходимые, стабильные реквизиты:

<JobLevel jobLevel={props.firstLevel.secondLevel.jobLevel} />

В противном случае вы, по сути, делаете

<JobLevel
  jobLevel={props.firstLevel.secondLevel.jobLevel}
  expectedSalary={props.firstLevel.secondLevel.expectedSalary}
/>

, так как опора expectedSalary меняется, компонент будет перерисован, даже если JobLevel помечен как Pure. React не может проанализировать ваш код рендеринга, чтобы узнать, какие реквизиты используются, или нет - он проводит поверхностное сравнение всех пропущенных реквизитов, чтобы решить, нужно ли повторно визуализировать PureComponent.

...