反应道具变化超出预期

时间:2019-11-12 19:42:11

标签: javascript reactjs react-hooks react-props pusher-js

我有一个React App,当它感测到API命中(使用Pusher进行发布-订阅)时,它会传递到SlotMachine组件。

每个API命中应该rotate() 一次,但是,正如您在我的输出中看到的那样,它会调用rotate() 多次次,并说道具每次都变了。但是道具在控制台中看起来是一样的,并且在最初的道具之后没有更多的API命中(我使用Postman触发)。

任何原因让我的道具发生很大变化?我对钩子或道具不了解? 另外,为什么我的Rotate函数总是不断被调用,除非在第一个API命中之后?

应用程序:

import React, { useState, useEffect } from 'react'
import { render } from 'react-dom'
import Pusher from 'pusher-js'
import SlotMachine from './components/SlotMachine'

const App = () => {
  const [ slots, setSlots ] = useState([]);
  const [ apiHit, setApiHit ] = useState(false);

  const pusher = new Pusher('XXXXXXX', {
         cluster: 'XXX',
         encrypted: true
       });

  const channel = pusher.subscribe('my-channel');
     channel.bind('my-event', data => {
       console.log(`Got data from pusher ${data}`);
       setSlots(data);
       setApiHit(true);
     });

  return(
    <div>
      <SlotMachine slots={slots} apiHit={apiHit}/>
    </div>
  );
};

render(<App />, document.getElementById('root'));

老虎机:

 import React, { useEffect, useRef, useState } from 'react'
    import './slots.css'

    function rnd(min, max) {
      return Math.floor(Math.random() * (max - min)) + min
    }

    const SlotMachine = (props) => {
        const ringElements = useRef();
        const [manual, setManual] = useState(true);
        const [rings, updateRings] = useState([]);
        const [ slots, setSlots ] = useState([]);

        const slotMap = {
          0: 72,
          1: 144,
          2: 216,
          3: 288,
          4: 360
      };

        useEffect(()=>{
          console.log(' --- props change --- ');
          console.log(props);
          setSlots( slots => props.slots );
          if(props.slots !== undefined && props.apiHit === true ) {
            setManual(manual => false);
            rotate();
            setManual(true);
          }
        }, [props.slots, props.apiHit]);

        //need to store previous to get accurate measure
        const rotate = () => {
          console.log(`---ROTATE---`);
            rings.forEach((el, i) => {
              let obj = el; //should be an obj
              console.log(obj);
              let prev = null;
              if (obj.prev !== null) {  prev = obj.prev; } //update prev if not first time
              console.log(`Prev is ${prev}`);
              let element = obj.ringElement; //should be the ring itself
              //let obj  = rings[i];
             console.log(element);
              //get the slots it needs to move to
              let move = manual && !props.apiHit ? slotMap[rnd(0,4)] : slotMap[slots[i]];
                console.log(`move: ${move}`);
              let diff = Math.abs(prev - move);
              console.log(`The diff is ${diff}`);
              //take the previous and the move and figure out "difference", be sure to add that difference to the spin
              let res = move + (360 * rnd(3,6));
                //let res = move //+ (360 * rnd(3,6)); //randomly spin should end up back at same spot no matter what
                console.log(`rotateX(${res}deg) for ring${i}`);
                element.style.transform = `rotateX(${res}deg)`;
              obj.prev = move;
                //rings[i] = obj;// update proper object in state
              rings[i] = obj;
              updateRings( rings  );
            });
         }

    //when first mounts
        useEffect(() => {
          console.log('component did mount');
            Array.from(ringElements.current.children).forEach((el, i) => {
              console.log(`elements in ring: ${el}`);
              let obj = {}
              obj.ringElement = el
              obj.prev = null
              updateRings(rings => [...rings, obj])
            //  updateRings(rings => [...rings, el]);
            });
          }, []);

        return(
          <div>
            <div className="slots">
            <div className="rings" ref={ringElements}>
              <div className="ring">
                <div className="slot" id="0">0</div>
                <div className="slot" id="1">1</div>
                <div className="slot" id="2">2</div>
                <div className="slot" id="3">3</div>
                <div className="slot" id="4">4</div>
              </div>
              <div className="ring">
              <div className="slot" id="0">0</div>
              <div className="slot" id="1">1</div>
              <div className="slot" id="2">2</div>
              <div className="slot" id="3">3</div>
              <div className="slot" id="4">4</div>
              </div>
              <div className="ring">
              <div className="slot" id="0">0</div>
              <div className="slot" id="1">1</div>
              <div className="slot" id="2">2</div>
              <div className="slot" id="3">3</div>
              <div className="slot" id="4">4</div>
              </div>
            </div>
          </div>
          <button className="spin-button" onClick={() => { setManual(true); rotate(); } }>SPIN</button>
        </div>

      );
    }

    export default SlotMachine

输出:

初始API命中(按预期工作,仅触发一次Rotate()

Got data from pusher 3,1,3 .   <-- new API data recieved 
55  **--- props change ---** 
56 {slots: Array(3), apiHit: false}
55  **--- props change ---**is  
56 {slots: Array(3), apiHit: true}
71 **---ROTATE---**
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 288
96 rotateX(2088deg) for ring0
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 144
92 The diff is 144
96 rotateX(1224deg) for ring1
75 {ringElement: div.ring, prev: null}
83 Prev is null
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 288
96 rotateX(2088deg) for ring2

随后的API匹配会进行多次轮换调用,并显示道具在每次轮换之前都会发生变化。

Got data from pusher 3,0,1 . <-- new API hit 
55  --- props change ---
56 {slots: Array(3), apiHit: true}
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1368deg) for ring0
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 144
92 The diff is 0
96 rotateX(1584deg) for ring1
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(2088deg) for ring2
VM12097 index.js:39 Got data from pusher 3,0,1 . <-- **same** API hit but recorded again????  
55  --- props change --- 
56 {slots: Array(3), apiHit: true}apiHit: trueslots: (3) [3, 0, 1]__proto__: Object
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1368deg) for ring0
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 72
92 The diff is 72
96 rotateX(1152deg) for ring1
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 144
92 The diff is 144
96 rotateX(1584deg) for ring2
VM12097 index.js:39 Got data from pusher 3,0,1
55  --- props change --- 
56 {slots: Array(3), apiHit: true}
71 ---ROTATE---
75 {ringElement: div.ring, prev: 288}
83 Prev is 288
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 288
92 The diff is 0
96 rotateX(1728deg) for ring0
75 {ringElement: div.ring, prev: 72}
83 Prev is 72
87 <div class=​"ring" style=​"transform:​ rotateX(1224deg)​;​">​…​</div>​
90 move: 72
92 The diff is 0
96 rotateX(1152deg) for ring1
75 {ringElement: div.ring, prev: 144}
83 Prev is 144
87 <div class=​"ring" style=​"transform:​ rotateX(2088deg)​;​">​…​</div>​
90 move: 144
92 The diff is 0
96 rotateX(1224deg) for ring2

2 个答案:

答案 0 :(得分:3)

我认为问题是由于您每次渲染应用程序时都创建了Pusher。我认为,将代码移到您的应用之外可以解决问题。 更新:在应用就绪后注册事件

const pusher = new Pusher('XXXXXXX', {
   cluster: 'XXX',
   encrypted: true
});
const App = () => {
  const [ slots, setSlots ] = useState([]);
  const [ apiHit, setApiHit ] = useState(false);
  useEffect(() => {
    const channel = pusher.subscribe('my-channel');
    const handler = data => {
      console.log(`Got data from pusher ${data}`);
      setSlots(data);
      setApiHit(true);
    };
    channel.bind('my-event', handler);

    return () => {
      channel.unbind(null, handler);
      channel.unsubscribe();
    }
  } ,[]);
  return(
      <div>
        <SlotMachine slots={slots} apiHit={apiHit}/>
      </div>
    );
  };

答案 1 :(得分:1)

如果要减少多个渲染,请将多个状态组合到一个对象中并更新相应的键

例如

// const [manual, setManual] = useState(true);
// const [rings, updateRings] = useState([]);
// const [ slots, setSlots ] = useState([]);
const [state, setState] = useState({ manual: true, rings: [], slots: [] });

and in your effects, code in a way to create and update the state and finally set it once.


 useEffect(()=>{
      console.log(' --- props change --- ');
      console.log(props);
      const newState = { ...state };
      newState.slots = props.slots;
      if(props.slots !== undefined && props.apiHit === true ) {
        newState.manual = false;
        setState(newState);
        rotate();
        setState({ ...state, manual: true });
      }
    }, [props.slots, props.apiHit]);