[英]Why is useSelector is called with outdated state - React Redux
我有一个组件,它绘制从我的 redux 商店获取的序列:
const sequence = useSelector(state => state.sequence)
每当“序列”更新时,都会触发我执行 canvas 绘制的 useEffect 调用:
useEffect(() => {
store.subscribe(() => {
console.log(`sequence-canvas: store state is now ${JSON.stringify(store.getState())}`)
drawSequenceCanvas(sequence)
});
}, [sequence])
但由于某种原因,useSelector 填充的“序列”位于我的商店 state 后面。 如果我绘制序列而不是商店 state 它会绘制正确的 state:
useEffect(() => {
store.subscribe(() => {
console.log(`sequence-canvas: store state is now ${JSON.stringify(store.getState())}`)
drawSequenceCanvas(store.getState().sequence)
});
}, [sequence])
我不明白为什么我需要在这里使用商店 state 而不是选择器。 我不明白什么?
这是完整的模块:
import React, { useEffect, useRef } from 'react';
import store from '../app/store'
import { useSelector } from "react-redux";
import * as PIXI from 'pixi.js'
import SongPlayer from "../player/song-player";
Math.minmax = (value, min, max) => Math.min(Math.max(value, min), max);
var pixiapp;
var container;
var parentContainer;
var renderer;
var height;
var width;
var mouseIsDown = false;
var isDraggingVertical = false
var isDraggingHorizontal = false
const dragThresholdVertical = 10
const dragThresholdHorizontal = 10
const grey = 0x404040
const darkgrey = 0x707070
const green = 0x10a010
const brightRed = 0xdb5660
const brightGreen = 0x56db60
const blue = 0x0000ff
const borderColor = 0x1e1e1e
const envelopeLineColor = 0xff0000
const backgroundColor = 0x28282b
const darkgreen = 0x107010
const red = 0xff0000
const white = 0xffffff;
const colorVelocityBar = 0x5e5f68;
const middleNote = 60;
const minNote = middleNote - 24;
const maxNote = middleNote + 24;
const borderWidth = 10;
const bottomBorderHeight = 20;
const topBorderHeight = 20;
const gapWidth = 4;
const barWidth = 50;
const numSteps = 16;
const lineThickness = 0.005;
const DragTargets = {
None: 0,
NoteBox: 1,
OctaveBox: 2,
VelocityBox: 3,
}
const DragDirections = {
None: 0,
Vertical: 1,
Horizontal: 2,
}
var dragTarget
var dragDirection
var dragStepNum = -1
var dragStartPos = { x: -1, y: -1, }
const noteBarTextStyle = new PIXI.TextStyle({
align: "center",
fill: "#000000",
fontFamily: "Helvetica",
fontSize: 12
});
const turnAKnobTextStyle = new PIXI.TextStyle({
align: "center",
fill: "#000000",
fontFamily: "Helvetica",
fontSize: 36,
});
const noteName = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
]
function getControllerValueTop(value, minValue, maxValue) {
// return bottomBorderHeight + (value - minvalue) / (maxValue - minvalue) * (height - bottomBorderHeight - topBorderHeight) / 127
return height - bottomBorderHeight - (value - minValue) / (maxValue - minValue) * (height - bottomBorderHeight - topBorderHeight);
}
function getTimeX(sequence, time) {
const maxTime = sequence.steps.length * sequence.tempo / 60 / sequence.division
// console.log(`maxTime ${maxTime} sequence.steps.length ${sequence.steps.length} sequence.tempo ${sequence.tempo} sequence.division ${sequence.division}`)
const timex = borderWidth + time / maxTime * (width - borderWidth - borderWidth)
// console.log(`time ${time} timex ${timex} borderWidth ${borderWidth} maxTime ${maxTime} width ${width}`)
return timex
}
// function getXTime(sequence, x) {
// const maxTime = sequence.steps * sequence.tempo / 60 / sequence.division
// return borderWidth + x / (width - borderWidth - borderWidth) * maxTime
// }
function getNoteTop(note) {
return height - bottomBorderHeight - (note + 12 - minNote) / (maxNote - minNote + 12) * (height - bottomBorderHeight - topBorderHeight);
}
function getNoteBottom(note) {
return getNoteTop(note - 12) + lineThickness;
// return bottomBorderHeight - lineThickness + (note - minNote) / (maxNote - minNote + 12) * (1 - bottomBorderHeight - topBorderHeight);
}
function getNoteHeight() {
return (height - bottomBorderHeight - topBorderHeight) * 12 / (maxNote - minNote + 12);
}
function getStepLeft(sequence, stepNum) {
var stride = getStepStride(sequence)
// console.log(`getStepLeft: borderWidth ${borderWidth} num steps ${sequence.steps.length} stride ${stride}`)
return borderWidth + stepNum * stride;
}
function getStepRight(sequence, stepNum) {
var stride = (width - (borderWidth * 2)) / sequence.steps.length;
var step = sequence.steps[stepNum];
return getStepLeft(sequence, stepNum) + stride * step.gateLength - lineThickness;
}
function getStepStride(sequence) {
var stride = (width - (borderWidth * 2)) / sequence.steps.length;
// console.log(`getStepStride: ${sequence.numSteps} -> ${stride}`)
return stride
}
function getStepWidth(sequence, stepNum) {
var stride = getStepStride(sequence);
var step = sequence.steps[stepNum];
return stride * step.gateLength;
}
function drawSequenceCanvas(sequence) {
const sequencePlayer = SongPlayer.getSequencePlayer(sequence._id)
// console.log(`draw: sequence player is ${JSON.stringify(sequencePlayer)}`)
const stepnum = sequencePlayer ? sequencePlayer.stepNum : -1;
// console.log(`stepnum ${stepnum}`)
if (container) {
container.destroy({ children: true })
}
container = new PIXI.Container();
parentContainer.addChild(container);
// Background
var bar = new PIXI.Graphics();
// bar.lineStyle(2, 0x00FFFF, 1);
bar.beginFill(backgroundColor, 1);
bar.drawRect(0, 0, width, height);
bar.endFill();
container.addChild(bar);
// Top border
bar = new PIXI.Graphics();
bar.beginFill(borderColor, 1);
bar.drawRect(0, 0, width, topBorderHeight, 16);
bar.endFill();
container.addChild(bar);
// Bottom border
bar = new PIXI.Graphics();
bar.beginFill(borderColor, 1);
bar.drawRect(0, height-bottomBorderHeight, width, bottomBorderHeight);
bar.endFill();
container.addChild(bar);
// Sides
bar = new PIXI.Graphics();
bar.beginFill(borderColor, 1);
bar.drawRect(0, topBorderHeight, borderWidth, height - bottomBorderHeight - topBorderHeight);
bar.drawRect(width - borderWidth, topBorderHeight, borderWidth, height - bottomBorderHeight - topBorderHeight);
bar.endFill();
container.addChild(bar);
// Velocity bars
var stepNum = 0;
var barColor = colorVelocityBar;
sequence.steps.forEach(step => {
const bar = new PIXI.Graphics();
// bar.lineStyle(2, 0xFF00FF, 1);
bar.beginFill(barColor, 1);
var barHeight = height - bottomBorderHeight - getControllerValueTop(step.velocity, 0, 127);
bar.drawRect(getStepLeft(sequence, stepNum), height - bottomBorderHeight - barHeight, getStepWidth(sequence, stepNum), barHeight);
bar.endFill();
container.addChild(bar);
++stepNum;
})
// Horizontal grid lines
const lineColor = borderColor;
var lines = new PIXI.Graphics();
// console.log(`minnote ${minNote} ${getNoteTop(minNote)} ${getNoteTop(minNote - 12)}`)
lines.position.set(borderWidth, getNoteTop(minNote - 12));
lines.lineStyle(2, lineColor, 1)
.moveTo(borderWidth, getNoteTop(minNote - 12))
.lineTo(width - borderWidth, getNoteTop(minNote - 12))
.moveTo(borderWidth, getNoteTop(minNote))
.lineTo(width - borderWidth, getNoteTop(minNote))
.moveTo(borderWidth, getNoteTop(minNote + 12))
.lineTo(width - borderWidth, getNoteTop(minNote + 12))
.moveTo(borderWidth, getNoteTop(minNote + 24))
.lineTo(width - borderWidth, getNoteTop(minNote + 24))
.moveTo(borderWidth, getNoteTop(minNote + 36))
.lineTo(width - borderWidth, getNoteTop(minNote + 36))
.moveTo(borderWidth, getNoteTop(minNote + 48))
.lineTo(width - borderWidth, getNoteTop(minNote + 48))
container.addChild(lines);
// Note bars
stepNum = 0;
console.log(`SequenceCanvas.draw - note bars - ${JSON.stringify(sequence.steps)}`);
sequence.steps.forEach(step => {
const bar = new PIXI.Graphics();
// bar.lineStyle(2, 0xFF00FF, 1);
barColor = sequencePlayer && stepNum === sequencePlayer.stepNum ? brightRed : brightGreen;
bar.beginFill(barColor, 1);
var barHeight = height - bottomBorderHeight - getControllerValueTop(step.velocity, 0, 127);
bar.drawRoundedRect(getStepLeft(sequence, stepNum), getNoteTop(step.note), getStepWidth(sequence, stepNum), getNoteHeight(), 8);
bar.endFill();
container.addChild(bar);
const noteText = new PIXI.Text(noteName[step.note % 12], noteBarTextStyle)
noteText.anchor.set(0.5);
// noteText.width = getStepStride(sequence)
noteText.x = getStepLeft(sequence, stepNum) + getStepWidth(sequence, stepNum) / 2
noteText.y = getNoteTop(step.note) + getNoteHeight() / 2
container.addChild(noteText)
++stepNum;
})
// Bottom bars
stepNum = 0;
barColor = green;
// console.log(`SequenceCanvas.Init ${JSON.stringify(sequence.steps)}`);
sequence.steps.forEach(step => {
const bar = new PIXI.Graphics();
// bar.lineStyle(2, 0xFF00FF, 1);
bar.beginFill(0x707481, 1);
bar.drawRoundedRect(getStepLeft(sequence, stepNum), height - bottomBorderHeight, getStepWidth(sequence, stepNum), bottomBorderHeight, 4);
bar.endFill();
container.addChild(bar);
const noteText = new PIXI.Text((step.note % 12), noteBarTextStyle)
noteText.anchor.set(0.5);
// noteText.width = getStepStride(sequence)
noteText.x = getStepLeft(sequence, stepNum) + getStepStride(sequence) / 2
noteText.y = height - bottomBorderHeight + 10
container.addChild(noteText)
++stepNum;
})
if (sequence.currentEnvelopeId != 'notes') {
DrawEnvelopes(container, sequence)
}
// // Random box for testing
// bar = new PIXI.Graphics();
// bar.lineStyle(2, 0x00FFFF, 1);
// bar.beginFill(0x650A5A, 0.25);
// bar.drawRoundedRect(height, getNoteTop(minNote), 50, getNoteHeight(minNote), 16);
// bar.endFill();
// container.addChild(bar);
}
function DrawEnvelopes(container, sequence) {
if (sequence.currentEnvelopeId) {
console.log(`DrawEnvelopes: ${sequence.currentEnvelopeId}`)
if (sequence.currentEnvelopeId == "notes") {
// don't draw envelopes
} else if (sequence.currentEnvelopeId == "new" || sequence.currentEnvelopeId == "learn") {
console.log(`draw warning`)
const bar = new PIXI.Graphics();
bar.beginFill(white, 0.5);
bar.drawRect(0, 0, width, height);
bar.endFill();
container.addChild(bar);
const turnAKnobText = new PIXI.Text("Turn a knob to map", turnAKnobTextStyle)
turnAKnobText.anchor.set(0.5);
turnAKnobText.width = width / 2
turnAKnobText.x = width / 2
turnAKnobText.y = height / 2
container.addChild(turnAKnobText)
} else {
DrawEnvelope(container, sequence, sequence.currentEnvelopeId)
}
}
}
function DrawEnvelope(container, sequence, envelopeId) {
console.log(`DrawEnvelope: ${envelopeId} ${JSON.stringify(sequence)}`)
const envelope = sequence.envelopes[envelopeId]
if (!envelope) {
return
}
console.log(`DrawEnvelope: ${envelopeId} - envelope.controller ${JSON.stringify(envelope.controller)}`)
if (envelope.controller !== "notes") {
console.log(`DrawEnvelope: ${envelope.controller}`)
if (envelope.controller == null) {
} else {
const points = envelope.points
// console.log(`DrawEnvelope: We have ${envelope.controllers.length} controllers with points ${JSON.stringify(points)}`)
// console.log(`DrawEnvelope: point 0 ${JSON.stringify(points[0])}`)
// Lines
const lineColor = envelopeLineColor;
var lines = new PIXI.Graphics();
lines.position.set(0, 0);
lines.lineStyle(1, lineColor, 1)
var y = getControllerValueTop(points[0].value, 0, 127)
lines.moveTo(0, y)
for (const [pointTime, point] of Object.entries(points)) {
var x = getTimeX(sequence, point.time)
y = getControllerValueTop(point.value, 0, 127)
// console.log(`point ${JSON.stringify(point)} draw line to time ${point.time} value ${point.value} ${x},${y}`)
lines.lineTo(x, y)
}
lines.lineTo(width, y)
// Dots
lines.fillStyle = { color: lineColor, alpha: 0.5, visible: true }
lines.beginFill(lineColor)
for (const [pointTime, point] of Object.entries(points)) {
var x = getTimeX(sequence, point.time)
y = getControllerValueTop(point.value, 0, 127)
// console.log(`point ${JSON.stringify(point)} draw line to time ${point.time} value ${point.value} ${x},${y}`)
lines.drawCircle(x, y, 4)
}
container.addChild(lines);
}
}
}
const handlePointerDown = e => {
const sequence = store.getState().sequence
console.log(`handlePointerDown ${mouseIsDown} ${JSON.stringify(e.data)}`)
mouseIsDown = true;
var x = e.data.global.x
var y = e.data.global.y
dragStartPos = { x: x, y: y, }
dragStepNum = -1
for (var stepNum = 0; stepNum < sequence.steps.length; stepNum++) {
var stepleft = getStepLeft(sequence, stepNum);
if (x > stepleft && x < getStepRight(sequence, stepNum)) {
dragStepNum = stepNum
console.log(`tapped step ${dragStepNum}`)
break
}
}
console.log(`handlePointerDown: ${x},${y} dragStepNum ${dragStepNum} target ${dragTarget}`)
console.log(`handlePointerDown: step ${JSON.stringify(sequence.steps[stepNum])}`)
if (dragStartPos.y > height - bottomBorderHeight) {
dragTarget = DragTargets.NoteBox
} else if (dragStartPos.y < getNoteBottom(sequence.steps[stepNum].note) && dragStartPos.y > getNoteTop(sequence.steps[stepNum].note)) {
dragTarget = DragTargets.OctaveBox
} else if (dragStartPos.y > bottomBorderHeight && dragStartPos.y < height - topBorderHeight) {
console.log(`drag velocity box y = ${dragStartPos.y}`)
dragTarget = DragTargets.VelocityBox
}
console.log(`handlePointerDown: step ${dragStepNum} target ${dragTarget}`)
}
const handlePointerUp = e => {
const sequence = store.getState().sequence
console.log(`handlePointerUp ${mouseIsDown}`)
mouseIsDown = false;
isDraggingVertical = false;
isDraggingHorizontal = false;
}
const handlePointerMove = e => {
const sequence = store.getState().sequence
if (mouseIsDown) {
var x = e.data.global.x
var y = e.data.global.y
if (x !== null && y !== null && x !== Infinity && y !== Infinity) {
// console.log(`drag ${JSON.stringify(e.data)}`)
if (dragTarget === DragTargets.OctaveBox) {
// Detect direction before we start editing
if (!isDraggingVertical && !isDraggingHorizontal) {
if (Math.abs(y - dragStartPos.y) > dragThresholdVertical) {
isDraggingVertical = true;
}
else if (Math.abs(x - dragStartPos.x) > dragThresholdHorizontal) {
isDraggingHorizontal = true;
}
else {
return
}
}
if (isDraggingVertical) {
console.log(`drag note: ${sequence.steps[dragStepNum].note} ${x},${y} `)
const notenum = sequence.steps[dragStepNum].note
if (y < getNoteTop(notenum)) {
console.log(`drag y ${y} < ${getNoteTop(notenum)} getNoteTop note for note ${notenum}, dragStepNum ${dragStepNum}`)
store.dispatch({type: "sequence/stepNote", payload: {stepNum: dragStepNum, note: notenum + 12}})
} else if (y > getNoteBottom(notenum)) {
console.log(`drag y ${y} > ${getNoteBottom(notenum)} getNoteBottom for note ${notenum}, dragStepNum ${dragStepNum}`)
store.dispatch({type: "sequence/stepNote", payload: {stepNum: dragStepNum, note: notenum - 12}})
}
}
else {
var stride = getStepStride(sequence);
// note.gateLength
// x = Math.minmix(x, getStepLeft(sequence, dragStepNum), getStepRight(sequence, dragStepNum))
var newGateLength = (x - getStepLeft(sequence, dragStepNum)) / getStepStride(sequence)
if (x < getStepLeft(sequence, dragStepNum)) {
newGateLength = 0;
} else if (x > getStepLeft(sequence, dragStepNum) + getStepStride(sequence)) {
newGateLength = 1.0
}
store.dispatch({
type: "sequence/stepGateLength",
payload: {stepNum: dragStepNum, gateLength: newGateLength}
})
}
}
else if (dragTarget === DragTargets.NoteBox) {
const notenum = sequence.steps[dragStepNum].note
if (y - dragStartPos.y > bottomBorderHeight) {
store.dispatch({ type: "sequence/stepNote", payload: { stepNum: dragStepNum, note: notenum - 1 } })
dragStartPos.y = y
} else if (y - dragStartPos.y < -bottomBorderHeight) {
store.dispatch({ type: "sequence/stepNote", payload: { stepNum: dragStepNum, note: notenum + 1 } })
dragStartPos.y = y
}
}
else if (dragTarget === DragTargets.VelocityBox) {
const velocity = sequence.steps[dragStepNum].velocity
y = Math.minmax(y, topBorderHeight, height - bottomBorderHeight)
const newVelocity = 127 - (127 * (y - topBorderHeight) / (height - topBorderHeight - bottomBorderHeight))
console.log(`drag velocitybox ${y} => ${newVelocity}`)
store.dispatch({ type: "sequence/stepVelocity", payload: { stepNum: dragStepNum, velocity: newVelocity } })
}
}
}
}
function SequenceCanvas(props) {
console.log(`hi from SequenceCanvas ${JSON.stringify(props)}`)
// The React way to get a dom element is by giving it a ref prop
const canvasRef = useRef(null)
const sequence = useSelector(state => state.sequence)
useEffect( () => {
// console.log(`sequence-canvas: store state is now ${JSON.stringify(store.getState())}`)
width = props.width;
height = props.height;
console.log("new PIXI.Application")
pixiapp = new PIXI.Application({
view: canvasRef.current,
powerPreference: 'high-performance',
backgroundAlpha: false,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
width: width,
height: height
});
renderer = PIXI.autoDetectRenderer();
parentContainer = new PIXI.Container();
parentContainer.interactive = true
parentContainer.on('pointerdown', (e) => { handlePointerDown(e) })
parentContainer.on('pointerup', (e) => { handlePointerUp(e) })
parentContainer.on('mouseupoutside', (e) => { handlePointerUp(e) })
parentContainer.on('pointermove', (e) => { handlePointerMove(e) })
pixiapp.stage.addChild(parentContainer);
drawSequenceCanvas(sequence)
return () => {
parentContainer.removeAllListeners()
}
}, [])
useEffect(() => {
store.subscribe(() => {
console.log(`sequence-canvas: store state is now ${JSON.stringify(store.getState())}`)
drawSequenceCanvas(store.getState().sequence)
});
}, [sequence])
const timerIdRef = useRef(0)
function handleInterval() {
console.log(`hi from sequenceCanvas.IntervalTimer`)
drawSequenceCanvas(sequence)
}
const startIntervalTimer = () => {
console.log(`SequenceCanvas.startIntervalTimer - timerIdRef.current === ${timerIdRef.current}`)
if (timerIdRef.current == 0) {
timerIdRef.current = setInterval(() => {
handleInterval()
})
}
}
const stopIntervalTimer = () => {
console.log(`SequenceCanvas.stopIntervalTimer`)
clearInterval(timerIdRef.current)
timerIdRef.current = 0
}
useEffect(() => {
console.log(`startIntervalTime from useEffect`)
// startIntervalTimer();
return () => clearInterval(timerIdRef.current);
}, []);
return (
<div>
<div>
<canvas {...props} ref={canvasRef}>
Your browser does not support the HTML canvas tag.
</canvas>
</div>
</div>
);
}
// export {SequenceCanvas, drawSequenceCanvas};
export {SequenceCanvas};
每次更改该sequence
时-您在 useEffect 挂钩上创建新的商店订阅-越来越多。 你可以让它像这样工作:
const sequence = useSelector(state => state.sequence); const sequenceRef = useRef(sequence); sequenceRef.current = sequence; useEffect(() => { return store.subscribe(() => { // <--- return will unsubscribe from store drawSequenceCanvas(sequenceRef.current); // <---actual link }); }, []);
或者你可以这样做
const sequence = useSelector(state => state.sequence); useEffect(() => { return store.subscribe(() => { // <--- return will unsubscribe from store drawSequenceCanvas(sequence); }); }, [sequence]);
但是 - useSelector 钩子已经使用了商店订阅 - 所以你可以这样做:
const sequence = useSelector(state => state.sequence); useEffect(() => { drawSequenceCanvas(sequence); }, [sequence]); // OR with usePrevious hook - https://usehooks.com/usePrevious/ const sequence = useSelector(state => state.sequence); const prevSequence = usePrevious(sequence); if (sequence;== prevSequence) { drawSequenceCanvas(sequence); }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.