首页 > 解决方案 > 1 组件的所有实例都引用第一个实例而不是其自身;2 子道具只有在调用父母的 setState 两次后才会更新

问题描述

更新:我通过为所有“评级”组件提供唯一名称轻松解决了第一个问题,但关于 2 x setState 修复道具的问题仍然悬而未决。

我其实有两个问题。第二个来自试图解决第一个问题。最初的问题是,每个 SongScoring 组件中的每个 onChange Rating 属性中的“this”都指向 SongScoring 类的第一个实例,而不是它本身:

父代码:

import React, { Component } from 'react';
import './Assess.css';
import { Accordion } from 'react-bootstrap';
import SongScoring from './SongScoring';

class Assess extends Component {
  constructor(props) {
    super(props);

    const songs = [
      {
        track: "song1",
      },
      {
        track: "song2",
      },
      {
        track: "song3",
      },
      {
        track: "song4",
      },
    ]

    this.state = {
      songs: songs,
    };
  }

  render() {
    return (
      <div className="root">
        <Accordion defaultActiveKey="0">
          {
            this.state.songs.map((song, index) => (
              <SongScoring song={song} key={index.toString()} index={index} />
            ))
          }
        </Accordion>
      </div>
    );
  }
}

export default Assess;

子代码:

import React, { Component } from 'react';
import './SongScoring.css';
import { Accordion, Card, Container, Row } from 'react-bootstrap';
import ReactPlayer from "react-player";
import Rating from '@material-ui/lab/Rating';
import Box from '@material-ui/core/Box';

const labels = {
  0.5: 'Unpleasant',
  1: 'Bearable',
  1.5: 'Bearable+',
  2: 'Intriguing',
  2.5: 'Intriguing+',
  3: 'Ok',
  3.5: 'Ok+',
  4: 'Pleasant',
  4.5: 'Pleasant+',
  5: 'Excellent',
};

class SongScoring extends Component {

  constructor(props) {
    super(props);
    this.state = {
      song: props.song,
      key: props.index.toString(),
      score: props.song.score || 0,
      hover: props.song.score || -1,
      onChange: this.props.onChange,
      onChangeActive: this.props.onChangeActive
    }
  }

  render() {
    return (
      <>
        <Card>
          <Accordion.Toggle as={Card.Header} variant="link" eventKey={this.state.key}>
            {this.state.key}
            <Rating name="read-only" value={this.state.score} precision={0.5} readOnly />
          </Accordion.Toggle>
          <Accordion.Collapse eventKey={this.state.key}>
            <Card.Body style={{ display: 'flex', alignItems: 'center', lineHeight: '1' }}>
              <Container>
                <Row className='scoring-row'>
                  <ReactPlayer
                    url="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
                    width="400px"
                    height="50px"
                    playing={false}
                    controls={true}
                    style={{ outline: 'none' }}
                  />
                </Row>
                <Row className='scoring-row'>
                  <Rating
                    name="song-rating"
                    value={this.state.score}
                    precision={0.5}
                    onChange={(event, newScore) => {
                      this.setState({
                        score: newScore
                      });
                    }}
                    onChangeActive={(event, newHover) => {
                      this.setState({
                        hover: newHover
                      });
                    }}
                    style={{ marginTop: '40px' }}
                  />
                </Row>
                <Row className='scoring-row'>
                  {this.state.score !== 0 && <Box>{labels[this.state.hover !== -1 ? this.state.hover : this.state.score]}</Box>}
                </Row>
              </Container>
            </Card.Body>
          </Accordion.Collapse>
        </Card>
      </>
    );
  }
}

export default SongScoring;

经过多次迭代尝试通过从箭头函数和绑定更改来解决这个问题,将 onChange 和 onChangeActive 放入状态等。我最终决定尝试通过将 onChange 和 onChangeActive 函数从父级传递给子级来解决这个问题。

这就是我想出的(在 onChange 和 onChangeActive 中有 console.log 而不是 setState,但我们真正关心的是 'this' 引用):

父代码:

import React, { Component } from 'react';
import './Assess.css';
import { Accordion } from 'react-bootstrap';
import SongScoring from './SongScoring';

