Vue3體驗卡~

前言

因爲vue更新後與文中demo不兼容 ,再此說明一下, 本文的全部內容是基於3.0.0-alpha.1版本編寫,各位小夥伴在學習的時候注意下版本哦javascript

vue我們都知道如今用的最多的是2.x,可是衆所周知在今年的上半年,至少做者是這麼說的 因此不少東西也都還沒肯定百分百是這樣的,頗有可能有改動,這個到時候再說html

本章講的內容基本都是根據Vue 官方 RFC來寫的,由於這裏面的消息還相對可信vue

與vue 2.x的區別以及我的觀感

vue3給個人感受有點像 vue2+mobx+ts+rxjs 的感受,你們若是用過這些東西可能也會有這種感受java

不閒扯那些沒用的,首先是生命週期 vue3捨棄了 beforeCreate 和 created,把這兩個混在一塊兒變成了一個setup , 其實在這裏面還有一個核心概念,就是vue2.x 你們用過可能都明白,就是數據響應,以數據爲中心node

也就是observe。這個observe的優點你們都能感受到,就是方便,劣勢就是性能,由於他這個東西本質來講就是全部的東西我都監聽,其實這在大型應用來講就比較不舒服了,由於你控制不了,並且功能也比較多,消耗也就高react

react你們都知道,什麼都不監聽,除非你用第三方的比方說 mobx這種的,其餘的都必須手動的去 setStategit

vue3就是在這兩個方面折了一箇中,這也是vue官方說的,既可以保證監聽,又能夠在大型應用上性能也是ok的,因此你須要本身去指定,這個要監聽,那個不要監聽github


扯了那麼多,首先我們如今clone一下項目vuex

$ git clone https://github.com/vuejs/vue-next.git
$ cd vue-next && yarn
複製代碼

克隆下來,而後安裝,這時候須要改兩個東西,都在項目的根目錄 分別的 rollup.config.jstypescript

加上 output.sourcemap = true 這裏已經改完了,須要的朋友能夠直接複製

import fs from 'fs'
import path from 'path'
import ts from 'rollup-plugin-typescript2'
import replace from '@rollup/plugin-replace'
import json from '@rollup/plugin-json'

if (!process.env.TARGET) {
  throw new Error('TARGET package must be specified via --environment flag.')
}

const masterVersion = require('./package.json').version
const packagesDir = path.resolve(__dirname, 'packages')
const packageDir = path.resolve(packagesDir, process.env.TARGET)
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
const pkg = require(resolve(`package.json`))
const packageOptions = pkg.buildOptions || {}

const knownExternals = fs.readdirSync(packagesDir).filter(p => {
  return p !== '@vue/shared'
})

// ensure TS checks only once for each build
let hasTSChecked = false

const outputConfigs = {
  'esm-bundler': {
    file: resolve(`dist/${name}.esm-bundler.js`),
    format: `es`
  },
  // main "vue" package only
  'esm-bundler-runtime': {
    file: resolve(`dist/${name}.runtime.esm-bundler.js`),
    format: `es`
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: `iife`
  },
  esm: {
    file: resolve(`dist/${name}.esm.js`),
    format: `es`
  }
}

const defaultFormats = ['esm-bundler', 'cjs']
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',')
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats
const packageConfigs = process.env.PROD_ONLY
  ? []
  : packageFormats.map(format => createConfig(format, outputConfigs[format]))

if (process.env.NODE_ENV === 'production') {
  packageFormats.forEach(format => {
    if (format === 'cjs' && packageOptions.prod !== false) {
      packageConfigs.push(createProductionConfig(format))
    }
    if (format === 'global' || format === 'esm') {
      packageConfigs.push(createMinifiedConfig(format))
    }
  })
}

export default packageConfigs

