Vue3에서 나만의 소셜 공유 컴포넌트 작성

70604 단어
우리는 전체 프로젝트를 Vue3로 마이그레이션하려고 하므로 기본 구성 요소, 버튼에서 메시지, 대화 상자 등, 모든 비즈니스 로직 및 관련 구성 요소를 포함하여 많은 재작성이 있었습니다.

다시 작성해야 하는 구성 요소 중 하나는 vue-social-sharing 으로 Vue2에서 사용하기 정말 쉽습니다.

소스 코드 해부


vue-social-sharing의 소스 코드 내부를 파헤치면 세 개의 소스 파일을 찾을 수 있습니다.
  • vue-social-sharing.js
  • network.js
  • share-network.js

  • 음, 첫 번째 항목vue-social-sharing.js은 올바른 방법으로 구성 요소를 마운트하는 방법을 지시하는 항목 파일입니다.



    음, 이것은 Vue3에 작성하기 쉽고 예제here도 찾을 수 있습니다.



    간단한 Vue.use(), 이해하기 쉬운, 다른 파일을 확인하자
    network.js에서 다음과 같은 여러 소셜 미디어 링크를 내보냅니다.



    우리는 그것을 유지할 수 있습니다, 아무것도 변경할 필요가 없습니다

    이제 남은 문제는 share-network.js입니다.

    import AvailableNetworks from './networks'
    
    let $window = typeof window !== 'undefined' ? window : null
    
    export function mockWindow (self) {
      $window = self || window // mock window for unit testing
    }
    
    export default {
      name: 'ShareNetwork',
    
      props: {
        /**
         * Name of the network to display.
         */
        network: {
          type: String,
          required: true
        },
    
        /**
         * URL of the content to share.
         */
        url: {
          type: String,
          required: true
        },
    
        /**
         * Title of the content to share.
         */
        title: {
          type: String,
          required: true
        },
    
        /**
         * Description of the content to share.
         */
        description: {
          type: String,
          default: ''
        },
    
        /**
         * Quote content, used for Facebook.
         */
        quote: {
          type: String,
          default: ''
        },
    
        /**
         * Hashtags, used for Twitter and Facebook.
         */
        hashtags: {
          type: String,
          default: ''
        },
    
        /**
         * Twitter user, used for Twitter
         * @var string
         */
        twitterUser: {
          type: String,
          default: ''
        },
    
        /**
         * Media to share, used for Pinterest
         */
        media: {
          type: String,
          default: ''
        },
    
        /**
         * HTML tag used by the Network component.
         */
        tag: {
          type: String,
          default: 'a'
        },
    
        /**
         * Properties to configure the popup window.
         */
        popup: {
          type: Object,
          default: () => ({
            width: 626,
            height: 436
          })
        }
      },
    
      data () {
        return {
          popupTop: 0,
          popupLeft: 0,
          popupWindow: undefined,
          popupInterval: null
        }
      },
    
      computed: {
        /**
         * List of available networks
         */
        networks () {
          return this.$SocialSharing
            ? this.$SocialSharing.options.networks
            : AvailableNetworks
        },
    
        /**
         * Formatted network name.
         */
        key () {
          return this.network.toLowerCase()
        },
    
        /**
         * Network sharing raw sharing link.
         */
        rawLink () {
          const ua = navigator.userAgent.toLowerCase()
    
          /**
           * On IOS, SMS sharing link need a special formatting
           * Source: https://weblog.west-wind.com/posts/2013/Oct/09/Prefilling-an-SMS-on-Mobile-Devices-with-the-sms-Uri-Scheme#Body-only
           */
          if (this.key === 'sms' && (ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1)) {
            return this.networks[this.key].replace(':?', ':&')
          }
    
          return this.networks[this.key]
        },
    
        /**
         * Create the url for sharing.
         */
        shareLink () {
          let link = this.rawLink
    
          /**
           * Twitter sharing shouldn't include empty parameter
           * Source: https://github.com/nicolasbeauvais/vue-social-sharing/issues/143
           */
          if (this.key === 'twitter') {
            if (!this.hashtags.length) link = link.replace('&hashtags=@h', '')
            if (!this.twitterUser.length) link = link.replace('@tu', '')
          }
    
          return link
            .replace(/@tu/g, '&via=' + encodeURIComponent(this.twitterUser))
            .replace(/@u/g, encodeURIComponent(this.url))
            .replace(/@t/g, encodeURIComponent(this.title))
            .replace(/@d/g, encodeURIComponent(this.description))
            .replace(/@q/g, encodeURIComponent(this.quote))
            .replace(/@h/g, this.encodedHashtags)
            .replace(/@m/g, encodeURIComponent(this.media))
        },
    
        /**
         * Encoded hashtags for the current social network.
         */
        encodedHashtags () {
          if (this.key === 'facebook' && this.hashtags.length) {
            return '%23' + this.hashtags.split(',')[0]
          }
    
          return this.hashtags
        }
      },
    
      render: function (createElement) {
        if (!this.networks.hasOwnProperty(this.key)) {
          throw new Error('Network ' + this.key + ' does not exist')
        }
    
        const node = {
          class: 'share-network-' + this.key,
          on: {
            click: () => this[this.rawLink.substring(0, 4) === 'http' ? 'share' : 'touch']()
          }
        }
    
        if (this.tag === 'a') node.attrs = { href: 'javascript:void(0)' }
    
        return createElement(this.tag, node, this.$slots.default)
      },
    
      methods: {
        /**
         * Center the popup on multi-screens
         * http://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen/32261263
         */
        resizePopup () {
          const width = $window.innerWidth || (document.documentElement.clientWidth || $window.screenX)
          const height = $window.innerHeight || (document.documentElement.clientHeight || $window.screenY)
          const systemZoom = width / $window.screen.availWidth
    
          this.popupLeft = (width - this.popup.width) / 2 / systemZoom + ($window.screenLeft !== undefined ? $window.screenLeft : $window.screenX)
          this.popupTop = (height - this.popup.height) / 2 / systemZoom + ($window.screenTop !== undefined ? $window.screenTop : $window.screenY)
        },
    
        /**
         * Shares URL in specified network.
         */
        share () {
          this.resizePopup()
    
          // If a popup window already exist, we close it and trigger a change event.
          if (this.popupWindow && this.popupInterval) {
            clearInterval(this.popupInterval)
    
            // Force close (for Facebook)
            this.popupWindow.close()
    
            this.emit('change')
          }
    
          this.popupWindow = $window.open(
            this.shareLink,
            'sharer-' + this.key,
            ',height=' + this.popup.height +
            ',width=' + this.popup.width +
            ',left=' + this.popupLeft +
            ',top=' + this.popupTop +
            ',screenX=' + this.popupLeft +
            ',screenY=' + this.popupTop
          )
    
          // If popup are prevented (AdBlocker, Mobile App context..), popup.window stays undefined and we can't display it
          if (!this.popupWindow) return
    
          this.popupWindow.focus()
    
          // Create an interval to detect popup closing event
          this.popupInterval = setInterval(() => {
            if (!this.popupWindow || this.popupWindow.closed) {
              clearInterval(this.popupInterval)
    
              this.popupWindow = null
    
              this.emit('close')
            }
          }, 500)
    
          this.emit('open')
        },
    
        /**
         * Touches network and emits click event.
         */
        touch () {
          window.open(this.shareLink, '_blank')
    
          this.emit('open')
        },
    
        emit (name) {
          this.$root.$emit('share_network_' + name, this.key, this.url)
          this.$emit(name, this.key, this.url)
        }
      }
    }
    


    안에 무엇이 있는지 궁금할 때this, 그냥 인쇄해 보겠습니다.



    여기서는 VueComponent 인스턴스임을 보여줍니다. 메서드, 계산 속성 및 기타 대부분을 먼저 Vue3 스타일로 다시 작성할 수 있습니다. 마지막으로 render 함수를 살펴보겠습니다.

    고쳐 쓰기



    행동 양식
  • ~ function 또는 화살표 기능

  • 변수
  • ref 또는 reactive를 사용하여
  • 를 정의합니다.

    소품
  • defineProps를 사용하여 소품
  • 을 정의합니다.
  • 소품을 분해하려면 toRefs 또는 toRef를 사용하세요.

  • 방출하다
  • defineEmits를 사용하여 방출
  • 을 정의합니다.

    계산
  • computed(() => xxx)를 사용하여 계산된 속성
  • 을 정의합니다.

    보다
  • watch(() => props.xxx, (newVal, oldVal) => xxxxxx)를 사용하여 특정 속성
  • 을 감시합니다.
  • watchEffect는 우리가 사용할 수 있는 또 다른 것입니다

  • 렌더링 기능



    먼저 Vue2 및 Vue3 문서에서 렌더링 함수 항목을 읽어봅시다.
  • Render Function in Vue3
  • Render Function in Vue2

  • 여기에서 Vue2에서 render 함수에 기본 템플릿이 있음을 알 수 있습니다.



    Vue2에서:
  • createElement는 실제로 함수입니다. 인쇄하여 살펴볼 수 있습니다
  • .
  • 렌더링 함수가 결국 반환됨createElement
  • createElement는 3개의 인수를 전달할 수 있습니다.
  • 첫 번째 필수 항목, 문자열/개체/함수
  • 두 번째 옵션, 객체
  • 세 번째 옵션, 문자열/배열


  • Vue3에는 큰 변화가 있는데, 사실 문서가 변화에 대해 아주 명확하게 설명하고 있는 것 같아서 그냥 다음 부분으로 넘어가겠습니다.

    Vue3에서 렌더 로직 재작성



    이제 여기에 share-network.js를 작성해 보겠습니다.

    import AvailableNetworks from './network';
    import {
        ref,
        computed,
        h,
        onMounted,
        reactive,
        toRefs,
        getCurrentInstance
    } from 'vue';
    let $window = typeof window !== 'undefined' ? window : null;
    
    export default {
        name: 'DPSharing',
        props: {
            network: {
                type: String,
                required: true
            },
            url: {
                type: String,
                required: true
            },
            title: {
                type: String,
                required: true
            },
            description: {
                type: String,
                default: ''
            },
            quote: {
                type: String,
                default: ''
            },
            hashtags: {
                type: String,
                default: ''
            },
            twitterUser: {
                type: String,
                default: ''
            },
            media: {
                type: String,
                default: ''
            },
            tag: {
                type: String,
                default: 'a'
            },
            popup: {
                type: Object,
                default: () => ({
                    width: 626,
                    height: 436
                })
            }
        },
        emits: ['change', 'close', 'open'],
        setup(props, context) {
            const { slots, emit } = context;
            const popupTop = ref(0);
            const popupLeft = ref(0);
            const popupWindow = ref(undefined);
            const popupInterval = ref(null);
    
            const {
                network,
                url,
                title,
                description,
                quote,
                hashtags,
                twitterUser,
                media,
                tag,
                popup
            } = toRefs(props);
    
            const networks = computed(() => {
                return AvailableNetworks;
            });
    
            const key = computed(() => {
                return network.value.toLowerCase();
            });
    
            const rawLink = computed(() => {
                const ua = navigator.userAgent.toLowerCase();
                if (
                    key.value === 'sms' &&
                    (ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1)
                ) {
                    return networks.value[key.value].replace(':?', ':&');
                }
                return networks.value[key.value];
            });
    
            const shareLink = computed(() => {
                console.log('check sharelink');
                let link = rawLink.value;
    
                if (key.value === 'twitter') {
                    if (!hashtags.value.length) link = link.replace('&hashtags=@h', '');
                    if (!twitterUser.value.length) link = link.replace('@tu', '');
                }
    
                return link
                    .replace(/@tu/g, '&via=' + encodeURIComponent(twitterUser.value))
                    .replace(/@u/g, encodeURIComponent(url.value))
                    .replace(/@t/g, encodeURIComponent(title.value))
                    .replace(/@d/g, encodeURIComponent(description.value))
                    .replace(/@q/g, encodeURIComponent(quote.value))
                    .replace(/@h/g, encodedHashtags.value)
                    .replace(/@m/g, encodeURIComponent(media));
            });
    
            const encodedHashtags = computed(() => {
                console.log('check encodedHashtags');
                if (key === 'facebook' && hashtags.value.length) {
                    return '%23' + hashtags.value.split(',')[0];
                }
    
                return hashtags.value;
            });
    
            const resizePopup = () => {
                console.log('resize popup method triggered here ');
    
                const width =
                    $window.innerWidth ||
                    document.documentElement.clientWidth ||
                    $window.screenX;
                const height =
                    $window.innerHeight ||
                    document.documentElement.clientHeight ||
                    $window.screenY;
                const systemZoom = width / $window.screen.availWidth;
    
                popupLeft.value =
                    (width - popup.width) / 2 / systemZoom +
                    ($window.screenLeft !== undefined
                        ? $window.screenLeft
                        : $window.screenX);
                popupTop.value =
                    (height - popup.height) / 2 / systemZoom +
                    ($window.screenTop !== undefined ? $window.screenTop : $window.screenY);
            };
    
            const share = () => {
                // debugger;
                resizePopup();
    
                // If a popup window already exist, we close it and trigger a change event.
                if (popupWindow.value) {
                    clearInterval(popupInterval.value);
    
                    // Force close (for Facebook)
                    popupWindow.value.close();
                    emit('change');
                }
    
                popupWindow.value = $window.open(
                    shareLink.value,
                    'sharer-' + key.value,
                    ',height=' +
                        popup.value.height +
                        ',width=' +
                        popup.value.width +
                        ',left=' +
                        popupLeft.value +
                        ',top=' +
                        popupTop.value +
                        ',screenX=' +
                        popupLeft.value +
                        ',screenY=' +
                        popupTop.value
                );
    
                // If popup are prevented (AdBlocker, Mobile App context..), popup.window stays undefined and we can't display it
                if (!popupWindow.value) return;
    
                popupWindow.value.focus();
    
                // Create an interval to detect popup closing event
                popupInterval.value = setInterval(() => {
                    if (!popupWindow.value || popupWindow.value.closed) {
                        clearInterval(popupInterval.value);
    
                        popupWindow.value = null;
    
                        emit('close');
                    }
                }, 500);
    
                emit('open');
            };
    
            const touch = () => {
                console.log('touch method triggered');
                window.open(shareLink.value, '_blank');
                emit('open');
            };
    
            const renderData = () => {
                if (!networks.value.hasOwnProperty(key.value)) {
                    throw new Error('Network ' + key.value + ' does not exist');
                }
    
                const node = {
                    class: 'share-network-' + key.value,
                    on: {
                        click: rawLink.value.substring(0, 4) === 'http' ? share : touch
                    },
                    attrs: {
                        href:
                            rawLink.value.substring(0, 4) === 'http'
                                ? shareLink.value
                                : rawLink.value
                    }
                };
    
                if (tag.value === 'a') {
                    node.attrs = { href: 'javascript:void(0)' };
                }
    
                return [tag.value, node, slots.default()];
            };
    
            const data = renderData();
            const tg = `${data[0]}`;
            const node = data[1];
            const content = data[2];
    
            return () =>
                h(
                    tg,
                    {
                        onClick: node.on.click,
                        href: node.attrs.href
                    },
                    content
                );
        }
    };
    
    


    마지막에 반환하는 렌더링 함수에 주의를 기울이십시오. 먼저 렌더링 목적으로 데이터를 반환하는 renderData 라는 함수를 식별하고 console.log() 응답을 표시합니다.

    {
        "a",
        {
            "class": "share-network-facebook",
            "on": {},
            "attrs": {
                "href": "javascript:void(0)"
            }
        }
    }
    

    a 태그는

    이를 내보내고 사용할 구성 요소를 등록할 수 있습니다.

    예시:

    
    <COSharing network="facebook" :title="'facebook'" url="https://www.google.com" quote="hello" >
     click me to open whatsapp sharing
    </COSharing>
    

    quote 트위터용

    요소를 검사하면 a 태그로 래핑된 요소를 찾을 수 있습니다.

    <a href="javascript:void(0)" data-v-5be083fa="">
    // whatever your content here 
    </a>
    


    이제 우리는 Vue3 for vue-social-sharing 라이브러리에 성공적으로 재작성했습니다. 더 이상 해당 패키지의 업데이트에 의존할 필요가 없습니다. 자신만의 패키지를 만드는 방법을 배울 수 있습니다. 이는 실제로 좋은 습관입니다.

    좋은 웹페이지 즐겨찾기