Кто-нибудь может объяснить, почему это работает с компонентом класса, но не с функциональным компонентом?

#javascript #reactjs

#javascript #reactjs

Вопрос:

В принципе, у меня есть несколько кнопок. Я хочу, чтобы пользователь мог выбрать несколько кнопок.

Я попытался использовать функциональный компонент и сохранить состояние кнопки как объект с помощью крючка useState. Состояние обновляется соответствующим образом при нажатии кнопок, но реквизиты кнопки не обновляются. Я пробовал использовать эффект для повторного отображения компонента при изменении props.isActive, но это не сработало.

Используя компонент класса, это работает именно так, как задумано. Я просто пытаюсь понять, почему это так. Если кто-нибудь может дать представление, я был бы вам очень признателен. Спасибо.

Функциональный компонент

 const View = (props) => {
  var [buttons, setButtons] = useState([
    { name: "Small", isActive: false },
    { name: "Large", isActive: false },
  ]);
  const handleClick = (index) => {
    let tmp = buttons;
    tmp[index].isActive = !tmp[index].isActive;
    return setButtons(tmp);
  };
  return (
      <div>
            {buttons.map((e, index) => {
              return (
                <MyButtonComponent
                  key={index}
                  name={e.name}
                  isActive={e.isActive}
                  onClick={() => handleClick(index)}
                />
              );
            })}
    </div>
  );
};
 

Компонент класса

 class View extends Component {
  state = {
    btn: [
      { name: "Small", isActive: false },
      { name: "Large", isActive: false },
    ],
  };
  handleClick = (index) => {
    let tmp = this.state.btn;
    tmp[index].isActive = !tmp[index].isActive;
    return this.setState({ ...this.state, btn: tmp });
  };
  render() {
    return (
            <div>
              {this.state.btn.map((e, index) => {
                return (
                  <MyButtonComponent
                    key={index}
                    name={e.name}
                    isActive={e.isActive}
                    onClick={() => this.handleClick(index)}
                  />
                );
              })}
            </div>
    );
  }
}
 

Ответ №1:

Вы изменяете свой старый массив, а затем устанавливаете состояние с помощью измененного массива. Это не очень хорошая идея в react, независимо от того, используете ли вы компоненты класса или функциональные компоненты. Компонент класса позволяет вам избежать этого, но функциональный компонент сравнивает состояние до с состоянием после и видит, что это один и тот же массив, поэтому он пропускает рендеринг.

Чтобы исправить это, вы должны создать новое состояние вместо изменения старого. Измените это:

 let tmp = buttons;
tmp[index].isActive = !tmp[index].isActive;
return setButtons(tmp);
 

К этому:

 // Create a copy of the array
let tmp = [...buttons]; 
// Also copy the item you want to change
tmp[index] = {
  ...tmp[index],
  active: !tmp[index].active
}
setState(tmp);
 

Комментарии:

1. Ааааа понял, в этом есть смысл. Спасибо. Ваше решение работает прекрасно 🙂

Ответ №2:

Вы обновляете ссылку и устанавливаете ту же ссылку на состояние (setButtons(tmp)) , которое react thinks , array не изменилось из-за неглубокого сравнения. Вам нужно использовать новую ссылку. Например, следующее

     let tmp = buttons; <-- problem is here, reference 
    tmp[index].isActive = !tmp[index].isActive;
    return setButtons(tmp); <-- and updating same `reference`
 
    const handleClick = (index) => {
     buttons[index].isActive = !buttons[index].isActive;
     return setButtons([...buttons]); <-- this will work
   };
 

Комментарии:

1. Спасибо за ваше объяснение, я ценю это. Это тоже отлично работает!