function createConfig(format, output, plugins = []) {
  if (!output) {
    console.log(require('chalk').yellow(`invalid format: "${format}"`))
    process.exit(1)
  }

  output.externalLiveBindings = false
  output.sourcemap = true

  const isProductionBuild =
    process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)
  const isGlobalBuild = format === 'global'
  const isRawESMBuild = format === 'esm'
  const isBundlerESMBuild = /esm-bundler/.test(format)
  const isRuntimeCompileBuild = /vue\./.test(output.file)

  if (isGlobalBuild) {
    output.name = packageOptions.name
  }

  const shouldEmitDeclarations =
    process.env.TYPES != null &&
    process.env.NODE_ENV === 'production' &&
    !hasTSChecked

  const tsPlugin = ts({
    check: process.env.NODE_ENV === 'production' && !hasTSChecked,
    tsconfig: path.resolve(__dirname, 'tsconfig.json'),
    cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
    tsconfigOverride: {
      compilerOptions: {
        declaration: shouldEmitDeclarations,
        declarationMap: shouldEmitDeclarations
      },
      exclude: ['**/__tests__', 'test-dts']
    }
  })
  // we only need to check TS and generate declarations once for each build.
  // it also seems to run into weird issues when checking multiple times
  // during a single build.
  hasTSChecked = true

  const entryFile =
    format === 'esm-bundler-runtime' ? `src/runtime.ts` : `src/index.ts`

  return {
    input: resolve(entryFile),
    // Global and Browser ESM builds inlines everything so that they can be
    // used alone.
    external:
      isGlobalBuild || isRawESMBuild
        ? []
        : knownExternals.concat(Object.keys(pkg.dependencies || [])),
    plugins: [
      json({
        namedExports: false
      }),
      tsPlugin,
      createReplacePlugin(
        isProductionBuild,
        isBundlerESMBuild,
        (isGlobalBuild || isRawESMBuild || isBundlerESMBuild) &&
          !packageOptions.enableNonBrowserBranches,
        isRuntimeCompileBuild
      ),
      ...plugins
    ],
    output,
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }
}

function createReplacePlugin( isProduction, isBundlerESMBuild, isBrowserBuild, isRuntimeCompileBuild ) {
  const replacements = {
    __COMMIT__: `"${process.env.COMMIT}"`,
    __VERSION__: `"${masterVersion}"`,
    __DEV__: isBundlerESMBuild
      ? // preserve to be handled by bundlers
        `(process.env.NODE_ENV !== 'production')`
      : // hard coded dev/prod builds
        !isProduction,
    // this is only used during tests
    __TEST__: isBundlerESMBuild ? `(process.env.NODE_ENV === 'test')` : false,
    // If the build is expected to run directly in the browser (global / esm builds)
    __BROWSER__: isBrowserBuild,
    // is targeting bundlers?
    __BUNDLER__: isBundlerESMBuild,
    // support compile in browser?
    __RUNTIME_COMPILE__: isRuntimeCompileBuild,
    // support options?
    // the lean build drops options related code with buildOptions.lean: true
    __FEATURE_OPTIONS__: !packageOptions.lean && !process.env.LEAN,
    __FEATURE_SUSPENSE__: true
  }
  // allow inline overrides like
  //__RUNTIME_COMPILE__=true yarn build runtime-core
  Object.keys(replacements).forEach(key => {
    if (key in process.env) {
      replacements[key] = process.env[key]
    }
  })
  return replace(replacements)
}

function createProductionConfig(format) {
  return createConfig(format, {
    file: resolve(`dist/${name}.${format}.prod.js`),
    format: outputConfigs[format].format
  })
}

function createMinifiedConfig(format) {
  const { terser } = require('rollup-plugin-terser')
  return createConfig(
    format,
    {
      file: resolve(`dist/${name}.${format}.prod.js`),
      format: outputConfigs[format].format
    },
    [
      terser({
        module: /^esm/.test(format)
      })
    ]
  )
}

複製代碼

還有就是 tsconfig.json,須要吧sourceMap改爲true,這裏也直接上代碼了

{
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "dist",
    "sourceMap": true,
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "allowJs": false,
    "noUnusedLocals": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "removeComments": false,
    "jsx": "preserve",
    "lib": ["esnext", "dom"],
    "types": ["jest", "puppeteer", "node"],
    "rootDir": ".",
    "paths": {
      "@vue/*": ["packages/*/src"]
    }
  },
  "include": [
    "packages/global.d.ts",
    "packages/runtime-dom/jsx.d.ts",
    "packages/*/src",
    "packages/*/__tests__",
    "test-dts"
  ]
}

複製代碼

而後隨便建一個目錄,我這裏叫public, 像這樣

而後執行 yarn dev 或者 cnpm run dev

而後在html啓動引入vue.js

path : ../packages/vue/dist/vue.global.js

上文提到 vuebeforeCreatecreated 都換成了 setup,那我們就來試一下,固然了,本篇的例子直接是用cdn方式來實驗的,畢竟如今vue也沒正式發版,不少東西都不肯定,配那麼全也沒有必要

