【拓展】Next.js + TypeScript 搭建一個簡易的博客系統(tǒng)
最近想攻關(guān)一個 node.js 框架。希望找到一個能夠幫我們把大部分事情都做好的框架,可以直接上手快速開發(fā)。不像傳統(tǒng)的 Express、Koa 需要配置大量中間件。按照這個想法,谷歌了一下就是 —— Next.js 了。最后完成了一個簡易的博客系統(tǒng),
代碼地址: https://github.com/Maricaya/nextjs-blog
預(yù)覽地址:http://121.36.50.175/
不得不說 SSR 真香,幾乎沒有白屏?xí)r間,加載非???。
來記錄下學(xué)習(xí)(踩坑)的過程,這篇文章的代碼都在https://github.com/Maricaya/nextjs-blog-1啦。
先來看看 Next.js 是什么吧。
Next.js 是一個全??蚣?/h1>
Next.js 是一個輕量級的 React 服務(wù)端渲染應(yīng)用框架。
它支持多種渲染方式:客戶端渲染、靜態(tài)頁面生成、服務(wù)端渲染。
使用Next.js 實現(xiàn) SSR 是一件很簡單的事,我們完全可以不用自己去寫webpack等配置,Next.js 都幫我們做好了。
弱項
上面討論了 Next.js 的很多優(yōu)點,但每個框架都有不完美的地方,尤其是在 Node.js 社區(qū)。
作為一個后端框架,Next.js 完全沒有提供操作數(shù)據(jù)庫的相關(guān)功能,只能自行搭配其他框架。(比如 Sequelize 或者 TypeORM)。
也沒有提供測試相關(guān)功能,也需要自行搭配,可以選擇 Jest 或者 Cypress。
現(xiàn)在我們基本了解了 Next.js,接下來跟著官網(wǎng)做一個簡單的項目吧。
創(chuàng)建項目
#?nextjs-blog-1?是我們的項目名稱
npm?init?next-app?nextjs-blog-1
選擇 Default starter app。
進入 nextjs-blog-1,用命令行啟動項目?yarn dev。
看到下面這個頁面?,就說明你的項目啟動成功啦。

下面我們?yōu)轫椖考由?TypeScript!
啟動 TypeScrip!
第一步就是安裝 TypeScript。
yarn?global?add?typescript
創(chuàng)建 tsconfig.json
然后我們運行?tsc \--init,得到 tsconfig.json,這是 TypeScript 的配置文件。
接下來安裝類型聲明文件,然后重啟項目。
yarn?add?--dev?typescript?@types/react?@types/node
yarn?dev
然后我們將文件名 index.js 改為 index.tsx。
創(chuàng)建第一篇文章
根目錄下創(chuàng)建 posts 文件夾,我們的文章放在這個路徑下。
創(chuàng)建 posts/first-post.tsx 文件,寫入代碼:
//?第一篇文章
import?React?from?"react"
import?{NextPage}?from?'next';
const?FirstPost:?NextPage?=?()?=>?{
??return?(
????First?Post</div>
??)
}
export?default?FirstPost;
這個時候訪問?http://localhost:3000/hosts/first-post?就能看見頁面了。
Link 快速導(dǎo)航
官網(wǎng)中介紹了 Link 快速導(dǎo)航。
稍微了解前端同學(xué)們可能會有這樣的問題,不是有 a 標(biāo)簽可以導(dǎo)航嗎,Next.js 為什么要多此一舉。
據(jù)官網(wǎng)介紹,Link 可以實現(xiàn)快速導(dǎo)航。我們來做個實驗,看看它和 a 標(biāo)簽有什么不同。
先在項目分別中使用 a 標(biāo)簽、Link 標(biāo)簽導(dǎo)航,實現(xiàn)首頁和第一篇文章互相跳轉(zhuǎn)。
index.tsx
<h1?className="title">
??第一篇文章
????<a?href="/posts/first-post">a?點擊這里a>
????<Link?href="/posts/first-post"><a?>link?點擊這里a>Link>
h1>
/posts/first-post.tsx
//?回到首頁
<hr/>
<a?href="/">a?點擊這里a>
<Link><a?href="/">link?點擊這里a>Link>
點擊 a 標(biāo)簽,每次進入 first-post、index 頁面,瀏覽器都會重新請求所有的 html、css、js。

