淺談vu3中的多種組件形式

前言

自從vue3徹底支持函數(shù)式編程后,終于支持jsx的寫法了。寫過react的朋友可能更容易理解無狀態(tài)組件與有狀態(tài)組件的區(qū)別。對寫慣了vue2對象式編程的人來說,可能對于函數(shù)式組件還有些一知半解,時常不知道普通函數(shù)與jsx函數(shù)有什么區(qū)別,畢竟習慣了在.vue文件中的template一把梭。分不清在函數(shù)組件中直接返回jsx模板與返回一個對象又有什么區(qū)別。這篇文章我將淺談一下自己的理解,希望為你解惑。

*基礎(chǔ)好的朋友可直接閱讀第四章

一、腳手架的.vue文件是什么

這里我們不討論腳手架具體是如何處理.vue文件的,這個涉及到打包器的知識,感興趣的朋友可以自行拓展了解。我們在基于vite或webpack的腳手架中開發(fā)vue時,能夠很方便的在.vue文件中開發(fā),一個.vue文件的基本三要素就是“template”、“style”以及“script”。在其他vue文件中使用時,只需要 import A from 'A.vue',然后在template模板中寫上 <A />即可渲染。這是一個大家再熟悉不過的流程,可.vue文件的本質(zhì)是什么呢?我們不妨輸出一下我們引入的組件:

Test.vue
<template>
  <div>測試</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>

在App.vue中引入Test.vue,然后輸出Test
<script setup lang="ts">
    import Test from './Test.vue';
    console.log(Test);
</script>
查看瀏覽器中的輸出結(jié)果
image.png

我們可以很清楚的看到,我們打印的Test輸出了一個對象,并且對象中存在一個render函數(shù),其他兩個帶下劃線的我們暫且忽略。這里最重要的是一個render對象。 我們由此可見,在模板中引入的vue組件,實際上是一個帶有render函數(shù)的對象。也就是說,vue在執(zhí)行我們的組件時,會去對象中找render函數(shù)并且執(zhí)行它,render函數(shù)會返回一個Vnodes渲染樹,Vue就能根據(jù)這個樹進行渲染顯示。

二、render函數(shù)是什么

在vue中,render函數(shù)實際上返回的是一個h函數(shù)返回的虛擬dom樹,實際上就是一個對象。它里面存儲了很多dom的節(jié)點信息,這些信息都是虛擬的。這里我們不考慮vue的template,我們暫時把它忘掉。這里我們考慮一個場景,我給定你一個對象,例如

const obj = {
  div:{
    style:"color:red;font-size:13px"
  }
}

我的需求是,希望你用這個對象,使用你知道的所有javascript知識,將它渲染到網(wǎng)頁里。
這里我就不貼代碼了。
我相信對你來說這是一件很簡單的事情,不就是讀取這個obj對象,然后創(chuàng)建相對應(yīng)的div標簽,接著將style設(shè)置為對象中的style,最后append到dom容器里不就好了嗎?
是的,你的做法以及思想都完全沒有錯,而且這也不算很有難度。那么,如果我希望在div下再渲染一個span呢?像這樣:

const obj = {
  div: {
    style: 'color:red;font-size:13px',
    children: [
                   { span: { style: 'color:red', content: '內(nèi)容' } },
                   { span: { style: 'color:red', content: '內(nèi)容2' } }
              ]
          },
      };

你可能注意到,我在div下增加了一個叫children的數(shù)組,為什么是數(shù)組?因為div下不可能只有一個元素嘛!那么現(xiàn)在我希望在div下渲染出這兩個span,并且content 內(nèi)容也要顯示在標簽里,你會怎么做呢?
我覺得這個對于看我文章的人來說,應(yīng)該也是毫無壓力的,無非就是處理div的時候,順便找一下有沒有children,有的話就一起處理,最后一起append到dom容器里就好了嘛!

你真是個天才!根本難不倒你,你的想法完全可行。

可是content內(nèi)容不可能一直不變,如果我需要修改span中的內(nèi)容,那么你又得重新執(zhí)行一遍你剛剛的處理的流程,我假設(shè)你很聰明的把剛剛對于obj的處理寫了一個函數(shù),那就意味著,你會想辦法監(jiān)聽到我對obj到更改。只要有更改,你就會重新執(zhí)行,確保頁面顯示的跟我的obj對象是一致的。

