直到for循环结束,ReactJS才会渲染

时间:2020-03-22 20:35:49

标签: javascript reactjs

重要!这不是异步API的问题! 我试图创建气泡排序可视化程序,并且在运行算法时 本身,我希望用户实际看到它的实际效果。 因此,每次我进行交换时,我都希望用户能够看到它的发生。 当bubbleSort内部的循环运行并更新状态ClassesArray时,什么都没有发生,直到循环完全结束。 如果我将break;放入循环中,则在循环停止时反应渲染。 问题是什么?我该如何解决?

编辑:

如果您有答案,请说明它的工作原理,以及如何在代码中实现它。

import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
import { render } from "@testing-library/react";
const SortingVisualizer = () => {
    const [getArray, setArray] = useState([]);
    const [getClasses, setClasses] = useState([""]);
    useEffect(() => {
        resetArray(200);
    }, []);
    useEffect(() => {}, [getArray, getClasses]);

    const resetArray = size => {
        const array = [];
        const classesArray = [];
        for (let i = 0; i < size; i++) {
            array.push(randomInt(20, 800));
            classesArray.push("array-bar");
        }
        setArray(array);
        setClasses(classesArray);
    };

    const bubbleSort = delay => {
        let temp,
            array = Object.assign([], getArray),
            classes = Object.assign([], getClasses);
        for (let i = array.length; i > 0; i--) {
            for (let j = 0; j < i - 1; j++) {
                classes[j] = "array-bar compared-bar";
                classes[j + 1] = "array-bar compared-bar";
                if (array[j] > array[j + 1]) {
                    temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
                //console.log((array.length - i) * 200 + (j + 1));
                setArray(array);
                setClasses(classes);
                break;
            }
        }
        console.log("done.");
    };
    return (
        <>
            <div className="menu">
                <button onClick={() => resetArray(200)}>Geneate new array</button>
                <button onClick={bubbleSort}>Do bubble sort</button>
            </div>
            <div className="array-container">
                {getArray.map((value, i) => (
                    <div
                        className={getClasses[i]}
                        key={i}
                        style={{ height: `${value * 0.1}vh` }}
                    ></div>
                ))}
            </div>
        </>
    );
};

function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;

3 个答案:

答案 0 :(得分:6)

React将尽可能关注性能。因此,例如,它将聚集一堆setState调用并分批处理它们,以防止不必要的渲染。适用于大多数Web应用程序,不适用于此类可视化。

您可以做的是等待渲染完成,然后再进入下一个状态。计时器和异步/等待将使它很容易阅读和完成。

// Returns a promise that resolves after given delay
const timer = (delay) => {
    return new Promise((resolve) => setTimeout(resolve, delay));
}

const bubbleSort = async(delay) => {
    let temp,
        array = Object.assign([], getArray),
        classes = Object.assign([], getClasses);
    for (let i = array.length; i > 0; i--) {
        for (let j = 0; j < i - 1; j++) {
            classes[j] = "array-bar compared-bar";
            classes[j + 1] = "array-bar compared-bar";
            if (array[j] > array[j + 1]) {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
            //console.log((array.length - i) * 200 + (j + 1));
            setArray(array);
            setClasses(classes);

            // Wait delay amount in ms before continuing, give browser time to render last update
            await timer(delay);
        }
    }
    console.log("done.");
};

更新

代码的下一部分需要更改的是修改数组的方式。当您调用setArray时,React会查看您传入的值,并进行浅表比较以查看其值是否与现有值不同。如果它们相同,它将忽略它并且不重新渲染。对于像String,Boolean和Number这样的原始数据类型,这不是问题。对于像对象,数组和函数这样的复杂数据类型,这是一个问题,因为它比较内存地址,而不是内容。

您的代码正在修改相同的数组,然后将其传递到setArray中,而setArray会发现它已经拥有相同的内存地址,而忽略了更新。

那为什么还要更新前两个呢?我真的不太确定。点击可能甚至告诉React应该重新渲染。

这是解决此问题的方法。确保每次都给// Create a new array and spread the old arrays contents out inside of it. // New memory address, same contents setArray([...array]); 一个新的内存地址,以便它触发重新渲染。您的另一种选择是使用强制渲染技术。

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.math.BigDecimal;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

public class CarsHire implements ActionListener, Runnable {
    private JButton exitButton;
    private JButton hireButton;
    private JButton receiptButton;
    private JComboBox<Car> carModelsCombo;
    private JComboBox<Integer> daysCombo;
    private JFrame frame;
    private JTextField emailTextField;
    private JTextField nameTextField;
    private JTextField phoneTextField;
    private JTextField priceTextField;

    @Override // java.awt.event.ActionListener
    public void actionPerformed(ActionEvent event) {
        Object src = event.getSource();
        if (exitButton == src) {
            System.exit(0);
        }
        else if (hireButton == src) {
            displayPrice();
        }
        else if (receiptButton == src) {
            System.out.println("====Receipt====");
            System.out.println("Name: " + t1.getText());
            System.out.println("Phone Number: " + t3.getText());
            System.out.println("Car Model: " + c1.getSelectedItem());
            System.out.println("Days: " + c2.getSelectedItem());
        }
    }

    @Override // java.lang.Runnable
    public void run() {
        createAndShowGui();
    }

    private void createAndShowGui() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(createHeaderPanel(), BorderLayout.PAGE_START);
        frame.add(createMainPanel(), BorderLayout.CENTER);
        frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JPanel createButtonsPanel() {
        JPanel buttonsPanel = new JPanel();
        receiptButton = new JButton("Print Receipt");
        receiptButton.setMnemonic(KeyEvent.VK_R);
        receiptButton.addActionListener(this);
        buttonsPanel.add(receiptButton);

        exitButton = new JButton("Exit");
        exitButton.setMnemonic(KeyEvent.VK_X);
        exitButton.addActionListener(this);
        buttonsPanel.add(exitButton);

        exitButton.setPreferredSize(receiptButton.getPreferredSize());

        return buttonsPanel;
    }

    private JPanel createHeaderPanel() {
        JPanel headerPanel = new JPanel();
        JLabel headerLabel = new JLabel("Car Rental");
        Font f = new Font("TimesRoman", Font.BOLD, 20);
        headerLabel.setFont(f);
        headerPanel.add(headerLabel);
        return headerPanel;
    }

    private JPanel createMainPanel() {
        JPanel mainPanel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.insets.bottom = 5;
        gbc.insets.left = 5;
        gbc.insets.right = 5;
        gbc.insets.top = 5;
        gbc.anchor = GridBagConstraints.LINE_START;

        // First row of form.
        JLabel nameLabel = new JLabel("Name");
        nameLabel.setDisplayedMnemonic(KeyEvent.VK_N);
        mainPanel.add(nameLabel, gbc);
        gbc.gridx = 1;
        nameTextField = new JTextField(10);
        mainPanel.add(nameTextField, gbc);
        nameLabel.setLabelFor(nameTextField);

        // Second row of form.
        gbc.gridx = 0;
        gbc.gridy = 1;
        JLabel emailLabel = new JLabel("Email");
        emailLabel.setDisplayedMnemonic(KeyEvent.VK_E);
        mainPanel.add(emailLabel, gbc);
        gbc.gridx = 1;
        emailTextField = new JTextField(10);
        mainPanel.add(emailTextField, gbc);
        emailLabel.setLabelFor(emailTextField);

        gbc.gridx = 0;
        gbc.gridy = 2;
        JLabel phoneLabel = new JLabel("Phone Number");
        phoneLabel.setDisplayedMnemonic(KeyEvent.VK_P);
        mainPanel.add(phoneLabel, gbc);
        gbc.gridx = 1;
        phoneTextField = new JTextField(10);
        mainPanel.add(phoneTextField, gbc);
        phoneLabel.setLabelFor(phoneTextField);

        gbc.gridx = 0;
        gbc.gridy = 3;
        JLabel carModelLabel = new JLabel("Car Model");
        carModelLabel.setDisplayedMnemonic(KeyEvent.VK_M);
        mainPanel.add(carModelLabel, gbc);
        gbc.gridx = 1;
        Car[] carModels = new Car[]{new Car("BMW", new BigDecimal(36295)),
                                    new Car("Mercedes", new BigDecimal(33795)),
                                    new Car("Audi", new BigDecimal(34295))};
        carModelsCombo = new JComboBox<>(carModels);
        carModelsCombo.setSelectedIndex(-1);
        mainPanel.add(carModelsCombo, gbc);
        carModelLabel.setLabelFor(carModelsCombo);

        gbc.gridx = 0;
        gbc.gridy = 4;
        JLabel daysLabel = new JLabel("Days");
        daysLabel.setDisplayedMnemonic(KeyEvent.VK_D);
        mainPanel.add(daysLabel, gbc);
        gbc.gridx = 1;
        daysCombo = new JComboBox<>(new Integer[]{1, 2, 3, 4, 5, 6, 7});
        daysCombo.setSelectedIndex(-1);
        mainPanel.add(daysCombo, gbc);
        daysLabel.setLabelFor(daysCombo);

        gbc.gridx = 0;
        gbc.gridy = 5;
        hireButton = new JButton("Hire");
        hireButton.setMnemonic(KeyEvent.VK_H);
        hireButton.addActionListener(this);
        mainPanel.add(hireButton, gbc);
        gbc.gridx = 1;
        priceTextField = new JTextField(10);
        mainPanel.add(priceTextField, gbc);

        return mainPanel;
    }

    private void displayPrice() {
        Car car = (Car) carModelsCombo.getSelectedItem();
        if (car != null) {
            BigDecimal price = car.getPrice();
            priceTextField.setText(price.toString());
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new CarsHire());
    }
}

class Car {
    private String model;
    private BigDecimal price;

    public Car(String model, BigDecimal price) {
        this.model = model;
        this.price = price;
    }

    public String getModel() {
        return model;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public String toString() {
        return model;
    }
}

答案 1 :(得分:1)

setState总是在组件状态发生变化时触发重新渲染。如果使用钩子,则将状态更新为与先前值相同的值不会导致重新渲染。因此,在您的情况下,为了显示更改,必须重新渲染。这是您正在寻找的工作演示。

https://stackblitz.com/edit/react-qaawgg?file=index.js

import React, { Component } from 'react';
import { render } from 'react-dom';

class App extends Component {
  constructor() {
    super();
    this.state = {
      arr: [],
      clsarr:[]
    };
  }
timeDelay = (delay) => {
    return new Promise((resolve) => setTimeout(resolve, delay));
}

     resetArray = (size)=> {
        const array = [];
        const classesArray = [];
        for (let i = 0; i < size; i++) {
            array.push(this.randomInt(20, 800));
            classesArray.push("array-bar");
        }
        this.setState({arr:array,clsarr:classesArray});
    };

    bubbleSort = async(delay) => {
        let temp,
            array = Object.assign([], this.state.arr),
            classes = Object.assign([], this.state.clsarr);
        for (let i = array.length; i > 0; i--) {
            for (let j = 0; j < i - 1; j++) {
                classes[j] = "array-bar compared-bar";
                classes[j + 1] = "array-bar compared-bar";
                if (array[j] > array[j + 1]) {
                    temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
                //console.log((array.length - i) * 200 + (j + 1));

                this.setState({arr:array,clsarr:classes})
                 await this.timeDelay(500);

            }
        }
        console.log("done.");
    };

    randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

  render() {
    return (
      <div>
            <div className="menu">
                <button onClick={() => this.resetArray(50)}>Geneate new array</button>
                <button onClick={()=>this.bubbleSort()}>Do bubble sort</button>
            </div>
            <div className="array-container">
                {this.state.arr.map((value, i) => (
                    <div
                        className={this.state.clsarr[i]}
                        key={i}
                    >{value}<br/></div>
                ))}
            </div></div>
    );
  }
}

render(<App />, document.getElementById('root'));

答案 2 :(得分:0)

也许取指令是异步的,但是一旦进入循环,它就会变为同步,一个64位每秒最多可以运行18,446,744,073,709,551,615(264-1)条指令,而不是人眼和屏幕能够看到的。是的,我看着维基百科找出了这个数字。