接下來使用 Link 標(biāo)簽導(dǎo)航,神奇的事情發(fā)生了,瀏覽器只發(fā)送了 2 個請求。

第二個請求是 webpack,所以真實的請求只有 1 個,就是 first-post.js。
反復(fù)在兩個頁面中跳轉(zhuǎn),除了 webpack,瀏覽器沒有發(fā)出任何請求。
Next.js 到底做了什么?快速導(dǎo)航和傳統(tǒng)導(dǎo)航有什么區(qū)別?
傳統(tǒng)導(dǎo)航
我們先來看看從 page1 到 page2,傳統(tǒng)導(dǎo)航是怎么實現(xiàn)的?

訪問第一個頁面 page1 時,瀏覽器請求 html,然后依次加載 css、js。
當(dāng)用戶點擊 a 標(biāo)簽,就重定向到 page2,瀏覽器請求 html,然后再次加載 css、js。
Link 快速導(dǎo)航
再看相同的過程,Next.js 中的快速導(dǎo)航是怎么實現(xiàn)的。

首先訪問 page1,瀏覽器下載 html,然后依次加載 css、js。這些和傳統(tǒng)導(dǎo)航一樣。
但是當(dāng)用戶點擊 Link 標(biāo)簽時, page1 會執(zhí)行一個 js,這個js 會對 Link 標(biāo)簽進行解析,點擊 Link 之后請求 page2 的 page2.js,這個 page2.js 就是 page2 的 html+css+js。
請求完 page2.js 之后,會回到 page1 的頁面,把 page2 的 html、css、js 更新到 page1 上。也就是把 page1 更新為 page2。
所以,瀏覽器沒有親自訪問過 page2,而是 page1 通過 ajax 來獲取 page2 的內(nèi)容。
優(yōu)點
所以,Link 快速導(dǎo)航(客戶端導(dǎo)航)有這么多優(yōu)點:
頁面不會刷新,用 AJAX 請求新頁面內(nèi)容。 不會請求重復(fù)的 HTML、CSS、JS。 自動在頁面插入新內(nèi)容,刪除舊內(nèi)容。 因為省了一些請求和解析過程,所以速度極快。
同構(gòu)代碼
什么是同構(gòu)?
同構(gòu)是指同開發(fā)一個可以跑在不同的平臺上的程序, 這里指 js 代碼可以同時運行在 node.js 的 web server 和瀏覽器中。
也就是代碼運行在兩端。
做個試驗,我們在組件里寫一句?console.log('aaa')。
結(jié)果 Node 控制臺、Chrome 控制臺都會打印出?aaa。
注意差異
但并不是所有的代碼都會運行在兩端。
比如需要用戶觸發(fā)的代碼,只會運行在瀏覽器端。
我們的代碼也不能隨意編寫,必須保證在兩端都能運行。比如?window,在 Node.js 中沒有這個對象,就會報錯。
優(yōu)點
減少代碼開發(fā)量, 提高代碼復(fù)用量。
一份代碼能同時跑在瀏覽器和服務(wù)器,因此代碼量減少了。 業(yè)務(wù)邏輯也不需要在瀏覽器和服務(wù)端同時維護,減小了程序出錯的可能。
全局配置 Head, Metadata, CSS
Head
title
我們想讓頁面的 title 不同,應(yīng)該怎么配置?
在 Head 中配置 title,Head 會幫我們寫入 title。
我的博客
Metadata
meta 也是一樣
<Head>
????<meta?name="viewport"?content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"/>
Head>
但是目前 index.tsx 和 first-post.tsx 是兩個文件,難道要寫入兩遍嗎?有沒有統(tǒng)一寫入的方法?
全局配置
創(chuàng)建 pages/_app.js,從官網(wǎng)上抄下代碼,寫入我們的 tie然后重啟 yarn dev。
export?default?function?App({?Component,?pageProps?})?{
??//?Component
??return?<div>
<Component?{...pageProps}?/>
div>
}
其中 Component 就是我們定義的 index 和 first-post;pageProps 是頁面的選項,目前是空對象。
export default function App?是每個頁面的根組件。頁面切換時 App 不會銷毀,App 里面的組件會銷毀。我們可以用 App 保存全局狀態(tài)。
CSS
也是一樣,全局的 CSS 放在 _app.js 中。因為切頁面的時候 App 不會被銷毀,其他地方只能寫局部 CSS。
imprort?'../styles/global.css'。
絕對引用
寫相對路徑有點麻煩,能不能指定根目錄寫絕對路徑呢?翻了翻官網(wǎng),發(fā)現(xiàn) Next.js 提供了類似的功能。
配置 tsconfig.json,定義根目錄。
{
??"compilerOptions":?{
????"baseUrl":?"."
??}
}
重啟項目,就可以絕對引入 css 啦:
imprort?'styles/global.css'
靜態(tài)資源
next 推薦放在 public/ 里,但是我并不推薦這種做法,因為不支持改文件名。
有前端基礎(chǔ)的同學(xué)就知道,不支持改文件名,會影響我們的緩存策略。
如果 public 中的靜態(tài)資源沒有加緩存,這樣每次請求資源都會去請求服務(wù)器,造成資源浪費。
但是如果加了緩存,我們每次更新靜態(tài)資源就必須更新資源名稱,否則瀏覽器還是會加載舊資源。
所以,我們在根目錄新建 /assets 來放置靜態(tài)資源,并且需要在 next.js 中配置 webpack。
根據(jù)官網(wǎng),在根目錄創(chuàng)建?next.config.js,自定義 webpack 配置。
圖片
配置 image-loader
配置?file-loader。
安裝?yarn add \--dev file-loader。
next.config.js
module.exports?=?{
??webpack:?(config,?options)?=>?{
????config.module.rules.push({
??????test:?/\.(png|jpg|jpeg|gif|svg)$/,
??????use:?[
????????{
??????????loader:?'file-loader',
??????????options:?{
????????????//?img?路徑名稱.hash.ext
????????????//?比如?1.png?路徑名稱為
????????????//?_next/static/1.29fef1d3301a37127e326ea4c1543df5.png
????????????name:?'[name].[contenthash].[ext]',
????????????//?硬盤路徑
????????????outputPath:?'static',
????????????//?網(wǎng)站路徑是
????????????publicPath:?'_next/static'
??????????}
????????}
??????]
????})
????return?config
??}
}
直接使用 next-images
如果不想自己配置,也可以直接使用 next-images。
yarn?add?--dev?next-images
next.config.js
const?withImages?=?require('next-images')
module.exports?=?withImages({
??webpack(config,?options)?{
????return?config
??}
})
使用方法
TypeScript
現(xiàn)在導(dǎo)入圖像的文件還是會報錯,因為我們使用了 TypeScript,而 Typescript 不知道如何解釋導(dǎo)入的圖像。
next-images 很貼心地準(zhǔn)備了圖像模塊的定義文件。
所以,我們只需要在?next-env.d.ts?文件中添加 next-images 類型的引用就好啦。
///?
更多的其他文件
自己找到 loader,然后配置 next.config.js,或者看看有沒有封裝成 next 插件。
這些屬于 webpack 的范圍,大家可以自己探索。這篇文章就不啰嗦了。
Next.js API
到現(xiàn)在為止,我們的 index 和 posts/first-post 都是 HTML 頁面。
但實際開發(fā)中我們需要請求 /user、 /shops 等 API,它們返回的內(nèi)容是 JSON 格式的字符串。在 Next.js 中怎么實現(xiàn)呢?
使用 Next.js 的 API 模式。
使用 Next.js API
demo
API 的默認(rèn)路徑為 /api/v1/xxx,我們新建一個測試接口 demo.ts 。
在 api 目錄下的代碼只運行在 Node.js 里,不會運行在瀏覽器中。
demo.tsx
//?ts?就是加上了類型
import?{NextApiHandler}?from?'next';
const?Demo:NextApiHandler?=?(req,?res)?=>?{
????//?其他的操作和?js?一樣
??res.statusCode?=?200;
??res.setHeader('Content-Type',?'application/json');
??res.write(JSON.stringify({name:?'狗子'}));
??res.end();
};
export?default?Demo;
訪問?http://localhost:3000/api/demo,得到數(shù)據(jù)。