如果你真的這么做了,當obj這個對象所表達的標簽足夠多時,可能只是其中一個標簽中的content改變,你就需要重新運行整個函數(shù),然后整個處理完再全部append。你的頁面性能將會迎來瓶頸,說人話就是卡到爆。

你會如何處理這個性能問題呢?

我覺得可以自己悄悄再寫一個函數(shù),專門用來緩存上次保存的obj對象,當原始obj對象更改時,我將緩存里的obj對象和更改的obj對象先進行一次比對,找出實際被更改的那個標簽,然后單獨對這個標簽進行append。這樣,我就可以做到只對有更改的標簽進行append到頁面上,做到了局部的DOM更新。 很棒對吧?!

如果你也覺得上面這個方案很棒,尤雨溪也是這么覺得的,這就是我們所謂的diff算法。

看到這里,實際上我們已經(jīng)實現(xiàn)了最基本的數(shù)據(jù)驅(qū)動視圖,我們只需要控制obj這個對象,具體的渲染更新我們交給了事先封裝好的針對處理obj的函數(shù)(我假設(shè)你封裝好了)。

我們剛剛討論的那個obj,里面的結(jié)構(gòu)完全是我定義的,你是被動的。雖然可以實現(xiàn)想要的效果,但每個人都有自己的想法。比如那個content,你偏偏改成text或者value來表達不行嗎? 那當然行啦!誰能犟過你啊。 但是無規(guī)矩不成方圓,既然這個方案很好用,能實現(xiàn)數(shù)據(jù)驅(qū)動視圖的最基本方案。是不是應(yīng)該大力推廣呢?那用的人一多,那就必須有一套規(guī)范了,可不能亂來。

這套規(guī)范叫hyperscript,簡稱為h,就是我們熟悉的h函數(shù)啦。實際上,h函數(shù)就是我們上面討論規(guī)范后的結(jié)構(gòu)。具體的可以參考vue官方文檔:https://cn.vuejs.org/guide/extras/render-function.html#creating-vnodes

為什么是h函數(shù)而不是對象?
在實際開發(fā)中,框架除了要處理我們的模板外,還要處理很多其他的邏輯。比如說,你寫了一個虛擬DOM表達對象:{type:"div",....},但一個成熟可用的框架要做的事情很多,例如vue可能還需要維護虛擬DOM的唯一性,需要往對象里插入更多屬性配合性能優(yōu)化。所以vue自身提供了一個h函數(shù),我們可以把虛擬dom表達在函數(shù)中。vue調(diào)用h函數(shù)會經(jīng)過一層處理,再次返回一個可用的虛擬DOM樹,也就是VNode。同樣,在vue3中使用jsx時,它的邏輯也是h函數(shù)的語法糖,最終vue拿到的都需要是一個Vnode。也就是一個實際可用的虛擬DOM樹。

寫了這么多,終于可以回到主題了,什么是render函數(shù)? 其實render函數(shù)返回的就是我們上面寫的obj對象,只不過vue自身封裝了一個h函數(shù),h函數(shù)基于hyperscript規(guī)范同時又增加了vue中特有的字段,比如props。最終h函數(shù)會返回一個vue實際可用的Vnode虛擬DOM對象??偟膩碚f,render是返回一個可執(zhí)行的h函數(shù),h函數(shù)返回一個Vnode對象,對象結(jié)構(gòu)里表達了各種標簽渲染規(guī)則。

還記得一開始我打印的組件嗎?留給大家一個小問題:為什么vue調(diào)用render返回h函數(shù),為什么不直接命名為h而是命名為render呢?

三、什么是JSX

我們上面聊過h函數(shù),在vue中,它就是接收hyperscript基礎(chǔ)規(guī)范語法的函數(shù)。并且h函數(shù)返回一個vue可用的虛擬DOM樹。對象里描述了將來渲染真實html的規(guī)則。又由于對象可以層層嵌套描述更多html規(guī)則,所以我們把這種對象稱之為虛擬DOM樹。為什么說是虛擬?因為它是一個js對象,并不是真實dom,真實dom是需要你根據(jù)這個對象再去處理渲染的。又因為它層層嵌套很像一棵樹分叉,所以我們稱之為虛擬DOM樹。
說這么多,那什么是jsx呢?要不說程序員偷起懶來,就會促進科技的進步呢。實際上JSX就是一種偷懶的結(jié)果。我們先看下面這個示例:

const test = h('div', { id: 'foo' }, 'hello')

這是一個很簡單的h函數(shù),里面描述了一個div,它代表根節(jié)點。自身id為"foo",并且這個div內(nèi)有一個文本叫"hello"。我把這個h函數(shù)用test包裝起來,Vue的h函數(shù)將會為我們返回vue可執(zhí)行的虛擬DOM樹,將來它渲染出html就是以下樣式:

<div id="foo">
  hello
</div>

看起來結(jié)果很好,但現(xiàn)在我們只有一個div。我們知道在實際開發(fā)中,要寫完整個頁面可不止這一個div。那總不可能所有寫頁面過程都面向這個h函數(shù)吧,要一個個手寫對象去描述html結(jié)構(gòu),想想就頭大了。 所以這個時候jsx來了:

const test = <div id="foo">hello</div>

是的你沒看錯,我把html丟到了變量里。那有朋友就要問了:這么寫js不會報錯嗎?
會!而且大錯特錯!js才不認識你這堆玩意兒呢。js里有這種奇葩的做法嗎?再不濟寫到j(luò)s里也應(yīng)該用字符串包裹一下吧,你這直接扔過來算什么?報錯是必然的。
所以,JSX它注定要經(jīng)過一層編譯處理。無論你是在原生scirpt里寫jsx語法還是在腳手架里寫jsx語法。要么你就用scirpt引入解析jsx的插件,要么你就是在腳手架里配置好jsx的插件。無論如何,它都必須要經(jīng)過一層處理。
處理什么?如何處理?實際上,就是把jsx的語法轉(zhuǎn)換為h函數(shù),然后再交給實際要處理h函數(shù)渲染的邏輯去處理。
說到這里,肯定有朋友要問了:那既然jsx還得轉(zhuǎn)換成h函數(shù)才能執(zhí)行,那不是比直接寫h函數(shù)執(zhí)行效率更低了嗎?
我說朋友,你要這么想,也沒錯。但是jsx我們一般是在開發(fā)環(huán)境使用,畢竟一般開發(fā)react和vue的都上腳手架了是吧。實際上在我們最后執(zhí)行打包操作后,打包完的代碼里是沒有jsx的,所以在打包后的運行代碼是沒有性能消耗的,也就是開發(fā)運行時耗一點性能。再說這都2024了,你是要jsx呢?還是要自己手寫h函數(shù)里的DOM結(jié)構(gòu)描述對象?
jsx作為h函數(shù)的語法糖,它并不只是帶給我們布局更方便的好處。由于我們將布局揉合到了js里,也就意味著可以在jsx模板里一起把變量渲染的邏輯做了。
比如:

const name = '張三'
const test = <div id="foo">{name}</div>

渲染結(jié)果:

<div id="foo">
 張三
</div>

是不是很方便?那有同學說了,如果我變量更新了,如何保證重新渲染呢?
我們之前討論過,jsx只不過是h函數(shù)的語法糖,而h函數(shù)也只不過是返回一個標準化的虛擬DOM樹而已。說人話就是 只是統(tǒng)一了數(shù)據(jù)規(guī)范。至于如何處理jsx中的邏輯,不同的框架語言有自己的優(yōu)化邏輯。比如react能搭配hooks和state對虛擬DOM樹進行動態(tài)更新渲染。同樣vue3中的ref也可以搭配jsx使用,就像vue3的h函數(shù)在返回可用虛擬DOM之前,會往對象里插入更多其他的屬性,這些都是vue的一部分。
總之,jsx是h函數(shù)的語法糖。除此之外 我們需要搭配框架語言本身提供的功能使用。也就是說,在vue3里寫jsx跟react里最簡單的不同點是,你們可以在jsx基本模板語法保持一致的情況下,可以使用自身框架的狀態(tài)管理和hook來編寫代碼。

四、VUE多種組件形式之:函數(shù)式組件(無狀態(tài)組件)