class Assess extends Component {
  constructor(props) {
    super(props);

    const songs = [
      {
        track: "song1",
      },
      {
        track: "song2",
      },
      {
        track: "song3",
      },
      {
        track: "song4",
      },
    ]

    this.songScoring = [];
    const onChange = [];
    const onChangeActive = [];
    for (let i = 0; i < songs.length; i++) {
      this.songScoring.push(React.createRef());
      onChange.push(function (event, newScore) {
        console.log("onChange: ", this);
      });
      onChangeActive.push(function (event, newHover) {
        console.log("onChangeActive: ", this);
      });
    }

    this.state = {
      songs: songs,
      onChange: onChange,
      onChangeActive: onChangeActive
    };

  }

  componentDidMount() {
    console.log(this.state.songScoring);
    const onChange = [];
    const onChangeActive = [];
    for (let i = 0; i < this.state.songs.length; i++) {
      onChange.push(this.state.onChange[i].bind(this.songScoring[i], 1));
      onChangeActive.push(this.state.onChangeActive[i].bind(this.songScoring[i]));
    }    
    this.setState({
      onChange: onChange,
      onChangeActive: onChangeActive
    });//, () => this.setState({unicorn: 1}));
  }

  render() {
    return (
      <div className="root">
        <Accordion defaultActiveKey="0">
          {
            this.state.songs.map((song, index) => (
              <SongScoring song={song} ref={this.songScoring[index]} key={index.toString()} index={index} onChange={this.state.onChange[index]} onChangeActive={this.state.onChangeActive[index]} />
            ))
          }
        </Accordion>
      </div>
    );
  }
}

export default Assess;

子代码:

import React, { Component } from 'react';
import './SongScoring.css';
import { Accordion, Card, Container, Row } from 'react-bootstrap';
import ReactPlayer from "react-player";
import Rating from '@material-ui/lab/Rating';
import Box from '@material-ui/core/Box';

const labels = {
  0.5: 'Unpleasant',
  1: 'Bearable',
  1.5: 'Bearable+',
  2: 'Intriguing',
  2.5: 'Intriguing+',
  3: 'Ok',
  3.5: 'Ok+',
  4: 'Pleasant',
  4.5: 'Pleasant+',
  5: 'Excellent',
};

class SongScoring extends Component {

  constructor(props) {
    super(props);
    this.state = {
        song: props.song,
        key: props.index.toString(),
        score: props.song.score || 0,
        hover: props.song.score || -1,
        onChange: this.props.onChange,
        onChangeActive: this.props.onChangeActive
    }
    
  }

  componentDidUpdate(prevProps, prevState){
    if (prevProps.onChange !== this.state.onChange){
        this.setState({
            onChange: prevProps.onChange,
            onChangeActive: prevProps.onChangeActive
        })
    }

  }

  render() {
    return (
        <>
          <Card> 
            <Accordion.Toggle as={Card.Header} variant="link" eventKey={this.state.key}>
              {this.state.key}
              <Rating name="read-only" value={this.state.score} precision={0.5} readOnly />
            </Accordion.Toggle>
            <Accordion.Collapse eventKey={this.state.key}>
              <Card.Body style={{ display: 'flex', alignItems: 'center', lineHeight: '1' }}>
                <Container>
                  <Row className='scoring-row'>
                    <ReactPlayer
                      url="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
                      width="400px"
                      height="50px"
                      playing={false}
                      controls={true}
                      style={{outline: 'none'}}
                    />
                  </Row>
                  <Row className='scoring-row'>
                    <Rating
                      name="song-rating"
                      value={this.state.score}
                      precision={0.5}
                      onChange={this.state.onChange}
                      onChangeActive={this.state.onChangeActive}
                      style={{ marginTop: '40px' }}
                    />
                  </Row>
                  <Row className='scoring-row'>
                    {this.state.score !== 0 && <Box>{labels[this.state.hover !== -1 ? this.state.hover : this.state.score]}</Box>}
                  </Row>
                </Container>
              </Card.Body>
            </Accordion.Collapse>
          </Card>
        </>
      );
  }
}

export default SongScoring;

在此设置中,console.log 中的 onChange 和 onChangeActive 都输出“未定义”。

现在,如果我取消注释片段:'//, () => this.setState({unicorn: 1}));' (独角兽是一个虚拟的、未使用的变量)在父项中,onChange 和 onChangeActive 的 console.log 很好地打印出“this”作为各个 SongScoring 组件的引用。

因此我的问题是:

更新:我通过为所有“评级”组件提供唯一名称轻松解决了第一个问题,但关于 2 x setState 修复道具的问题仍然悬而未决。

标签: javascriptreactjsmaterial-uireact-propsreact-state

解决方案


推荐阅读