重要!这不是异步API的问题!
我试图创建气泡排序可视化程序,并且在运行算法时
本身,我希望用户实际看到它的实际效果。
因此,每次我进行交换时,我都希望用户能够看到它的发生。
当bubbleSort
内部的循环运行并更新状态Classes
和Array
时,什么都没有发生,直到循环完全结束。
如果我将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;
答案 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)条指令,而不是人眼和屏幕能够看到的。是的,我看着维基百科找出了这个数字。