React + TypeScript + Taro前端開(kāi)發(fā)小結(jié)
前言
項(xiàng)目到一段落,先來(lái)記錄一下,本文以前端新手的角度記錄React、TypeScript、Taro相關(guān)技術(shù)的開(kāi)發(fā)體驗(yàn)以及遇到的問(wèn)題和解決方法。
之前總說(shuō)要學(xué)React(這篇博客:代碼使我頭疼之React初學(xué)習(xí)),這次項(xiàng)目需要做H5前端+小程序,我終于能用上React了~
使用React的開(kāi)發(fā)框架之前就聽(tīng)過(guò)京東的Taro,所以就這個(gè)了,直接開(kāi)碼。
關(guān)于React
不錯(cuò),感覺(jué)比Vue的模板寫法自由很多,我看Taro文檔的例子都是class組件,但一開(kāi)始「前端帶師」就推薦我用function組件,現(xiàn)在我全都是用function組件,react就該這么寫,真香~
因?yàn)橹皩懥艘欢螘r(shí)間的Flutter,所以react對(duì)我來(lái)說(shuō)很親切,至少可以無(wú)縫上手聲明式UI的寫法。
不過(guò)我感覺(jué)React的生態(tài)太大,更新太快了,有點(diǎn)碎片化,很多第三方庫(kù)官方文檔都跟不上更新速度(批評(píng)一下mobx,害人不淺)
話說(shuō)一開(kāi)始我看了某位知乎大V的那本React和Redux的書,應(yīng)該是我太菜的原因,感覺(jué)不是很容易理解,果然技術(shù)厲害的大佬不一定教書也厲害嗎~
參考資料
React 入門 - 從js的角度理解 react:https://github.com/coppyC/blog/issues/16
關(guān)于TypeScript
第一次用TypeScript,不過(guò)作為日常用C#寫后端的人,又處處是熟悉的感覺(jué)~
反正比JS好用一萬(wàn)倍就是了,類型提示真是太棒了
目前用得不深,后續(xù)有什么相關(guān)的我再寫寫博客記錄一下。
參考資料
探索 TypeScript 類型注解 - 自定義類型:https://github.com/WowBar/blog/issues/9
Taro框架使用感受
框架是個(gè)好框架,不過(guò)文檔方面感覺(jué)還不是很完善,有些地方寫得不是很清楚,感覺(jué)很多文檔都是復(fù)制了微信小程序的文檔來(lái)的,對(duì)于沒(méi)開(kāi)發(fā)過(guò)微信小程序且沒(méi)讀過(guò)微信官方文檔的人來(lái)說(shuō),不是很友好。(不過(guò)官方文檔還是要看,不看的話遇到很多問(wèn)題都查不到的)
然后Taro官方提供了一個(gè)UI庫(kù),叫 TaroUI,用的話還是能用的,就是更新太慢了,它的github項(xiàng)目主頁(yè) 顯示上次更新時(shí)間還是去年(2021年)6月份,到現(xiàn)在近一年時(shí)間沒(méi)動(dòng)過(guò)了。最新穩(wěn)定版本還在2.x,而Taro框架已經(jīng)更新到3版本了。
因?yàn)槲矣玫腡aro框架是3.x版本,所以只能硬著頭皮上TaroUI 3的beta版本,導(dǎo)致遇到了一些奇奇怪怪的問(wèn)題,頭大。
除了TaroUI這個(gè)界面庫(kù),Taro官方還提供另一個(gè)叫 NutUI 的庫(kù),不過(guò)是基于Vue的,我這個(gè)項(xiàng)目沒(méi)法用,這個(gè)庫(kù)就更新挺勤快的,github上最近更新還是6小時(shí)前,Star也有4.2k,比TaroUI的3.9k多。(看來(lái)React在京東不受待見(jiàn)呀~)。
我還看到有一個(gè)叫 Taroify 的UI庫(kù),看起來(lái)好像不錯(cuò),更新也很勤快,不過(guò)GitHub Stars只有300多,不敢用~ 下次來(lái)試試看
用的同時(shí)我還參考了這些項(xiàng)目/代碼/文檔:
https://github.com/NervJS/taro-v2ex/blob/react/src/pages/thread_detail/thread_detail.tsx https://github.com/wuba/Taro-Mortgage-Calculator https://github.com/NervJS/awesome-taro
接下來(lái)進(jìn)入正題,總體說(shuō)說(shuō)遇到的一些問(wèn)題/坑,以及解決方案。
頁(yè)面路由問(wèn)題
Taro封裝了路由相關(guān)的方法,我是做完了項(xiàng)目有時(shí)間去翻一下 微信小程序文檔 才發(fā)現(xiàn)這玩意跟小程序的路由特別像。
PS:我討厭這種路由設(shè)計(jì),不知道小程序這樣是哪個(gè)人才想出來(lái)的,頁(yè)面多了的話不太好維護(hù)啊~
在 app.config.ts 文件里把路由配置好,類似這樣:
export?default?defineAppConfig({
??pages:?[
????'pages/index/index',
????'pages/info/place',
????'pages/supply/index',
????'pages/user/login',
??],
??window:?{
????backgroundTextStyle:?'light',
????navigationBarBackgroundColor:?'#fff',
????navigationBarTitleText:?'WeChat',
????navigationBarTextStyle:?'black'
??},
})
然后要跳轉(zhuǎn)的地方就用 Taro.navigateTo({url: 'pages/user/login'}) 就行了
這里有個(gè)很坑的地方!Taro的熱更新不完善,添加了新頁(yè)面后熱更新是不生效的,必須 yarn dev:h5 重啟才能看到效果,一開(kāi)始我被坑得嗷嗷叫~
地址參數(shù)問(wèn)題
這個(gè)問(wèn)題在我之前的博客:Django + Taro 前后端分離項(xiàng)目實(shí)現(xiàn)企業(yè)微信登錄 有提到,Taro本身提供了 useRouter() 來(lái)給我們讀取地址里的參數(shù)
比如上面那個(gè)路由跳轉(zhuǎn)的地方我們加上了參數(shù):Taro.navigateTo({url: 'pages/user/login?title=hello'})
那我們?cè)?pages/user/login 頁(yè)面里要獲取參數(shù)就是這樣
import?{useEffect}?from?"react"
import?{useRouter}?from?"@tarojs/taro"
export?default?function?()?{
????const?router?=?useRouter()
????
????useEffect(()?=>?{
????????console.log(router.params.title)
????},?[])
}
但當(dāng)在讀取微信登錄服務(wù)器回調(diào)參數(shù)的時(shí)候,就不行,就取不出來(lái),得自己拿完整鏈接 window.location.href 去匹配。詳見(jiàn)我這篇博客:Django + Taro 前后端分離項(xiàng)目實(shí)現(xiàn)企業(yè)微信登錄
Taro.relaunch不會(huì)清除URL
這看起來(lái)不是什么大問(wèn)題,不過(guò)也導(dǎo)致了一個(gè)小bug,就是我在使用微信登錄后,注銷登錄的時(shí)候不會(huì)清除地址里的code,這樣沒(méi)關(guān)閉頁(yè)面的情況下,再次使用微信登錄,那個(gè)code還是舊的,就直接報(bào)錯(cuò)了~
TaroUI form的bug
說(shuō)實(shí)話我不知道這是哪里的問(wèn)題
只有一個(gè)頁(yè)面出現(xiàn)了這個(gè)問(wèn)題,在最后一個(gè)輸入框按回車,表現(xiàn)是form提交,但其實(shí)也沒(méi)提交,并且頁(yè)面變成重新刷新了
百思不得其解
我只好在最后面再加了一個(gè)隱藏的input
name='hide'
onChange={() => {
}}
disabled={true}
border={false}
style={{display: 'none'}}/>
網(wǎng)絡(luò)請(qǐng)求封裝
Taro框架自帶了 Taro.request 可以用來(lái)請(qǐng)求,不過(guò)我用的時(shí)候很奇怪一直提示跨域,因?yàn)榍捌跁r(shí)間很趕,我就沒(méi)去深入,直接換成我之前vue項(xiàng)目封裝好的axios,果然還是axios好用~
(不過(guò)之后做成小程序的話,應(yīng)該還是得重構(gòu)一下,據(jù)說(shuō)小程序不支持formdata)
封裝useState
感謝「前端帶師 coppy」提供的代碼~
import?{useState}?from?'react'
export?default?function?useYourState<T?extends?{}>(state:?T):?[T,?(state:?Partial )?=>?void]?{
??const?[_state,?_setState]?=?useState(state);
??return?[
????_state,
????(state:?Partial )?=>?{
??????_setState((_state)?=>?{
????????return?{
??????????..._state,
??????????...state
????????};
??????});
????}
??];
}
這樣就不需要每次setState都需要加...state了
使用前:
import?{useState}?from?'react'
export?const?LoginPage?=?observer(()?=>?{
??const?[state,?setState]?=?useState({
????username:?'',
????password:?'',
??})
??
??setState({
??????...state,
??????username:?'',?password:?''
??})
}
使用后:
import?useYourState?from?"@/utils/coppy_state";
export?const?LoginPage?=?observer(()?=>?{
??const?[state,?setState]?=?useYourState({
????username:?'',
????password:?'',
??})
??
??setState({
??????username:?'',?password:?''
??})
}
生產(chǎn)力獲得了提高~
全局狀態(tài)管理
沒(méi)去用大名鼎鼎的redux,轉(zhuǎn)而使用比較簡(jiǎn)單的mobx
但是找到的例子文檔都不太行(舉例,官方中文文檔:https://cn.mobx.js.org/)
最終還是尋求「前端帶師」的幫助,搞定了
坑點(diǎn):
store現(xiàn)在沒(méi)法用裝飾器了,用這個(gè) makeAutoObservable不需要全局provider Taro官網(wǎng)和例子可以說(shuō)是史上最坑,千萬(wàn)別被騙了,地址:https://taro-docs.jd.com/taro/docs/mobx/ 請(qǐng)用最新版的mobx和 mobx-react-lite,別用Taro官網(wǎng)那個(gè)4.8版本,太老了沒(méi)用
代碼
不需要全局provider包裝了,直接用全局變量,當(dāng)然也可以用React Context
store定義
import?{makeAutoObservable}?from?"mobx";
import?{User}?from?"@/models/user";
import?*?as?auth?from?'@/utils/auth'
export?class?UserStore?{
??isLogin?=?false
??user:?User?|?null?=?null
??token?=?''
??constructor()?{
????makeAutoObservable(this)
??}
??login(user:?User,?token:?string)?{
????this.user?=?user
????this.token?=?token
????this.isLogin?=?true
????//?保存登錄數(shù)據(jù)到本地
????auth.login(token,?user.username)
??}
??logout()?{
????this.isLogin?=?false
????this.user?=?null
????this.token?=?''
????auth.logout()
??}
}
export?const?myUserStore?=?new?UserStore()
組件使用
import {View} from "@tarojs/components"
import {AtButton} from "taro-ui";
import {observer} from "mobx-react-lite";
import {myUserStore} from "@/store/user";
import Taro from "@tarojs/taro";
import {useEffect} from "react";
export const UserPage = observer(() => {
useEffect(() => {
if (!myUserStore.isLogin) {
Taro.redirectTo({url: '/pages/user/login'})
}
}, [])
return (
用戶中心
用戶名:{myUserStore.user?.first_name}
退出登錄
)
function logout() {
myUserStore.logout()
Taro.reLaunch({url: '/pages/index/index'})
}
})
export default UserPage
參考資料
官網(wǎng)文檔:https://mobx.js.org/react-integration.html 項(xiàng)目地址:https://github.com/mobxjs/mobx/tree/main/packages/mobx-react Mobx React 初學(xué)者入門指南(掘金):https://juejin.cn/post/6844903831726211079 mobx 在 react 中的 類組件、函數(shù)組件、配合 hooks 的使用:https://juejin.cn/post/6873794258743066632#heading-2
JSON反序列化class
使用這個(gè)庫(kù):https://github.com/typestack/class-transformer
model定義,這個(gè)定義可以用JSON來(lái)生成,有很多在線工具,比如:https://apihelper.jccore.cn/jsontool
export?class?User?{
??username:?string
??first_name:?string
??last_name:?string
??email:?string
??date_joined:?string
}
注意項(xiàng)目主頁(yè)上的文檔也是過(guò)期了的,plainToClass方法已過(guò)期,得用這個(gè)方法:plainToInstance
import?{plainToInstance}?from?"class-transformer";
const?user?=?plainToInstance(User,?res.data.user)
myUserStore.login(user,?res.data.token)
