React 组件在重新保存文件之前不会更改状态,然后同时运行两者

问题描述 投票:0回答:1

我有一个 React 组件钢琴,它工作得很好,直到我像我应该的那样,从 return 语句到 useEffect 中搞乱了钢琴的创建。现在,当我设置任何状态(特别是我在 useEffect 中执行此操作),然后几秒钟后将其打印出来时,它处于默认状态。如果我随后对组件进行更改并保存,它会热刷新,然后当我打印出状态时,一份打印来自旧行,该行仍打印为空,一份打印来自新行,该行打印预期的新设置状态。通过新行和旧行,我的意思是,如果我检查来自开发工具的打印,一个会引导我到旧文件,另一个会引导我到新文件。我有薛定谔的组件...我将转储整个组件代码,但重要的 3 行是: 在第 69 行初始化测试状态,在第 112 行将 test 设置为“hello”,并在第 135 行按键时打印测试。下面是 devtools 中的打印结果 (https://i.stack.imgur.com/Lxlb2.png)

钢琴组件

import React, { useEffect, useRef, useState } from 'react'
import PianoKey from './PianoKey'
import { Howl } from 'howler'
import './Piano.css'
import { attribute_animation, default_ajax } from '../../utility/CommonFunctions'
import { FaXmark } from "react-icons/fa6";
import { MdOutlineSaveAlt } from "react-icons/md";
import { TbShare2 } from "react-icons/tb";
import { IoMdCheckmark } from "react-icons/io";
import { FaPlay, FaPause } from "react-icons/fa";



const Piano = ({ start=12, end=60, type='', set_product=null, visible=true, user_interact=true, song_prop=null }) => {
  
  // Recording START
  let [recording, set_recording] = useState([false, 0])
  let [recorded_song, set_recorded_song] = useState([[], []])
  let [save_prompt, set_save_prompt] = useState([false, false])

  let recording_action = (action) => {
    if (action === 'start') {
      set_recording(prev_state => {
        let rec_arr = [...prev_state]
        rec_arr[0] = true
        return rec_arr
      })

      console.log("recording")
    } else if (action === 'end') {
      console.log("stopped recording")

      // if second recording
      if (recording[1] === 1) {

        if (type == 'register') {
          set_product(recorded_song)
        }
        set_recorded_song([[], []])
      } else {
        if (type == 'login') {
          set_product(recorded_song[0])
          set_recorded_song([[], []])
        } else if (type == 'register') {
          set_recording([false, 1])
        } else {
          set_recording([false, 0])
          set_save_prompt(prev_state => [true, false])
        }
        
      }
    }
  }
  let clear_song = () => {
    set_recorded_song([], [])
    set_recording([false, 0])
  }
  // Recording END

  // Create Piano START
  

  let piano_keys_ref = useRef({})

  let [pressed_keys, set_pressed_keys] = useState(Array(piano_keys_ref.current.length).fill(false))
  let [playback_visual, set_playback_visual] = useState(Array(piano_keys_ref.current.length).fill(''))
  let [key_to_key, set_key_to_key] = useState({})
  let [piano, set_piano] = useState([])
  let [test, set_test] = useState ()


  useEffect(() => {
    // create piano
    // let notes = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
    // let keyboard_keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 
    //                   'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 
    //                   'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", 'enter',
    //                   'l_shft', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'r_shft']
    
    // for (let i = start; i < end; i++) {

    //   if (i-start >= keyboard_keys.length)
    //     break

    //   let note = notes[(i % 12)] + (1+Math.floor(i / 12)).toString()

    //   set_piano(prev_state => {
    //     const new_piano = [
    //       ...prev_state,
    //       <PianoKey 
    //         note={note} 
    //         color={notes[(i % 12)].length === 1 ? 'white' : 'black'} 
    //         keyboard_key={keyboard_keys[i-start]}
    //         key={note}
    //         innerRef={ref => piano_keys_ref.current[i] = ref}
    //         recorded_song={recorded_song}
    //         set_recorded_song={set_recorded_song}
    //         recording={recording}
    //         pressed={pressed_keys[i]}
    //         user_interact={user_interact}
    //         playback_visual={playback_visual[i-start]}
    //       />
    //     ]

    //     // set_key_to_key(prev_state => ({
    //     //   ...prev_state,
    //     //   [keyboard_keys[i-start]]: i-start
    //     // }))
    //     return new_piano;
    //   });
    // 
    set_test('hello')

    return () => {
      set_key_to_key({})
      set_pressed_keys(Array(piano_keys_ref.current.length).fill(false))
      set_piano([])
    }
  }, [])

  // Create Piano END

  // Keyboard Listener START
  const play_piano_key = (event) => {

    let pressed_key = event.key.toLowerCase();

    if (pressed_key === 'shift') {
      pressed_key = event.location === 1 ? 'l_shft': 'r_shft'
    }


    let key_state = event.type === 'keydown'
    let key_ind = key_to_key[pressed_key]
    console.log(test)
    set_pressed_keys(prev_state => ({
      ...prev_state,
      [key_ind]: key_state
    }))
    // set_piano(prev_state => {
    //   const keys = [...prev_state]; 
    //   console.log(keys, key_ind)
    //   keys[key_ind] = React.cloneElement(keys[key_ind], { pressed: key_state }); // Clone the specific PianoKey element and update the pressed prop
    //   return keys; 
    // });
  }


  useEffect(() => {
    if (user_interact && visible) {
      document.addEventListener('keydown', play_piano_key)
      document.addEventListener('keyup', play_piano_key)
    }

    return () => {
      document.removeEventListener('keydown', play_piano_key)
      document.addEventListener('keyup', play_piano_key)
    };
  }, [visible])
  // Keyboard Listener END


  // Switch 1/2 piano layers START
  let [piano_styling, set_piano_styling] = useState(null)


  const [window_size, setwindow_size] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setwindow_size({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    // Listen for window resize events
    window.addEventListener('resize', handleResize);

    // Clean up the event listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  useEffect(() => {
    if (window_size['width'] >= 1000) {
      set_piano_styling(prev_state => ({
        first_level: {
          width: '50%',
          bottom: '0',
          left: '0'
        },
        second_level: {
          width: '50%',
          bottom: '0',
          right: '0'
        }
      }))
    } else {
      set_piano_styling(null)
    }
  }, [window_size])

  // Switch 1/2 piano layers END

  let save_song = async () => {
    set_save_prompt(async prev_state => {
      let res = await default_ajax('post', 'songs/create-song', { 'song': recorded_song[0], 'name': 'TODO' })
      if (res !== -1) {
        console.log(res)
      }
      return [true, true]
    })
  }

  // Playback START
  let [playing, set_playing] = useState(null)
  let [song, set_song] = useState(song_prop)
  let [playback_index, set_playback_index] = useState(0)
  let song_player = useRef(null)

  function song_playback() {
    let start_time = null;
    let animation_frame_id = null;

    function animate(timestamp) {
      if (!start_time) {
        start_time = timestamp;
      }

      for (let i = playback_index; i < song.length; i++) {
        if (song[i]['start_timestamp'] <= timestamp) {
          let pkey_index = key_to_key[song[i]['note']]
          set_playback_visual(prev_state => {
            let l =[...prev_state]
            l[pkey_index] = 'expand_up'
          })
        } else if (song[i]['end_timestamp'] <= timestamp) {
          let pkey_index = key_to_key[song[i]['note']]
          set_playback_visual(prev_state => {
            let l =[...prev_state]
            l[pkey_index] = 'move_up'
          })
          set_playback_index(prev_state => prev_state + 1)
          }
        }
  
      if (playing) {
        animation_frame_id = requestAnimationFrame(animate);
      }
    }
  
    function pause() {
      set_playing(false)
      cancelAnimationFrame(animation_frame_id);
    }
  
    function resume() {
        set_playing(true)
        requestAnimationFrame(animate);
    }
  
    function reset() {
      start_time = null;
      set_playback_index(0)
      set_playing(false)
    }
    return { pause, resume, reset };
  }
  
  useEffect(() => {
    set_song(song_prop)
    // console.log(song_prop.song_notes)
  }, [song_prop])

  
  useEffect(() => {
    if (playing === null) {
      song_player.current = song_playback()
    } else if (playing) {
      console.log('Play')
      song_player.current.resume()
    } else {
      console.log('stop')
      song_player.current.pause()
    }
  }, [playing])

  return (
    <>
      {save_prompt[0] && 
        <div className='piano-saveprompt-screen' onClick={() => set_save_prompt([false, false])}>
          
          <div className='piano-saveprompt' onClick={e => e.stopPropagation()} >
            <button className='close-btn' onClick={() => set_save_prompt([false, false])}><FaXmark /></button>
              {save_prompt[0] && !save_prompt[1] && <h2>Save or Share Your Recording</h2>}
              {save_prompt[0] && !save_prompt[1] &&
              <div>
                <button className='piano-saveprompt-btn' onClick={() => save_song()} ><MdOutlineSaveAlt /> Save </button>
                <button className='piano-saveprompt-btn'><TbShare2 /> Share</button>
              </div>
              }
              {save_prompt[0] && save_prompt[1] &&
              <div>
                <div className='pianosave-checkmark' >Saved! <IoMdCheckmark /></div>
                <button>Click here to go to thingy</button>
              </div>
              }

          </div>
        </div>
      }
      
      <div id='piano-wrapper'>
        {user_interact && 
          <div id='piano-btn-wrapper'>
            <button className='piano-btn' onClick={() => recording[0] ? recording_action('end') : recording_action('start')} ><div className='red-dot'></div></button>
            {recording[0] && <button className='piano-btn' id='clear-song-btn' onClick={() => clear_song()} ><FaXmark /></button>}
            {(type === 'register' || type === 'login') && !recording && <p>{recording[1] === 0 ? 'Press Start To Begin Recording' : 'Confirm Password'}</p>}
          </div>
        }
        { type == 'playback' && song &&
          <div id='pianoplayback-btn-wrapper'>
            <button className='piano-btn' onClick={() => set_playing(prev_state => {return prev_state === null ? true : !prev_state})}>{playing ? <FaPause />: <FaPlay /> }</button>
            <button className='piano-btn'>Go to start</button>
          </div>
        }

        

        <div id='piano'>
          <div id="first-level" style={piano_styling ? piano_styling['first_level']: undefined}>
            {piano.length === end-start && piano.slice(0, Math.floor(piano.length/2))}
          </div>
          <div id="second-level" style={piano_styling ? piano_styling['second_level']: undefined}>
            {piano.length === end-start && piano.slice(Math.floor(piano.length/2), piano.length)}
          </div>  
        </div>
      </div>
    </>
  )
}

export default Piano

我尝试过修改状态的设置,但是在将一个简单的测试状态设置为“hello”时却不起作用,我不知所措。我已经尝试重置服务器

javascript reactjs state
1个回答
0
投票

我相信我现在明白问题所在了。当我在空的 useEffect 中设置测试状态时,它正确地更新了该值,但是,我打印它的方式(通过另一个 useEffect 中的事件侦听器)是错误的。事件监听器调用一个函数,并且由于 JS 闭包的工作方式,该函数仅引用它初始化时的状态。这导致它总是未定义。我仍然有点困惑为什么它打印了两次,一次打印旧值,一次打印新值,因为我认为 React 通常不会在文件保存时重新安装应用程序,但我想这就是发生的事情。我也不知道为什么第二个打印语句捕获了测试状态的正确值,因为重新安装应该会导致完全相同的错误行为

© www.soinside.com 2019 - 2024. All rights reserved.