reactjs - React + PIXI + Zustand - Παλαιά κατάσταση στους χειριστές ποντικιού


Έχω ένα καμβά Pixi με χειριστές δείκτη. Μπορείτε να κάνετε κλικ σε ένα σημείο σε μια «ακολουθία» και να το μετακινήσετε.

Οι χειριστές του ποντικιού είχαν παλιό κατάσταση. Για να το διορθώσω, αναδημιουργώ τους χειριστές κάθε φορά που τροποποιείται η σειρά, ώστε να έχουν πάντα φρέσκια κατάσταση. Αυτό διόρθωσε το σφάλμα κατάστασης. Αλλά δεν δουλεύει αν η κατάσταση τροποποιηθεί κατά την απελευθέρωση του ποντικιού. Η δημιουργία νέων χειριστών μέσα σε έναν χειριστή κατανοητά διατηρεί την παλιά κατάσταση, ακόμη και μέσω χρονοδιακόπτη αναδρομής. Έτσι τώρα όλα τα σφάλματα έχουν διορθωθεί εκτός από το ότι δεν μπορείτε να σύρετε ένα σημείο δύο φορές :)

Είμαι νέος στο React και νιώθω ότι τα κάνω όλα λάθος. Έχετε κάποιες προτάσεις;

Έτσι παίρνω την κατάσταση ακολουθίας:

const SequenceCanvas = React.forwardRef((props: any, ref) => {
    const canvasRef = useRef(null)

    const sequence: Sequence = useSequenceStore(state => { return state.sequence} )

Εδώ είναι τώρα που επαναδημιουργώ τους χειριστές:

const addHandlers = () : void => {
    console.log(`new mouse handlers: numsteps ${sequence.numSteps}`)
    parentContainer.removeAllListeners()
    parentContainer.on('pointerdown', (e: any) => {
        handlePointerDown(e)
    })
    parentContainer.on('dblclick', (e: any) => {
        handleDoubleClick(e)
    })
    parentContainer.on('pointerup', (e: any) => {
        handlePointerUp(e)
    })
    parentContainer.on('mouseupoutside', (e: any) => {
        handlePointerUp(e)
    })
    parentContainer.on('pointermove', (e: any) => {
        handlePointerMove(e)
    })
}

useEffect(() => {
    if (!mouseIsDown) {
        // Recreate mouse handlers each time the sequence changes. Otherwise the sequence state is stale
        addHandlers()
    }
}, [sequence]);

const handlePointerUp = (e: any) => {
    console.log(`handlePointerUp ${mouseIsDown}`)
    mouseIsDown = false;
    dragStepNum = -1
    isDraggingVertical = false;
    isDraggingHorizontal = false;

    setTimeout(() => {
        addHandlers()
    }, 100)
}

Εδώ είναι ένα παράδειγμα παρωχημένης κατάστασης. Το αντικείμενο sequence εδώ είναι παρωχημένο εκτός αν επαναδημιουργήσω τον χειριστή δείκτη κάθε φορά που αλλάζει η ακολουθία.

const handlePointerMoveImp = (e: any) => {
    const sequence = useSequenceStore.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.EnvelopePoint) {
                console.log(`handlePointerMoveImp - drag target is EnvelopePoint - tapPointNum ${tapPointNum}`)
                const midiOutputDeviceName : string = sequence.midiSettings.midiOutputDeviceName
                const midiChart:MidiChart = findMidiChart(midiOutputDeviceName, midiMaps)
                const envelope = findEnvelope(sequence.currentEnvelopeId);
                if (envelope) {
                    // const point = envelope.points[tapPointNum]
                    const controllerInfo: ControllerInfo = MidiDeviceDataService.getControllerInfo(midiChart, envelope.controller)
                    const time = Math.max(getXTime(sequence, x), 0)
                    const value: number = getYValue(sequence, y, controllerInfo.min, controllerInfo.max)
                    console.log(`handlePointerMoveImp - drag EnvelopePoint to ${time}/${value} - envelope <${sequence.currentEnvelopeId}>`)
                    moveEnvelopePoint(sequence.currentEnvelopeId, tapPointNum, time, value)
                }
            }

Απάντηση #1

Η ξεπερασμένη κατάσταση React στους χειριστές κλήσεων συνήθως διορθώνεται αποθηκεύοντας ένα αντίγραφο της τρέχουσας κατάστασης σε μια αναφορά React και αναφερόμενοι στην τρέχουσα τιμή της αναφοράς στις κλήσεις. Δεν είμαι πολύ εξοικειωμένος με το zustand αλλά νομίζω ότι κάτι σαν το παρακάτω θα πρέπει να είναι πολύ κοντά σε αυτό που ψάχνετε.

const SequenceCanvas = React.forwardRef((props: any, ref) => {
  const canvasRef = useRef(null);

  const sequence: Sequence = useSequenceStore(state => state.sequence);
  const sequenceRef = useRef<Sequence>(sequence);

  useEffect(() => {
    sequenceRef.current = sequence;
  }, [sequence]);

...

  useEffect(() => {
    parentContainer.on('pointerdown', handlePointerDown);
    parentContainer.on('dblclick', handleDoubleClick);
    parentContainer.on('pointerup', handlePointerUp);
    parentContainer.on('mouseupoutside', handlePointerUp);
    parentContainer.on('pointermove', handlePointerMove);

    return () => {
      parentContainer.removeAllListeners()
    };
  }, []);

...

  const handlePointerUp = (e: any) => {
    console.log(`handlePointerUp ${mouseIsDown}`)
    mouseIsDown = false;
    dragStepNum = -1
    isDraggingVertical = false;
    isDraggingHorizontal = false;
  }

...

  const handlePointerMoveImp = (e: any) => {
    const sequence = sequenceRef.current;

    if (mouseIsDown) {
      const { x, y } = e.data.global.x;

      if (x !== null && y !== null && x !== Infinity && y !== Infinity) {
        if (dragTarget === DragTargets.EnvelopePoint) {
          const midiOutputDeviceName : string = sequence.midiSettings.midiOutputDeviceName
          const midiChart:MidiChart = findMidiChart(midiOutputDeviceName, midiMaps)
          const envelope = findEnvelope(sequence.currentEnvelopeId);
          if (envelope) {
            const controllerInfo: ControllerInfo = MidiDeviceDataService.getControllerInfo(midiChart, envelope.controller)
            const time = Math.max(getXTime(sequence, x), 0)
            const value: number = getYValue(sequence, y, controllerInfo.min, controllerInfo.max)
            moveEnvelopePoint(sequence.currentEnvelopeId, tapPointNum, time, value)
          }
        }
      }
    }
  }