稍微來搞一個模版,直接上代碼了

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> var App = { template: ` <div class="container"> </div>`, setup() { } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

大概就是這種感受,上文說過,vue如今折了一箇中,因此vue3裏推出了兩個新東西(實際上是一個,另外一個是變種)

reactive - 監聽數據

我們來直接上例子 正常什麼都不用的狀況下,若是要是想用數據,就直接寫在setup函數返回的json裏就能夠,像這樣

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive } = Vue var App = { template: ` <div class="container"> {{aaa}} </div>`, setup() { return { aaa: 123 } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

可是我們如今這個數據,其實不是受監聽的,你們能夠試一下單獨搞個json,而後改變值頁面是不會渲染的,這時候,就能用到我們的reactive

用法也很簡單,直接調用函數裏面的參數就是你的數據,返回值就是一個可監聽的數據

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive } = Vue var App = { template: ` <div class="container"> 名字 {{data.name}} <br/> 年齡 {{data.age}} <br/> </div>`, setup() { let data = reactive({name: 'name', age: 18}) return { data } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

這就能夠了

固然了,可能會有人感受麻煩,感受全部數據都得手動來監聽一下,其實若是你先麻煩的化能夠直接在setup return 出來的json外直接包一個reactive,這樣你就會到了vue2的寫法,像這樣

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive } = Vue var App = { template: ` <div class="container"> 名字 {{data.name}} <br/> 年齡 {{data.age}} <br/> </div>`, setup() { let data = {name: 'name', age: 18} return reactive({ data }) } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

並且,reactive 自帶深度監聽,也就是說你這個數據無論套了多少層,json裏有多少數組,都會監聽的

ref

這個是reactive 的一個小兄弟,可能這時候有人會感受奇怪,ref不是獲取dom元素用的麼,跟reactive咋還能有關係呢,這個其實官方也沒定下來,沒肯定下來究竟是獲取元素仍是包裝數據,這個可能得須要vue發佈了以後才能知道

先不說這個,先來講說ref怎麼用,首先vue3ref也能裝數據,只不過它是經過reactive來實現的,背後也仍是reactive

其實你們看個例子一眼就能看明白

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按鈕</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } console.log(refData) return { refData, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

能夠看到我在修改的時候改的是refData.value,沒錯,其實

let refData = ref(0); == let refData = reactive({value: 0 })

可是在模板裏用的時候就不用.value,畢竟vue 本身家的東西,至於vue爲何要這麼作,就要看最終發佈是什麼樣子了

確定有人會想,那我若是就是想獲得ref,好比一個input,該怎麼辦呢? 很簡單,這裏也直接上代碼,而後再解釋

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, watch, onMounted } = Vue var App = { template: ` <div class="container"> <input ref="input1" value="321" /> </div>`, setup() { const input1 = ref(null); console.log(input1.value) onMounted(()=>{ console.log(input1.value.value) }) return { input1 } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

這裏先是給這個ref一個初始的一個null,而後返回出去給到我們的元素,這也是我們在setup裏直接拿不到這個value的緣由,由於它還沒完事,何時能拿到,onMounted的時候,這個是生命週期相信你們都認識,生命週期在下文會說,這裏很少贅述

react熟悉的兄弟這時候頗有感受對吧?

Props

其實這個props沒有太多的變化,幾句話就能說清,參數約定也還在,像這樣

稍微有點區別的就是以前我們用props數據的時候,能夠直接this.props.xxx這樣獲取,如今他是直接放到setup的參數裏了,像這樣

固然看到這你們應該能感受到和什麼比較像了哈?

固然了,畢竟vue3是主推ts版的,因此固然ts這一套也全均可以用,好比這樣

固然,html裏確定是不能直接寫ts的,這裏就是說明意思

computed

這個computed有兩種寫法,一種是直接傳一個函數,這個其實就至關於過去computed只有get,因此當你設置值的時候,是會報錯的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, computed } = Vue var App = { template: ` <div class="container"> {{count}} <button @click="handleClick()">按鈕</button> </div>`, setup() { let refData = ref(0); let count = computed(()=>{ return refData.value; }) const handleClick = () =>{ count.value+=1 } console.log(refData) return { count, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

固然了,想和過去徹底同樣的寫法固然也能夠,也就是第二中寫法,直接傳一個json

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, computed } = Vue var App = { template: ` <div class="container"> {{count}} <button @click="handleClick()">按鈕</button> </div>`, setup() { let refData = ref(0); let count = computed({ get(){ return refData.value; }, set(value){ console.log(value) refData.value = value; } }) const handleClick = () =>{ count.value+=1 } console.log(refData) return { count, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

這基本上和2.x沒什麼區別了,就很少解釋了

不過稍微注意一點,這個computed自身不帶任何渲染,也就是說,若是這個refData 不是一個ref或者說是reactive的話,若是修改了數據,是會變沒錯,可是整個vue不會去從新渲染,這也是爲了靈活,若是這些東西都帶的話,也就沒有辦法去加強性能了,因此reactive纔是vue3整個的核心

readonly

這個用法其實跟上述的都差很少,調一下函數返回一個值,相似這樣

let Version = readonly(1.1.3)

這時候可能有人會以爲有些奇怪,爲何會有這個東西,直接用const或者tsreadonly不就行了麼,固然了,這確定是沒錯,用const聲明在這個函數裏改確實會報錯,可是別忘了,這個值我們是要在setup裏返回出去的,或者傳給別的地方,也就是經過vue中轉了一下,這時候就不行了,這也是有用的

watch

注意,這個watch必須監聽的是一個reactive的數據,若是是一個普通數據的話,用這個watch是監聽不出來的,而後我們就來試試

還用剛纔的例子來改一下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, watch } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按鈕</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) return { refData, handleClick } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

也是跟過去的時候差很少,不過稍微有點區別的是,我們有的時候多是某一個特殊的時間要監聽,過了這個特殊的時候就不監聽了,這個watch會返回一個函數,執行的話就會直接中止監聽

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, watch } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按鈕</button> <button @click="handleStop">中止</button> </div>`, setup() { let refData = ref(0); const handleClick = () =>{ refData.value+=1 } let stop = watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) const handleStop = () =>{ stop() } return { refData, handleClick, handleStop } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>

複製代碼

還蠻方便的對吧~

生命週期

其實我相信生命週期這東西你們仍是比較關心的,好比mounted直接寫在外面就能夠了,固然了,相信看到這你們應該明白vue3的寫項目的模式了,沒錯,也是一個函數,在setup裏調用 直接上代碼了

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="../packages/vue/dist/vue.global.js"></script>
  <div id="app"></div>
  <script> const { reactive, ref, watch, onMounted } = Vue var App = { template: ` <div class="container"> {{refData}} <button @click="handleClick()">按鈕</button> <button @click="handleStop">中止</button> </div>`, setup() { onMounted(()=>{ console.log('mounted~'); }) let refData = ref(0); const handleClick = () =>{ refData.value+=1 } let stop = watch(refData,(val, oldVal)=>{ console.log(val, oldVal) }) const handleStop = () =>{ stop() } return { refData, handleClick, handleStop } } } Vue.createApp().mount(App, '#app') </script>
</body>
</html>
複製代碼

仍是比較簡單的,固然了,有onMounted就有on別的,這都同樣的,除了那個卸載的更名了,叫onUnmounted

跨組件共享數據

vue2.x裏我們組件間共享數據是否是隻能props,稍微複雜點的共享就得用vuex,可是這個東西一多,就太亂了

因此vue3裏提供了另外兩個東西

provide - 提供數據

inject - 獲得數據

這裏我就直接寫僞代碼了

const userData = Symbol();
const Cmp1 = {
    setup(){
        let user = reactive({name: "aaa", age: 18});
        provide(userData, user);
    }
}

const Cmp2 = {
    setup(){
        const user = inject(userData, {});
    }
}
複製代碼

這個就很簡單了,你們應該能看明白,不過爲何要用Symbol呢,你們知道Symbol出來的東西永遠都是惟一的,這也是官方推薦的寫法,若是要是你純自定義,很容易重名或者打錯字母什麼之類的,因此我們只要保管好這個key,數據共享就會變的很方便

這個inject第二個參數就是默認值,也就是說沒有這個數據的時候默認值是什麼,這個仍是比較好理解的

這個多說一句,其實我我的是比較喜歡的,其實你們能夠仔細想一想,以前在寫vue的時候,不少程度的時間都在搞父子組件,什麼兄弟組件,這個那個的互相傳,很費勁,有了這個,就能夠徹底拋棄以前的寫法了,只須要想辦法怎麼把這個數據統一管理、聲明也好,省了不少麻煩事

雖然說2.x裏也有,不過官方也說了,推薦在高級組件裏或者庫裏用,也不推薦你寫,畢竟還有不少沒完善

總結

其實看到這你們應該都感受到了,vue3這回仍是很是注重這個setup函數的,其實這樣也有很大的好處,等於說vue此次把不少的決定權交給你了,再也不是全部的東西都是它背後作的了,也很方便

固然了,可能會有人以爲全部的東西全放到一個地方太亂了,其實也還好,由於你能夠本身去作模塊化什麼之類的,這個看你本身的管理了,不過我預感可能官方還會出一個什麼之類的規範

不過如今的小道消息也不可信,具體都還須要等具體vue3發版才能知道

可能有朋友比較喜歡vue 2.x的裝飾器寫法,這裏官方說明了,裝飾器如今畢竟還不穩定,框架上也有一下爛七八糟的不方便,this什麼的,對繼承也沒什麼幫助,因此vue3就直接捨棄class寫法了,不過。。。暫時吧,具體怎麼樣仍是得看上線了以後

最後,安利一個vue3的文章,也是我一個朋友寫的,你們若是對vue3還有興趣,能夠去看看 juejin.cn/post/684490…

有什麼問題歡迎在評論提問,或者加我qq或者微信,一塊兒溝通

qq

916829411
複製代碼

微信

Thank You

相關文章
相關標籤/搜索