函數(shù)式組件通常用來做無狀態(tài)組件和有狀態(tài)組件,這里并不是說.vue文件那種不能寫無狀態(tài)和有狀態(tài)無狀態(tài)組件,只是在無狀態(tài)組件比較簡單時,更優(yōu)先采用函數(shù)式。在vue3中,平時當我們編寫組件時,我們會在組件內(nèi)寫很多響應(yīng)式變量,可能還會寫點擊事件改變某些變量從而重新渲染頁面。這種有自己內(nèi)部響應(yīng)式變量需要管理維護的組件叫有狀態(tài)組件。而相反,如果一個組件它只是接收一個參數(shù),然后渲染到頁面,自身并沒有定義響應(yīng)式變量和方法來異步控制頁面更新,那么這種就叫無狀態(tài)組件。 類似組件比如時間轉(zhuǎn)換組件,可能你有一個組件接收一個時間props,處理后展示到頁面上,僅此而已沒有其他變量狀態(tài)需要管理。這種我們就可以稱之為無狀態(tài)組件,它就兩件事 1.接收變量2.處理變量渲染。也有可能就一件事:渲染靜態(tài)。
沒有任何自身其他的響應(yīng)式變量需要管理。

一般無狀態(tài)組件,很適合編寫一個函數(shù),然后函數(shù)內(nèi)返回一個jsx模板,比如下面的示例:

假設(shè)有個index.jsx有以下代碼:
export default  function HandleTime(){
  return <div>2024年x月x日<div>
}

這是一個很普通的函數(shù)式組件,那如何使用這個組件呢?
假設(shè)我們有個index.vue:

<templat>
      <HanldeTime />
</template>
<script  setup>
import HanldeTime from 'index.jsx'
</script>

以上代碼會在頁面渲染出以下結(jié)果

<div>2024年x月x日</div>

這是很顯而易見的結(jié)果,但是 我編寫的HandleTime不是一個函數(shù)嗎?為什么vue能把我的函數(shù)當作模板渲染?
在第一小節(jié)中,我打印了一個.vue后綴的組件,在瀏覽器中能看到它實際上是一個對象,里面有個render方法。所以我們其實可以得到一個結(jié)論:vue的.vue后綴文件被解析成一個對象,并且在解析過程中,template會被解析成render函數(shù)。那render函數(shù)又返回什么呢?
在第二小節(jié)我已經(jīng)給了解答,實際上render返回的是一個h函數(shù)。而h函數(shù)又是返回一個包裝好的虛擬DOM樹,也就是Vnode,vue拿到Vnode就能執(zhí)行渲染邏輯了。

那到底為什么vue可以把HanldeTime函數(shù)當作模板渲染?
因為一個我編寫的函數(shù)組件,采用的是jsx的方式返回

 return <div>2024年x月x日<div>

這一段return實際上就是jsx,然而我們前面總結(jié)過,在vue中jsx其實就是h函數(shù)的語法糖。 而我們又看到一個組件輸出的是一個對象里面包含render,調(diào)用render仍然也是為了拿到h函數(shù)的結(jié)果得到Vnode。所以如果我們函數(shù)里直接返回了jsx也就意味著直接返回了Vnode,所以它可以直接放在模板中當作組件渲染。

既然如此,如果項目中沒有用到j(luò)sx,應(yīng)當如何返回一個無狀態(tài)組件?
那就不得不手動引入vue提供的h函數(shù)了,你可以用以下寫法

import { h } from 'vue'
export default  function HandleTime(){
  return h('div','2024年x月x日')
}

引入這個HandleTime,渲染的效果跟用jsx是一樣的,因為jsx就是h函數(shù)的語法糖。在沒有jsx的情況下,用h函數(shù)來應(yīng)急也是不錯的選擇,但不建議大量在項目中使用,是真的很難維護。

五、VUE多種組件形式之:函數(shù)式組件(有狀態(tài)組件)

我們了解過無狀態(tài)函數(shù)組件后,總會發(fā)現(xiàn)一個組件不能傳參、不能管理自己的變量的話,使用場景會變得很局限。所以如何給一個函數(shù)式組件傳參?
其實很簡單:

<templat>
      <HandleTime name='張三' />
</template>
<script  setup>
import HandleTime from 'index.jsx'
</script>  

我們可以把vue的模板使用當作函數(shù)的調(diào)用來看待,這里其實就是調(diào)用了HandleTime這個函數(shù),并且將name當作函數(shù)參數(shù)傳遞進去了。

export default  function HandleTime(props){
  return <div>{props.name}<div>
}