posts
接下來我們完成一個正式博客 API,posts 接口。
首先準(zhǔn)備博客文件,根目錄下創(chuàng)建 markdown 文檔,寫入幾篇 md 格式的博客。
然后我們借助 gray-matter 從 md 文件中解析數(shù)據(jù)。
lib/posts.tsx 這個文件導(dǎo)出 JSON 數(shù)據(jù)。
import?path?from?"path";
import?fs,?{promises?as?fsPromise}?from?"fs";
import?matter?from?"gray-matter";
export?const?getPosts?=?async?()?=>?{
??const?markdownDir?=?path.join(process.cwd(),?'markdown');
??const?fileNames?=?await?fsPromise.readdir(markdownDir);
??const?x?=?fileNames.map(fileName?=>?{
????const?fullPath?=?path.join(markdownDir,?fileName);
????const?id?=?fileName.replace(fullPath,?'');
????const?text?=?fs.readFileSync(fullPath,?'utf8');
????const?{data:?{title,?date},?content}?=?matter(text);
????return?{
??????id,?title,?date
????}
??});
??console.log('x');
??console.log(x);
??return?x;
};
搞定了數(shù)據(jù),下面就簡單多了,posts API 接口直接從上面的代碼中獲取數(shù)據(jù),然后返回給前端即可。pages/api/posts.tsx
import?{NextApiHandler}?from?'next';
import?{getPosts}?from?'lib/posts';
const?Posts:?NextApiHandler?=?async?(req,?res)?=>?{
??const?posts?=?await?getPosts();
??res.statusCode?=?200;
??res.setHeader('Content-Type',?'application/json');
??res.write(JSON.stringify(posts));
??res.end();
};
export?default?Posts;
ps:Next.js 基于 Express,所以支持 Express 的中間件。如果有復(fù)雜的操作,可以借助 Express 中間件。
Next.js 三種渲染方式
下面我們來做前端部分,用三種渲染方式實現(xiàn)。
客戶端渲染
只在瀏覽器上執(zhí)行的渲染。
也就是最原始的前端渲染方式,頁面在瀏覽器獲取到 JavaScript 和 CSS 等文件后開始渲染。路由是客戶端路由,也就是目前最常見的 SPA 單頁應(yīng)用。
缺點
但這種方式會造成兩個問題。一是白屏,目前解決方法是在 AJAX 得到相應(yīng)之前,頁面中先加入 Loading。二是 SEO 不友好,因為搜索引擎訪問頁面時,默認(rèn)不會執(zhí)行 JS,只能看到 HTML,看不到 AJAX 請求的數(shù)據(jù)。
代碼
pages/posts/BSR.tsx
import?{NextPage}?from?'next';
import?axios?from?'axios';
import?{useEffect,?useState}?from?"react";
import?*?as?React?from?"react";
type?Post?=?{
????id:?string,
????id:?string,
????title:?string
}
const?PostsIndex:?NextPage?=?()?=>?{
????//?[]?表示只在第一次渲染的時候請求
????const?[posts,?setPosts]?=?useState([]);
????const?[isLoading,?setIsLoading]?=?useState(false);
????useEffect(()?=>?{
????????setIsLoading(true);
????????axios.get('/api/posts').then(response?=>?{
??????????setPosts(response.data);
??????????setIsLoading(false);
????????},?()?=>?{
????????????setIsLoading(true);
????????})
????},?[]);
????return?(
????????
????????????文章列表</h1>
????????????{isLoading???加載中div>?:
????????????????posts.map(p?=>?
????????????????{p.id}
????????????</div>)}
????????div>
????)
};
export?default?PostsIndex;
訪問?http://localhost:3000/posts/BSR,如果網(wǎng)絡(luò)不好,白屏?xí)r間很長。
因為數(shù)據(jù)本來不在頁面上,通過 ajax 請求后渲染到頁面上。
文章列表都是前端渲染的,我們稱之為客戶端渲染。
靜態(tài)頁面生成(SSG) Static Site Generation
我們做的博客網(wǎng)站,其實每個人看到的文章列表都是一樣的。
那為什么還需要在每個人的瀏覽器上渲染一次呢?
能不能直接在后端渲染好,瀏覽器直接請求呢?
這樣的話,N 次渲染就變成了 1 次渲染,N 次客戶端渲染變成了 1 次靜態(tài)頁面生成。
這個過程就叫做動態(tài)內(nèi)容靜態(tài)化。
優(yōu)缺點
這種方式可以解決白屏問題、SEO 問題。
但這種方式所有用戶請求的內(nèi)容都一樣,無法生成用戶相關(guān)內(nèi)容。
代碼:getStaticProps 獲取 posts
顯然,后端最好不要通過 AJAX 來獲取 posts。
我們的數(shù)據(jù)就在文件夾里面,直接讀取數(shù)據(jù)就可以,沒必要發(fā)送 AJAX。
那么,應(yīng)該如何獲取獲取 posts 呢?
使用 Next.js 提供的方法?getStaticProps?導(dǎo)出數(shù)據(jù),NextPage 的 props 參數(shù)會自動獲取導(dǎo)出的數(shù)據(jù)。
具體來看看代碼吧:
SSG.tsx
import?{GetStaticProps,?NextPage}?from?'next';
import?{getPosts}?from?'../../lib/posts';
import?Link?from?'next/link';
import?*?as?React?from?'react';
type?Post?=?{
??id:?string,
??title:?string
}
type?Props?=?{
??posts:?Post[];
}
//?props?中有下面導(dǎo)出的數(shù)據(jù)?posts
const?PostsIndex:?NextPage?=?(props)?=>?{
??const?{posts}?=?props;
????//?前后端控制臺都能打印?->?同構(gòu)
??console.log(posts);
??return?(
????
??????文章列表</h1>
??????{posts.map(p?=>?
????????posts/${p.id}`}>
??????????
????????????{p.id}
??????????
????????
??????)}
????
??);
};
export?default?PostsIndex;
//?實現(xiàn)SSG
export?const?getStaticProps:?GetStaticProps?=?async?()?=>?{
??const?posts?=?await?getPosts();
??return?{
????props:?{
??????posts:?JSON.parse(JSON.stringify(posts))
????}
??};
};
訪問?http://localhost:3000/posts/SSG,頁面訪問成功。
前端怎么不通過 AJAX 獲取數(shù)據(jù)?
posts 數(shù)據(jù)我們只傳遞給了服務(wù)器,為什么在前端也能打印出來?
我們來看看此時的頁面:

現(xiàn)在前端不用 AJAX 也能拿到 posts 了,直接通過?__NEXT_DATA__?獲取數(shù)據(jù)。這就是同構(gòu) SSR 的好處:后端數(shù)據(jù)可以直接傳給前端,前端 JSON.parse 一下子就能得到 posts。
getStaticProps 靜態(tài)化的時機
在開發(fā)環(huán)境,每次請求都會運行一次 getStaticProps,這是為了方便我們修改代碼重新運行。
而在生產(chǎn)環(huán)境,getStaticProps 只在 build 時運行,這樣可以提供一份 HTML 給所有用戶下載。
來體驗下生產(chǎn)環(huán)境吧,打包我們的項目。
yarn?build
yarn?start
打包之后,我們得到三種類型的文件:
λ (Server) SSR 不能自動創(chuàng)建 HTML(等會再說)
○ (Static) 自動創(chuàng)建 HTML (發(fā)現(xiàn)你沒用到 props)
● (SSG) 自動創(chuàng)建 HTML + JSON (等你用到 props)
創(chuàng)建出了這三種文件:posts.html = posts.js + posts.json
posts.html 含有靜態(tài)內(nèi)容,用于用戶直接訪問 post.js 也含有靜態(tài)內(nèi)容,用于快速導(dǎo)航(與 HTML 對應(yīng)) posts.json 含有數(shù)據(jù),跟 posts.js 結(jié)合得到頁面
那為什么不直接把數(shù)據(jù)放入 posts.js 呢?顯然,是為了讓 posts.js 接受不同的數(shù)據(jù)。
當(dāng)我們展示每篇博客的時候,他們的樣式相同,內(nèi)容不同,就會用到這個功能了。
小結(jié)
如果動態(tài)內(nèi)容與用戶無關(guān),那么可以提前靜態(tài)化。 通過 getStaticProps 可以獲取數(shù)據(jù),靜態(tài)內(nèi)容 + 數(shù)據(jù)(本地獲取)就得到了完整頁面。代替了之前的 靜態(tài)內(nèi)容+動態(tài)數(shù)據(jù)(AJAX獲取)。 靜態(tài)化是在 yarn build 的時候?qū)崿F(xiàn)的 優(yōu)點 生產(chǎn)環(huán)境直接給出完整頁面 首屏不會白屏 搜索引擎能看到頁面內(nèi)容(方便 SEO)
服務(wù)端渲染(SSR)
如果頁面跟用戶相關(guān)呢?這種情況較難提前靜態(tài)化。
那怎么辦呢?
要么客戶端渲染,下拉更新 要么服務(wù)的渲染,下拉 AJAX 更新(沒有白屏
優(yōu)點
這種方式可以解決白屏問題、SEO 問題??梢陨捎脩粝嚓P(guān)內(nèi)容(不同用戶結(jié)果不同)。
代碼
和 SSG 代碼基本一致,不過使用的函數(shù)換成 getServerSideProps。
寫一段代碼,顯示當(dāng)前用戶瀏覽器是什么。
import?{GetServerSideProps,?NextPage}?from?'next';
import?*?as?React?from?'react';
import?{IncomingHttpHeaders}?from?'http';
type?Props?=?{
??browser:?string
}
const?index:?NextPage?=?(props)?=>?{
??return?(
????
??????你的瀏覽器是?{props.browser}</h1>
????div>
??);
};
export?default?index;
export?const?getServerSideProps:?GetServerSideProps?=?async?(context)?=>?{
??const?headers:IncomingHttpHeaders?=?context.req.headers;
??const?browser?=?headers['user-agent'];
??return?{
????props:?{
??????browser
????}
??};
};
getServerSideProps
無論是開發(fā)環(huán)境還是生產(chǎn)環(huán)境,都是在請求到來之后運行?getServerSideProps。
回顧一下 getStaticProps,看看他們的區(qū)別。
開發(fā)環(huán)境,每次請求到來后運行,方便開發(fā) 生產(chǎn)環(huán)境,build 時運行
參數(shù)
context,類型為 NextPageContext context.req/context.res 可以獲取請求和響應(yīng) 一般只需要用到 context.req
SSR 原理
最后我們來看看 SSR 到底是怎么實現(xiàn)的。
我們都知道 SSR 是提前渲染好靜態(tài)內(nèi)容,這些靜態(tài)內(nèi)容是在服務(wù)端渲染,還是在客戶端渲染的?
具體渲染幾次呢?一次還是兩次?
參考 React SSR 的官方文檔
推薦 在后端調(diào)用?renderToString()?的方法,把整個頁面渲染成字符串。
然后前端調(diào)用?hydrate()?方法,把后端傳遞的字符串和自己的實例混合起來,保留 HTML 并附上事件監(jiān)聽。
以上就是 Next.js 實現(xiàn) SSR 的主要方法,也就是后端會渲染 HTML, 前端添加監(jiān)聽。
前端也會渲染一次,以確保前后端渲染結(jié)果一致。如果結(jié)果不一致,控制臺會報錯提醒我們。
總結(jié)
創(chuàng)建項目?npm init next-app 項目名 快速導(dǎo)航? 同構(gòu)代碼:一份代碼,兩端運行 全局組件:pages/_app.js 全局 CSS:在 _app.js 里 import
自定義 head:使用 組件 Next.js API:都放在 /pages/api 目錄中 三種渲染的方式:BSR、SSG、SSR 動態(tài)內(nèi)容 術(shù)語:客戶端渲染,通過 AJAX 請求,渲染成 HTML。 動態(tài)內(nèi)容靜態(tài)化 術(shù)語:SSG,通過 getStaticProps 獲取用戶無關(guān)內(nèi)容 用戶相關(guān)動態(tài)內(nèi)容靜態(tài)化 術(shù)語:SSR,通過 getServerSideProps 獲取請求 缺點:無法獲取客戶端信息,如瀏覽器窗口大小 靜態(tài)內(nèi)容 直接輸出 HTML,沒有術(shù)語。
篇幅有限,更多可前往 https://github.com/Maricaya/nextjs-blog-1

1. JavaScript 重溫系列(22篇全)
2. ECMAScript 重溫系列(10篇全)
3. JavaScript設(shè)計模式 重溫系列(9篇全) 4.?正則 / 框架 / 算法等 重溫系列(16篇全) 5.?Webpack4 入門(上)||?Webpack4 入門(下) 6.?MobX 入門(上)?||??MobX 入門(下) 7. 80+篇原創(chuàng)系列匯總 回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點擊“閱讀原文”查看 80+ 篇原創(chuàng)文章
瀏覽
71
評論圖片表情
波多野吉衣毛片
|
免费看片色
|
一区二区精品视频尤酸乳
|
91干在线播放
|
中文字幕无码成人
|
