Обновления реквизита дочернего компонента React действуют непоследовательно?

#javascript #reactjs

#javascript #reactjs

Вопрос:

Я работаю над компонентом панели загрузки для приложения React.

Процент передается как целое число, но вместо переходного обновления 0, 1, 2, 3, 4, и т.д… 100, реквизит просто обновляет 0… затем сразу до 100.

Но здесь все становится странным.

В render() функции компонента я выводю процент с помощью console.log() , и консоль показывает, что значение обновляется переходно (0, 1, 2, 3 и т. Д. 100), Но его отображение через {percent} по-прежнему остается равным 0, а затем сразу переходит к 100.

Также просмотр инструментов React dev для компонента показывает, что он переходит от 0 к 100, но console.log() в рендеринге проходят все числа.

Вот код для компонента:

 const Loader = ({msg, percent = null, inline = false}) => (
  <div className={"loader" (inline ? ' inline': '')}>
    <i className="fa fa-refresh" />
    <span>{msg}</span>
    {
      console.log(percent "%") // <- this updates number by number
    }
    {
      !isNaN(percent)
      ? <div className="progress-bar">
          <div style={{ width: percent "%" }} /> {/* <- this jumps from 0 right to 100 */}
        </div>
      : null
    }
  </div>
);
  

Кто-нибудь сталкивался с чем-либо подобным раньше?

Я пробовал как функциональный компонент, так и компонент на основе классов, но в обоих случаях console.log() обновляется правильно, но отображаемое значение и значение инструментов React dev — нет.

Родительский компонент:

 class ReadStory extends Component {
  constructor(props) {
    super(props);
    this.state = {
      // ...

      renderTestStr: "",
      storyPages: [],
      pageHeight: 0,
      wordCount: 0,
      wordsRendered: 0
    };

    this.renderTest = React.createRef();

    this.paginate = this.paginate.bind(this);
    this.addWordAndCheckHeight = this.addWordAndCheckHeight.bind(this);
  }

  async componentDidMount() {
    // ...

    await this.paginate(storyData);

    this.setState({
      // ...
      loading: false
    });
  }

  paginate(storyData) {
    return new Promise((resolve) => {
      const {html} = storyData;
      const words = html.split(' ');
      
      const ps = window.getComputedStyle(this.renderTest.current);
      const paddingY = parseFloat(ps.paddingTop)   parseFloat(ps.paddingBottom);


      this.setState({
        pageHeight: (this.renderTest.current.clientHeight - paddingY),
        wordCount: words.length
      }, async () => {

        for (let i in words) {
          const isLastWord = (parseInt(i) === (words.length - 1));
          await this.addWordAndCheckHeight(words[i], isLastWord, i);
        }

        const {storyPages, renderTestStr} = this.state;

        console.log(storyPages);

        resolve();
      });
    });
  }

  addWordAndCheckHeight(word, isLast, i) {
    return new Promise((resolve) => {
      let {renderTestStr, storyPages, pageHeight} = this.state;
      const renderTestReset = renderTestStr;

      renderTestStr  = (" "   word);
      this.setState({ renderTestStr, wordsRendered: (parseInt(i)   1) }, () => {
        const ps = window.getComputedStyle(this.renderTest.current);
        const paddingY = parseFloat(ps.paddingTop)   parseFloat(ps.paddingBottom);
        const rtHeight = (this.renderTest.current.clientHeight - paddingY);

        if (rtHeight === pageHeight amp;amp; !isLast) {
          resolve();
        }
        else if (isLast) {
          storyPages = [
            ...storyPages,
            renderTestStr
          ];
          renderTestStr = "";
          this.setState({
            storyPages,
            renderTestStr
          }, resolve);
        }
        else {
          storyPages = [
            ...storyPages,
            renderTestReset
          ];
          renderTestStr = word;
          this.setState({
            storyPages,
            renderTestStr
          }, resolve);
        }
      });

    });
  }

  render() {
    const {
      // ...
      loading,

      renderTestStr,
      storyPages,
      wordCount,
      wordsRendered
    } = this.state;

    const renderPercent = !isNaN(Math.floor(wordsRendered / wordCount * 100))
      ? Math.floor(wordsRendered / wordCount * 100)
      : 0;
    
    console.log(renderPercent);
    
    let lrPages = [];

    for (let i in storyPages) {
      const isEven = (parseInt(i) % 2 === 0);
      if (isEven) {
        const lr = {
          l: storyPages[i],
          r: (storyPages[parseInt(i)   1] || null)
        };

        lrPages.push(lr);
      }
    }

    return loading
    ? (
        <Fragment>
          <Loader msg="Loading story..." percent={renderPercent} />

          <div className="view read-story">
            <article className="book-page render-test">
              <div ref={this.renderTest}>
                <p>{renderTestStr}</p>
              </div>
            </article>
          </div>
        </Fragment>
      )
    : (
        <Fragment>
          {/* ... */}
        </Fragment>
      );
  }
}

export default withRouter(ReadStory);
  

Это большой компонент просмотра, поэтому я опустил много кода, не связанного с Loader компонентом. Функции paginate и addWordAndCheckHeight , вероятно, выглядят довольно странно, но в основном у меня есть текст, который мне нужно поместить в переменные высоты <div> (страницы в книге), поэтому мне нужно добавить каждое слово и повторно измерить высоту <div> , чтобы увидеть, превышает ли оно ограничение по высоте, после чего оно переходит на новую страницу. Поскольку этот процесс может занять некоторое время в больших историях, я пытаюсь показать индикатор выполнения.

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

1. Было бы полезно посмотреть, какой процент увеличивается, и получаете ли вы такое же поведение журнала внутри !isNaN блока. Кроме того, !isNaN(null) принимает значение true, так что я не думаю, что это сработает так, как вы хотите.

2. Как percent обновляется, можете ли вы показать код?

3. Немного сложно определить, как оно увеличивается, но я знаю, что оно увеличивается правильно, поскольку журналы консоли из render() функции родительского компонента также показывают, что число увеличивается правильно. Но я все равно добавлю код. И приятно знать об этом isNaN , спасибо @DCTID

4. Я думаю, проблема может заключаться в том, что обновления пакетные, и ваш цикл слишком плотный, поэтому, возможно, недостаточно времени для отображения изменений. Попробуйте добавить задержку, это может сработать.

5. Хм, хорошо, я изучу это и опубликую, если у меня получится. Спасибо @CertainPerformance

Ответ №1:

Здесь у вас замкнутый цикл:

 for (let i in words) {
  const isLastWord = (parseInt(i) === (words.length - 1));
  await this.addWordAndCheckHeight(words[i], isLastWord, i);
}
  

потому что addWordAndCheckHeight не обязательно ждать, пока измененное состояние (и обновленное wordsRendered ) будет полностью отображено перед разрешением.

Вы можете добавить небольшую задержку внутри цикла:

 for (let i in words) {
  const isLastWord = (parseInt(i) === (words.length - 1));
  await this.addWordAndCheckHeight(words[i], isLastWord, i);
  await new Promise(resolve => setTimeout(resolve, 10));
}
  

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

Ответ №2:

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