參數(shù)會變成一個對象傳遞進來,在HandleTime這個函數(shù)中接收,直接使用這個name就可以了。
那有人會問了,如果外面的name變化了怎么辦?
其實,在vue3中,只需要保證你傳遞的這個name是響應(yīng)式變量,當name更新時,HandleTtime組件就會重新渲染。
比如你可以這么寫:

<templat>
      <HandleTime :name='name' />
</template>
<script  setup>
import {ref} from 'vue
import HandleTime from 'index.jsx'
const name = ref('張三')
</script> 

我把name用ref包裹,并且傳遞到HandleTime組件中,后續(xù)只要name更新,組件內(nèi)也會跟著一起重新渲染。

到此為止,我們其實還算是無狀態(tài)組件,無非就是傳遞了一個參數(shù)進去,如果Handle需要自己的響應(yīng)式變量怎么辦?
其實也很簡單,直接引用vue的函數(shù)就好了,這也是vue3的特點:

import { ref}  from 'vue'
export default  function HandleTime(props){
  const count = ref(0)
  return <div onClick=()=>{ count++ }>{props.name}<div>
}

這就是一個最基礎(chǔ)的有狀態(tài)組件,我們可以引入Vue的hook來搭配使用。是不是很簡單?

*問題解答:為什么vue調(diào)用render返回h函數(shù),為什么不直接命名為h而是命名為render呢?

因為.vue的文件中,render只是轉(zhuǎn)換了你的template模板代碼,render實際上是不包含你的業(yè)務(wù)代碼的,包括生命周期也不包含在內(nèi)。render負責渲染html那么就只是需要返回一個h函數(shù)的Vnode即可。 除了渲染工作,還有其他的事情要做,所以重新定義一個render函數(shù),可以在render函數(shù)中做很多中間處理,最后只需要返回一個可用的Vnode即可。類似于:

{
  render(){
    return h('div')
  }
}

而在render中還可以做很多中間操作

{
  render(){
    const name = 'content'
    return h('div',name)
  }
}

vue在渲染時,調(diào)用render即可拿到需要渲染的Vnode信息,而除了render,vue還可以在這個對象里存儲其他的數(shù)據(jù),比如生命周期或一些私有變量。
這里也就意味著,我們直接用函數(shù)式組件 等于就直接返回了Vnode,不需要再次使用render來包裝,我們打印一個函數(shù)式組件會發(fā)現(xiàn)控制臺有如下輸出:


image.png

這就可以證明,函數(shù)式組件確實沒有render,而是直接返回的一個可執(zhí)行的函數(shù),并且里面就是Vnode,執(zhí)行就可以渲染。

但如果在特殊情況下,我們的jsx渲染或者組件不得不自己寫在對象里,也是可以起名叫render的,畢竟這個單詞它好理解,直觀。

六、VUE多種組件形式之:對象式組件

細心的朋友可能發(fā)現(xiàn)了,講了半天函數(shù)式組件,生命周期呢?昂?生命周期呢?
不好意思,函數(shù)式組件沒辦法使用生命周期hook。
想要在組件中使用生命周期,我們先看文檔是怎么說的:


image.png

什么意思呢?意思就是說,生命周期必須要在setup周期內(nèi)執(zhí)行,生命周期鉤子也可以封裝到其他函數(shù)里,但前提是函數(shù)調(diào)用必須在setup周期內(nèi)。但請大家注意了,它這里指的是.vue模板開發(fā)中,你可以把生命周期鉤子寫在別的函數(shù)里,在setup作用域使用即可。 但我們這個函數(shù)式組件是直接給模板使用了,不存在什么setup,在函數(shù)式組件里寫生命周期是無效的。

但同時它也給出了答案,在setup上下文中使用即可。
還記得vue的對象式編程剛轉(zhuǎn)vue3的時候嗎?是不是用setup函數(shù)代替的?那時候還沒有script setup這個用法。這里,我們也可以這樣使用:

在index.jsx中
import {onMounted} from 'vue'
export default {
  setup(){
        onMounted(()=>{
              console.log('生命周期加載')
          })
        return '<div>你好</div>'
    }
}

同樣在模板中

<template>
  <HandleTime />
</template>
<script setup>
  import  HandleTime from 'index.jsx'
<script>

有朋友會細心的發(fā)現(xiàn),不對啊,setup最終return不是代表對外暴露的變量嗎?
的確是,我們回想一下,在vue3剛出來的時候,我們是不是可以在寫template的情況下,然后對象上加一個setup就好了? 但是我們這邊是不是沒有template了,我們也沒辦法使用template,因為我們這不是約束在.vue模板中了,那沒有template了怎么辦?
這里的問題還是,template是什么?我們前面討論過,templte就是render函數(shù)的語法糖,而render返回的是h函數(shù)。h函數(shù)又會返回Vnode。沒錯吧?所以沒有了template,我們需要手動寫上render,所以以下的代碼也是可用的:

import {onMounted} from 'vue'
export default {
    render(){
      return '<div>你好</div>'
    },
    setup(){
        onMounted(()=>{
              console.log('生命周期加載')
          })
      
    }
}

而以上寫法,會使得變量的傳遞顯得異常的麻煩,因為上面的寫法不多,這里不再過多贅述,我們了解一下本質(zhì)就可以。

因為變量傳遞過于麻煩,所以vue3還支持在setup中直接return返回模板,至于變量我們在setup中做就可以了,生命周期也能用了。

那么問題來了,本身setup返回的是對外暴露的變量,這么一寫,還怎么暴露變量?現(xiàn)在都默認暴露的是JSX了,也就是Vnode。 怎么辦?

我們可以使用expose函數(shù),它不是一個需要顯示導入的hook,以下是官方示例用法:

export default {
  setup(props, { expose }) {
    // 讓組件實例處于 “關(guān)閉狀態(tài)”
    // 即不向父組件暴露任何東西
    expose()

    const publicCount = ref(0)
    const privateCount = ref(0)
    // 有選擇地暴露局部狀態(tài)
    expose({ count: publicCount })
  }
}

此時我們可以用expose替代原本的return暴露屬性,return我們就用來返回一個jsx。 具體的細節(jié)可以查看官方文檔:https://cn.vuejs.org/api/composition-api-setup.html#usage-with-render-functions

不知道大家注意到?jīng)]有,我導出的是一個對象。而函數(shù)組件我導出的是一個函數(shù),無論是函數(shù)還是對象,vue都可以正常解析,這里面的基礎(chǔ)邏輯是什么呢?可以總結(jié)為:
當vue模板渲染時,如果拿到的是一個函數(shù),會嘗試執(zhí)行它,如果返回的不是一個Vnode,將會在控制臺報錯,如果拿到的是Vnode,將會正常渲染成html。
如果拿到的是一個對象,vue會去找這個對象的render函數(shù),如果沒有render函數(shù)將會執(zhí)行setup,最終拿到setup返回的Vnode。

七:VUE多種組件形式之:輔助函數(shù)包裝組件:defineComponent

輔助函數(shù)包裝組件這個名字是我自己取的,我也不知道具體如何稱呼它,但也是字面意思,它通過vue提供的輔助函數(shù)編寫組件。
它的作用主要是為Ts提供類型推導,以下是官方語法:

import { ref, h } from 'vue'

const Comp = defineComponent(
  (props) => {
    // 就像在 <script setup> 中一樣使用組合式 API
    const count = ref(0)

    return () => {
      // 渲染函數(shù)或 JSX
      return h('div', count.value)
    }
  },
  // 其他選項,例如聲明 props 和 emits。
  {
    props: {
      /* ... */
    }
  }
)

它接收兩個參數(shù),參數(shù)1可以當setup函數(shù)使用,最終返回一個Vnode,也就是JSX。但它這里沒有基于jsx,所以返回的是h函數(shù)。如果有引入jsx這里直接返回jsx就可以。
第二個參數(shù)是組件的一些選項,這里不再過多贅述了,就是提供多了一些TS支持,如果有需要可以用defineComponent包裝一下,它既支持生命周期又支持TS推導,也是一個不錯的選擇,不過我比較少用。
具體的可看文檔了解:https://cn.vuejs.org/api/general.html#definecomponent

最后

這篇文章我嘗試從vue的h函數(shù)開始到JSX到templat模板語法,接著給大家介紹基于這些概念的幾種組件編寫方式。知識點多而雜,如有理解錯誤的地方 還請大家指出,多多包涵。感謝你的耐心閱讀。

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容