繁体   English   中英

为什么用过时的 state 调用 useSelector - React Redux

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM