ReactSVG Class Component Refactoring 1-初期Refactoring


テストが完了する前に作成したプロジェクトなので、再パッケージしながらコードを整理します.
コードは、実際に有効なレベルではなく、現在の機能のみを実行します.
実際には,コードを有効レベルに達させるとともに,後で修正するためにコードを再パッケージする.
現在の再構築の目的
  • stateを積極的に利用します.
  • 注意事項
  • setState()は非同期(内部キューが存在し、キューをポーリングする方法で)であり、実行時に変数はすぐに変更されません.
    ->リアルタイムアニメーションに影響しますか?
  • 特別事項


    意外なことに、文法は以下の通りです.
  • は、イベントをオブジェクト内の関数として宣言し、使用するために参照します.
  • 上記イベントでこのキーワードを使用するバインド方式は
  • である.
    変な、特にお勧めの方法ではないと思いますが、正式な書類でも使われています.
    svg.addEventListener('pointerdown', this.onPointerDown.bind(this));
    
    //...중략
    
    // Function called by the event listeners when user start pressing/touching
    onPointerDown(event) {
      this.isPointerDown = true; // We set the pointer as down
    
      // We get the pointer position on click/touchdown so we can get the value once the user starts to drag
      var pointerPosition = getPointFromEvent(event);
      this.pointerOrigin.x = pointerPosition.x;
      this.pointerOrigin.y = pointerPosition.y;
    }
    もちろん、これが正しいとは保証できません.
    しかし、良い代替案が思いつかないので、今もこの方式を維持し続けています.
    しかし、varはletに変更された.

    最初の再構築の試み


    以下は機動の再構築を確認する試みです.
    import kr_map from './kr.svg'
    import React from 'react'
    import { ReactSVG } from 'react-svg'
    import gsap from 'gsap/all';
    
    //이 함수는 class와 아무 관계 없이 독립적이므로 독립시킨다.
    function getPointFromEvent (event) {
        let point = {x:0, y:0};
        // If event is triggered by a touch event, we get the position of the first finger
        if (event.targetTouches) {
            point.x = event.targetTouches[0].clientX;
            point.y = event.targetTouches[0].clientY;
        } else {
            point.x = event.clientX;
            point.y = event.clientY;
        }
        
        return point;
    }
    
    function animatingViewBox(target, x, y, width, height){
        gsap.to(target, {
            duration: .5,
            attr: {viewBox: `${x} ${y} ${width} ${height}`},
            ease: "power3.inOut"
        });
    }
    
    
    export default class map extends React.Component {
        constructor(props){
            super(props);
    
            this.state = {
                current_viewbox : {
                    x:0,
                    y:0,
                    width:1200,
                    height:1080
                },
                new_viewbox : {
                    x:0,
                    y:0,
                    width:1200,
                    height:1080
                },
                pointerOrigin : {
                    x:0,
                    y:0
                },
                isPointerDown : false,
                svg : null
            }
        }
    
        // This function returns an object with X & Y values from the pointer event
        
        onPointerUp() {
            this.setState(prevState => ({
                // The pointer is no longer considered as down
                isPointerDown : false,
                // We save the viewBox coordinates based on the last pointer offsets
                current_viewbox : {
                    x : prevState.new_viewbox.x,
                    y : prevState.new_viewbox.y,
                    width: prevState.current_viewbox.width,
                    height: prevState.current_viewbox.height
                }
            }));
        }
    
        // Function called by the event listeners when user start pressing/touching
        onPointerDown(event) {
            let pointerPosition = getPointFromEvent(event);
    
            this.setState({
                isPointerDown : true, // We set the pointer as down
                pointerOrigin : {
                // We get the pointer position on click/touchdown so we can get the value once the user starts to drag
                    x : pointerPosition.x,
                    y : pointerPosition.y
                }
            });
        }
    
         // Function called by the event listeners when user start moving/dragging
        onPointerMove (event) {
            // Only run this function if the pointer is down
            if (!this.state.isPointerDown) {
                return;
            }
            // This prevent user to do a selection on the page
            event.preventDefault();
    
            // Get the pointer position
            let pointerPosition = getPointFromEvent(event);
    
            // We calculate the distance between the pointer origin and the current position
            // The viewBox x & y values must be calculated from the original values and the distances
            this.setState(prevState=> ({
                new_viewbox : {
                    x: prevState.current_viewbox.x - (pointerPosition.x - prevState.pointerOrigin.x),
                    y: prevState.current_viewbox.y - (pointerPosition.y - prevState.pointerOrigin.y)
                }
            }));
    
            
        }
    
        onZoom(event){
    
            if(event.deltaY > 0){
                this.setState(prevState => ({
                    current_viewbox : {
                        width : prevState.current_viewbox.width / 9,
                        height: prevState.current_viewbox.height / 9
                    }
                }))
            }
            else if(event.deltaY < 0){
                this.setState(prevState => ({
                    current_viewbox : {
                        width : prevState.current_viewbox.width / 1.1,
                        height: prevState.current_viewbox.height / 1.1
                    }
                }))
            }
        }
    
        onClick(event){
            if(event.target.getAttribute('name'))
            {
                let rect = event.target.getBoundingClientRect();
    
                this.setState({
                    current_viewbox : {
                        x : rect.top,
                        y : rect.bottom,
                        width : rect.width,
                        height: rect.height
                    }
                },
                () => {
                    animatingViewBox(this.svg, this.current_viewbox.x, this.current_viewbox.y, this.current_viewbox.width, this.current_viewbox.height);
                })
    
            }
        }
    
        render(){
           return <ReactSVG
                beforeInjection = {(inject_svg) => {
                    inject_svg.setAttribute('width',`${this.state.current_viewbox.width}`);
                    inject_svg.setAttribute('height',`${this.state.current_viewbox.height}`);
                    inject_svg.setAttribute('viewBox',`${this.state.current_viewbox.x} ${this.state.current_viewbox.y} ${this.state.current_viewbox.height} ${this.state.current_viewbox.height}`);
    
                    if (window.PointerEvent) {
                        inject_svg.addEventListener('pointerdown', this.onPointerDown.bind(this)); // Pointer is pressed
                        inject_svg.addEventListener('pointerup', this.onPointerUp.bind(this)); // Releasing the pointer
                        inject_svg.addEventListener('pointerleave', this.onPointerUp.bind(this)); // Pointer gets out of the SVG area
                        inject_svg.addEventListener('pointermove', this.onPointerMove.bind(this)); // Pointer is moving
                        inject_svg.addEventListener('wheel', this.onZoom.bind(this));
                        inject_svg.addEventListener('click', this.onClick.bind(this));
                    } else {
                        // Add all mouse events listeners fallback
                        inject_svg.addEventListener('mousedown', this.onPointerDown.bind(this)); // Pressing the mouse
                        inject_svg.addEventListener('mouseup', this.onPointerUp.bind(this)); // Releasing the mouse
                        inject_svg.addEventListener('mouseleave', this.onPointerUp.bind(this)); // Mouse gets out of the SVG area
                        inject_svg.addEventListener('mousemove', this.onPointerMove.bind(this)); // Mouse is moving
    
                        // Add all touch events listeners fallback
                        inject_svg.addEventListener('touchstart', this.onPointerDown.bind(this)); // Finger is touching the screen
                        inject_svg.addEventListener('touchend', this.onPointerUp.bind(this)); // Finger is no longer touching the screen
                        inject_svg.addEventListener('touchmove', this.onPointerMove.bind(this)); // Finger is moving
                    }
                    
                    //this.setState({svg : inject_svg})
            }
            }
    
            afterInjection = {(error, svg) => {
                if (error) {
                    console.error(error);
                    return;
                }
                svg.classList.add('region');
            }
            }
    
            src = {kr_map}
           ></ReactSVG>
        }
    }
    現在prevStateの操作やsetState完了時のコールバックのthis処理はまだあいまいで、エラーが発生しやすいコードです.しかし、予想とは異なるエラーが発生したため、記録しておく.
    このコードにエラーが発生しました.
      162 | render(){
      163 |    return <ReactSVG
      164 |         beforeInjection = {(inject_svg) => {
    > 165 |             this.setState({svg : inject_svg});
        
    エラーの内容は次のとおりです.
    Error: Maximum update depth exceeded.
    This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
    React limits the number of nested updates to prevent infinite loops.
    上記のコードでは、setStateのプロセスにおいて、componentWillUpdate/24579142コードが繰り返し呼び出され、無限回帰する.
    エラーをよく確認すると、次のコードが表示されます.
    //ReactSVG.componentDidUpdate
    //.../Projects/compiled/ReactSVG.js:114
    
      112 | componentDidUpdate(prevProps) {
      113 |     if (shallowDiffers(prevProps, this.props)) {
    > 114 |         this.setState(() => this.initialState, () => {
      115 | ^           this.removeSVG();
      116 |             this.renderSVG();
      117 |         });
    ReactSVGに割り当てられているのはもう1つReactComponentとして管理されています.
    (ReactSVGの値を変更すると、既存のSVGが削除され、新しいSVGがレンダリングされます)
    今の問題は、
    this.setState({svg : inject_svg});
    部門として、実際に削除しても無限の回帰問題は発生しません.
    なぜこんなことが起こったのか理解してください.
    this.setState(..) で行ないます.
    Component.prototype.setState = function (partialState, callback) {
      if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
        {
          throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
        }
      }
    
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    設定するステータスがobject、function、nullでない場合はErrorです.
    この値を超えると、Stateを設定できるキューに移動します.
    enqueueSetState(...)内部は以下のように伸びている.
    var classComponentUpdater = {
      isMounted: isMounted,
      //여기서 inst는 this이다.
      enqueueSetState: function (inst, payload, callback) {
        var fiber = get(inst);
        var eventTime = requestEventTime();
        var lane = requestUpdateLane(fiber);
        var update = createUpdate(eventTime, lane);
        update.payload = payload;
    
        if (callback !== undefined && callback !== null) {
          {
            warnOnInvalidCallback(callback, 'setState');
          }
    
          update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      },
      
    //후략
    コールバックなどを設定し、updateを作成してenqueueUpdateを行います.
    EnqueueUpdate関数の内部は次のようになります.
    function enqueueUpdate(fiber, update) {
      var updateQueue = fiber.updateQueue;
    
      if (updateQueue === null) {
        // Only occurs if the fiber has been unmounted.
        return;
      }
    
      var sharedQueue = updateQueue.shared;
      var pending = sharedQueue.pending;
    
      if (pending === null) {
        // This is the first update. Create a circular list.
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }
    
      sharedQueue.pending = update;
    
      {
        if (currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate) {
          error('An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.');
    
          didWarnUpdateInsideUpdate = true;
        }
      }
    }
    updateQueueには共有部分が含まれています.
    この共有キューが保留中であることを確認する部門があります.
    懸案がない場合は、updateのnextをすぐにさっきのキューに入れてください.
    保留中の場合は、保留中のキューに立っています.
    この過程自体は何も繰り返すことができなくて、重要なのはそのQの内容です.
    上記リンクは、次のコードを示します.
    inputDigit(digit){
      this.setState({
        displayValue: String(digit)
      })
    
    <button type="button" onClick={this.inputDigit(0)}>0</button>;
    上のコードはさっきと同じ無限回帰エラーを生成します.
    回答として、以下の文章があります.
    The function onDigit sets the state, which causes a rerender, which causes onDigit to fire because that’s the value you’re setting as onClick which causes the state to be set which causes a rerender, which causes onDigit to fire because that’s the value you’re… Etc
    (翻訳)
    onDigit機能は状態を設定することによって再レンダリングされ、onDigitはこの値がonClickに設定されているため実行されます.以下を繰り返します.
    値を変更すると、下部のbuttonコンポーネントが再表示されます.したがって、inputDigitを再起動してbuttonコンポーネントのonClickを定義します.再び値をsetStateに変更すると、再表示されます.以下無限反復.
    クエリの応答:があります.
    要するに、componentDidUpdateではrenderと呼ばないでください.
    結局,以上のコードはクロスリサイクルである.
  • 素子はRealtSVGの形をしており、レンダリングを試みている.
  • レンダリング中、svgはsetStateを介して事前投影された関数の内部でsvgを定義しようとした.
  • 2回の試行でsetStateが呼び出されたため、renderは再び発生します.
  • 以下無限反復
  • したがってsvgはstateではなくsetStatus()を呼び出すことはできません.
    svgは単純な内部変数としてしか存在しない.

    二次改造

    import kr_map from './kr.svg'
    import React from 'react'
    import { ReactSVG } from 'react-svg'
    import gsap from 'gsap/all';
    
    //이 함수는 class와 아무 관계 없이 독립적이므로 독립시킨다.
    function getPointFromEvent (event) {
        let point = {x:0, y:0};
        // If event is triggered by a touch event, we get the position of the first finger
        if (event.targetTouches) {
            point.x = event.targetTouches[0].clientX;
            point.y = event.targetTouches[0].clientY;
        } else {
            point.x = event.clientX;
            point.y = event.clientY;
        }
        
        return point;
    }
    
    function animatingViewBox(target, x, y, width, height){
        gsap.to(target, {
            duration: .5,
            attr: {viewBox: `${x} ${y} ${width} ${height}`},
            ease: "power3.inOut"
        });
    }
    
    
    export default class map extends React.Component {
        constructor(props){
            super(props);
    
            this.svg = null
    
            this.state = {
                current_viewbox : {
                    x:0,
                    y:0,
                    width:1200,
                    height:1080
                },
                new_viewbox : {
                    x:0,
                    y:0,
                    width:1200,
                    height:1080
                },
                pointerOrigin : {
                    x:0,
                    y:0
                },
                isPointerDown : false,
            }
        }
    
    
        // This function returns an object with X & Y values from the pointer event
        
        onPointerUp() {
            this.setState(prevState => ({
                // The pointer is no longer considered as down
                isPointerDown : false,
                // We save the viewBox coordinates based on the last pointer offsets
                current_viewbox : {
                    x : prevState.new_viewbox.x,
                    y : prevState.new_viewbox.y,
                    width: prevState.current_viewbox.width,
                    height: prevState.current_viewbox.height
                }
            }));
        }
    
        // Function called by the event listeners when user start pressing/touching
        onPointerDown(event) {
            let pointerPosition = getPointFromEvent(event);
    
            this.setState({
                isPointerDown : true, // We set the pointer as down
                pointerOrigin : {
                // We get the pointer position on click/touchdown so we can get the value once the user starts to drag
                    x : pointerPosition.x,
                    y : pointerPosition.y
                }
            });
        }
    
         // Function called by the event listeners when user start moving/dragging
        onPointerMove (event) {
            // Only run this function if the pointer is down
            if (!this.state.isPointerDown) {
                return;
            }
            // This prevent user to do a selection on the page
            event.preventDefault();
    
            // Get the pointer position
            let pointerPosition = getPointFromEvent(event);
    
            // We calculate the distance between the pointer origin and the current position
            // The viewBox x & y values must be calculated from the original values and the distances
            this.setState(prevState=> ({
                new_viewbox : {
                    x: prevState.current_viewbox.x - (pointerPosition.x - prevState.pointerOrigin.x),
                    y: prevState.current_viewbox.y - (pointerPosition.y - prevState.pointerOrigin.y)
                }
            }));
    
            
        }
    
        onZoom(event){
    
            if(event.deltaY > 0){
                this.setState(prevState => ({
                    current_viewbox : {
                        width : prevState.current_viewbox.width / 9,
                        height: prevState.current_viewbox.height / 9
                    }
                }))
            }
            else if(event.deltaY < 0){
                this.setState(prevState => ({
                    current_viewbox : {
                        width : prevState.current_viewbox.width / 1.1,
                        height: prevState.current_viewbox.height / 1.1
                    }
                }))
            }
        }
    
        onClick(event){
            if(event.target.getAttribute('name'))
            {
                let rect = event.target.getBoundingClientRect();
    
                this.setState({
                    current_viewbox : {
                        x : rect.top,
                        y : rect.bottom,
                        width : rect.width,
                        height: rect.height
                    }
                },
                () => {
                    animatingViewBox(this.svg, this.current_viewbox.x, this.current_viewbox.y, this.current_viewbox.width, this.current_viewbox.height);
                })
    
            }
        }
    
        render(){
           return <ReactSVG
                beforeInjection = {(inject_svg) => {
                    inject_svg.setAttribute('width',`${this.state.current_viewbox.width}`);
                    inject_svg.setAttribute('height',`${this.state.current_viewbox.height}`);
                    inject_svg.setAttribute('viewBox',`${this.state.current_viewbox.x} ${this.state.current_viewbox.y} ${this.state.current_viewbox.height} ${this.state.current_viewbox.height}`);
    
                    if (window.PointerEvent) {
                        inject_svg.addEventListener('pointerdown', this.onPointerDown.bind(this)); // Pointer is pressed
                        inject_svg.addEventListener('pointerup', this.onPointerUp.bind(this)); // Releasing the pointer
                        inject_svg.addEventListener('pointerleave', this.onPointerUp.bind(this)); // Pointer gets out of the SVG area
                        inject_svg.addEventListener('pointermove', this.onPointerMove.bind(this)); // Pointer is moving
                        inject_svg.addEventListener('wheel', this.onZoom.bind(this));
                        inject_svg.addEventListener('click', this.onClick.bind(this));
                    } else {
                        // Add all mouse events listeners fallback
                        inject_svg.addEventListener('mousedown', this.onPointerDown.bind(this)); // Pressing the mouse
                        inject_svg.addEventListener('mouseup', this.onPointerUp.bind(this)); // Releasing the mouse
                        inject_svg.addEventListener('mouseleave', this.onPointerUp.bind(this)); // Mouse gets out of the SVG area
                        inject_svg.addEventListener('mousemove', this.onPointerMove.bind(this)); // Mouse is moving
    
                        // Add all touch events listeners fallback
                        inject_svg.addEventListener('touchstart', this.onPointerDown.bind(this)); // Finger is touching the screen
                        inject_svg.addEventListener('touchend', this.onPointerUp.bind(this)); // Finger is no longer touching the screen
                        inject_svg.addEventListener('touchmove', this.onPointerMove.bind(this)); // Finger is moving
                    }
                    
                    this.svg = inject_svg
            }
            }
    
            afterInjection = {(error, inject_svg) => {
                if (error) {
                    console.error(error);
                    return;
                }
                inject_svg.classList.add('region');
            }
            }
    
            src = {kr_map}
           ></ReactSVG>
        }
    }
    svgを独立させる.
    svgはstateの一員ではなく、setStateの最適化範囲を超えています.
    エラーは発生しません.
    しかし,以前に実現したすべての機能は機能せず,さらなる修正が必要である.