Ik ben erg nieuw bij ReactJS (zoals in, ben net vandaag begonnen). Ik begrijp niet helemaal hoe setState
werkt. Ik combineer React en Easel JS om een raster te tekenen op basis van gebruikersinvoer. Hier is mijn JS-bak:
http://jsbin.com/zatula/edit?js,output
Hier is de code:
var stage;
var Grid = React.createClass({
getInitialState: function() {
return {
rows: 10,
cols: 10
}
},
componentDidMount: function () {
this.drawGrid();
},
drawGrid: function() {
stage = new createjs.Stage("canvas");
var rectangles = [];
var rectangle;
//Rows
for (var x = 0; x < this.state.rows; x++)
{
// Columns
for (var y = 0; y < this.state.cols; y++)
{
var color = "Green";
rectangle = new createjs.Shape();
rectangle.graphics.beginFill(color);
rectangle.graphics.drawRect(0, 0, 32, 44);
rectangle.x = x * 33;
rectangle.y = y * 45;
stage.addChild(rectangle);
var id = rectangle.x + "_" + rectangle.y;
rectangles[id] = rectangle;
}
}
stage.update();
},
updateNumRows: function(event) {
this.setState({ rows: event.target.value });
this.drawGrid();
},
updateNumCols: function(event) {
this.setState({ cols: event.target.value });
this.drawGrid();
},
render: function() {
return (
<div>
<div className="canvas-wrapper">
<canvas id="canvas" width="400" height="500"></canvas>
<p>Rows: { this.state.rows }</p>
<p>Columns: {this.state.cols }</p>
</div>
<div className="array-form">
<form>
<label>Number of Rows</label>
<select id="numRows" value={this.state.rows} onChange={ this.updateNumRows }>
<option value="1">1</option>
<option value="2">2</option>
<option value ="5">5</option>
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
</select>
<label>Number of Columns</label>
<select id="numCols" value={this.state.cols} onChange={ this.updateNumCols }>
<option value="1">1</option>
<option value="2">2</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
</select>
</form>
</div>
</div>
);
}
});
ReactDOM.render(
<Grid />,
document.getElementById("container")
);
U kunt in de JSbin zien dat wanneer u het aantal rijen of kolommen wijzigt met een van de vervolgkeuzemenu’s, er de eerste keer niets zal gebeuren. De volgende keer dat u een dropdown-waarde wijzigt, wordt het raster naar de rij- en kolomwaarden van de vorige staat getrokken. Ik vermoed dat dit gebeurt omdat mijn functie this.drawGrid()
wordt uitgevoerd voordat setState
is voltooid. Misschien is er een andere reden?
Bedankt voor je tijd en hulp!
Antwoord 1, autoriteit 100%
setState(updater[, callback])
is een asynchrone functie:
https://facebook.github.io/react/docs/ react-component.html#setstate
Je kunt een functie uitvoeren nadat setState klaar is met de tweede parameter callback
zoals:
this.setState({
someState: obj
}, () => {
this.afterSetStateFinished();
});
Hetzelfde kan worden gedaan met haken in de functionele component React:
https://github.com/ the-road-to-learn-react/use-state-with-callback#usage
Kijk naar useStateWithCallbackLazy:
import { useStateWithCallbackLazy } from 'use-state-with-callback';
const [count, setCount] = useStateWithCallbackLazy(0);
setCount(count + 1, () => {
afterSetCountFinished();
});
Antwoord 2, autoriteit 5%
render
wordt aangeroepen elke keer dat u setState
gebruikt om de component opnieuw te renderen als er wijzigingen zijn. Als je je aanroep naar drawGrid
daar verplaatst in plaats van het aan te roepen in je update*
-methoden, zou je geen probleem moeten hebben.
Als dat niet voor u werkt, is er ook een overbelasting van setState
die een callback als tweede parameter neemt. Daar zou je als laatste redmiddel van moeten kunnen profiteren.
Antwoord 3, autoriteit 3%
Door setState
een Promise
te laten retourneren
Naast het doorgeven van een callback
naar setState()
-methode, kunt u deze ook om een async
-functie wikkelen en de then()
methode — die in sommige gevallen een schonere code kan produceren:
(async () => new Promise(resolve => this.setState({dummy: true}), resolve)()
.then(() => { console.log('state:', this.state) });
En hier kun je nog een stap verder gaan en een herbruikbare setState
-functie maken die naar mijn mening beter is dan de bovenstaande versie:
const promiseState = async state =>
new Promise(resolve => this.setState(state, resolve));
promiseState({...})
.then(() => promiseState({...})
.then(() => {
... // other code
return promiseState({...});
})
.then(() => {...});
Dit werkt prima in React16.4, maar ik heb het nog niet getest in eerdere versies van React.
Het is ook de moeite waard om te vermelden dat het in de meeste – waarschijnlijk alle gevallen – beter is om uw callback-code in de componentDidUpdate
-methode te houden.
Antwoord 4, autoriteit 2%
wanneer nieuwe rekwisieten of toestanden worden ontvangen (zoals je setState
hier aanroept), zal React enkele functies aanroepen, die componentWillUpdate
en componentDidUpdate
voeg in jouw geval gewoon een functie componentDidUpdate
toe om this.drawGrid()
aan te roepen
hier is werkende code in JS Bin
zoals ik al zei, in de code, wordt componentDidUpdate
aangeroepen na this.setState(...)
dan gaat componentDidUpdate
inside this.drawGrid()
aanroepen
lees meer over component Levenscyclusin React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
Antwoord 5, autoriteit 2%
Met haken vanaf React 16.8 is het gemakkelijk om dit te doen met useEffect
Ik heb een CodeSandboxgemaakt om demonstreer dit.
useEffect(() => {
// code to be run when state variables in
// dependency array changes
}, [stateVariables, thatShould, triggerChange])
Kortom, useEffect
synchroniseert met statuswijzigingen en dit kan worden gebruikt om het canvas weer te geven
import React, { useState, useEffect, useRef } from "react";
import { Stage, Shape } from "@createjs/easeljs";
import "./styles.css";
export default function App() {
const [rows, setRows] = useState(10);
const [columns, setColumns] = useState(10);
let stage = useRef()
useEffect(() => {
stage.current = new Stage("canvas");
var rectangles = [];
var rectangle;
//Rows
for (var x = 0; x < rows; x++) {
// Columns
for (var y = 0; y < columns; y++) {
var color = "Green";
rectangle = new Shape();
rectangle.graphics.beginFill(color);
rectangle.graphics.drawRect(0, 0, 32, 44);
rectangle.x = y * 33;
rectangle.y = x * 45;
stage.current.addChild(rectangle);
var id = rectangle.x + "_" + rectangle.y;
rectangles[id] = rectangle;
}
}
stage.current.update();
}, [rows, columns]);
return (
<div>
<div className="canvas-wrapper">
<canvas id="canvas" width="400" height="300"></canvas>
<p>Rows: {rows}</p>
<p>Columns: {columns}</p>
</div>
<div className="array-form">
<form>
<label>Number of Rows</label>
<select
id="numRows"
value={rows}
onChange={(e) => setRows(e.target.value)}
>
{getOptions()}
</select>
<label>Number of Columns</label>
<select
id="numCols"
value={columns}
onChange={(e) => setColumns(e.target.value)}
>
{getOptions()}
</select>
</form>
</div>
</div>
);
}
const getOptions = () => {
const options = [1, 2, 5, 10, 12, 15, 20];
return (
<>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</>
);
};
Antwoord 6
Ik moest een functie uitvoeren na het updaten van de staat en niet bij elke update van de staat.
Mijn scenario:
const [state, setState] = useState({
matrix: Array(9).fill(null),
xIsNext: true,
});
...
...
setState({
matrix: squares,
xIsNext: !state.xIsNext,
})
sendUpdatedStateToServer(state);
Hier is sendUpdatedStateToServer()
de vereiste functie die moet worden uitgevoerd na het bijwerken van de status.
Ik wilde useEffect() niet gebruiken omdat ik sendUpdatedStateToServer()
niet wil uitvoeren na elke statusupdate.
Wat voor mij werkte:
const [state, setState] = useState({
matrix: Array(9).fill(null),
xIsNext: true,
});
...
...
const newObj = {
matrix: squares,
xIsNext: !state.xIsNext,
}
setState(newObj);
sendUpdatedStateToServer(newObj);
Ik heb zojuist een nieuw object gemaakt dat door de functie moet worden uitgevoerd na de statusupdates en heb het gewoon gebruikt. Hier blijft de setState-functie de status bijwerken en de sendUpdatedStateToServer()
ontvangt de bijgewerkte status, wat ik wilde.
Antwoord 7
Hier is een betere implementatie
import * as React from "react";
const randomString = () => Math.random().toString(36).substr(2, 9);
const useStateWithCallbackLazy = (initialValue) => {
const callbackRef = React.useRef(null);
const [state, setState] = React.useState({
value: initialValue,
revision: randomString(),
});
/**
* React.useEffect() hook is not called when setState() method is invoked with same value(as the current one)
* Hence as a workaround, another state variable is used to manually retrigger the callback
* Note: This is useful when your callback is resolving a promise or something and you have to call it after the state update(even if UI stays the same)
*/
React.useEffect(() => {
if (callbackRef.current) {
callbackRef.current(state.value);
callbackRef.current = null;
}
}, [state.revision, state.value]);
const setValueWithCallback = React.useCallback((newValue, callback) => {
callbackRef.current = callback;
return setState({
value: newValue,
// Note: even if newValue is same as the previous value, this random string will re-trigger useEffect()
// This is intentional
revision: randomString(),
});
}, []);
return [state.value, setValueWithCallback];
};
Gebruik:
const [count, setCount] = useStateWithCallbackLazy(0);
setCount(count + 1, () => {
afterSetCountFinished();
});