-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Taro Next #97
Comments
多平台独立目录编译配置
const path = require("path");
const outputRootStrtegy = {
h5: "dist_h5",
weapp: "dist_weapp",
alipay: "dist_alipay/client",
swan: "dist_swan",
tt: "dist_tt",
jd: "dist_jd",
["undefined"]: "dist"
};
const env = process.env.TARO_ENV;
const outputRoot = outputRootStrtegy[env];
const config = {
projectName: "myAppnext",
date: "2020-2-14",
designWidth: 750,
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
},
sourceRoot: "src",
outputRoot: outputRoot,
plugins: [],
defineConstants: {},
copy: {
patterns: [],
options: {}
},
framework: "react",
mini: {
postcss: {
pxtransform: {
enable: true,
config: {}
},
url: {
enable: true,
config: {
limit: 1024 // 设定转换尺寸上限
}
},
cssModules: {
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: "module", // 转换模式,取值为 global/module
generateScopedName: "[name]__[local]___[hash:base64:5]"
}
}
}
},
h5: {
publicPath: "/",
staticDirectory: "static",
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: ["last 3 versions", "Android >= 4.1", "ios >= 8"]
}
},
cssModules: {
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: "module", // 转换模式,取值为 global/module
generateScopedName: "[name]__[local]___[hash:base64:5]"
}
}
}
}
};
module.exports = function(merge) {
if (process.env.NODE_ENV === "development") {
return merge({}, config, require("./dev"));
}
return merge({}, config, require("./prod"));
};
|
Taro 组件异形 轮播图import React, { Component, useEffect, useLayoutEffect, useReducer, useState, useContext, useRef, useCallback, useMemo, } from 'react'
import Taro, { useRouter, useTabItemTap, useResize, useReachBottom, usePullDownRefresh, useDidHide, useDidShow, usePageScroll } from '@tarojs/taro'
import { View, Text, Swiper, SwiperItem, Image } from '@tarojs/components'
import { DeepWriteable } from '@/utils/tsUtils'
import { useHomeQueryResponse } from '@/graphql/query/useHome'
import './Banner.less'
type Props = {
banneres: DeepWriteable<useHomeQueryResponse['userBanners']>;
}
export default function Banner({ banneres = [] }: Props) {
const [current, setCurrent] = useState<number>(0)
useEffect(() => {
console.log('banneres---2:', banneres)
}, [banneres])
const change = (e) => {
setCurrent(e?.detail?.current)
}
return (
<View className='w-banner-container'>
<Swiper
className='w-swiper'
indicatorColor='#999'
indicatorActiveColor='#333'
circular
indicatorDots
autoplay
previousMargin='144rpx'
nextMargin='144rpx'
onChange={change.bind(this)}
>
{banneres && banneres.map((banner, index) => {
return (
<SwiperItem key={index} >
<Image src={banner.cover} className={current === index ? 'image' : 'image small'} mode='aspectFill' />
</SwiperItem>
)
})}
</Swiper>
</View>
)
} @import "../../style/color.less";
@import "../../style/size.less";
.w-banner-container {
width: 100%;
height: 41vw;
.w-swiper {
width: 100%;
height: 190px;
.image {
width: 459px;
height: 190px;
border-radius: 10px;
background-color: @gray-1;
}
.small {
transform: scale3d(0.857, 0.857, 0.857);
}
}
}
|
Taro 图表Taro 使用 Echarts:taro-react-echarts安装 npm install taro-react-echarts 导入组件 import Echarts from 'taro-react-echarts' 定制下载 Echarts js库 import { useEffect, useRef } from 'react'
import Echarts, { EChartOption, EchartsHandle } from 'taro-react-echarts'
//@ts-ignore
//import echarts from '../lib/echarts.min'
//@ts-ignore
import echarts from '../lib/echarts'
import styles from "./index.module.scss";
interface Props{
}
const HealthRecords= ({}:Props) => {
const echartsRef = useRef<EchartsHandle>(null)
const option: EChartOption = {
legend: {
top: 50,
left: 'center',
z: 100,
},
tooltip: {
trigger: 'axis',
show: true,
confine: true,
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
},
],
}
return <Echarts echarts={echarts} option={option} ref={echartsRef} />
};
export default HealthRecords; Taro 使用 taro-f2-react
yarn add taro-f2-react @antv/f2 -S 详细使用文档参考 https://f2.antv.antgroup.com/ 使用
const config = {
compiler: {
type: 'webpack5',
prebundle: {
enable: false,
},
},
} 否则可能会报错“
import React, { useCallback, useRef } from "react";
import { View, Text, Button, Image } from "@tarojs/components";
import F2Canvas from "taro-f2-react";
import { Chart, Interval } from "@antv/f2";
import "./index.less";
const data = [
{ genre: "Sports", sold: 275 },
{ genre: "Strategy", sold: 115 },
{ genre: "Action", sold: 120 },
{ genre: "Shooter", sold: 350 },
{ genre: "Other", sold: 150 },
];
const Index = () => {
return (
<View style={{ width: "100%", height: "260px" }}>
<F2Canvas>
<Chart data={data}>
<Interval x="genre" y="sold" />
</Chart>
</F2Canvas>
</View>
);
};
export default Index; 注意
或者等待官方 antvis 修复 antvis/F2#1980 解决方案1:等待 官方 antvis 修复。 解决方案2: 锁定版本 {
...
"dependencies": {
"@antv/f2": "4.0.51",
"taro-f2-react": "1.1.1"
}
} 滚动后获取 ScrollBar 当前的区间 range
export const data2 = [
{
title: 'Bohemian Rhapsody',
artist: 'Queen',
release: 1975,
year: '1999',
rank: '1',
count: 978
},
{
title: 'Hotel California',
artist: 'Eagles',
release: 1977,
year: '1999',
rank: '2',
count: 1284
},
{
title: 'Child In Time',
artist: 'Deep Purple',
release: 1972,
year: '1999',
rank: '3',
count: 1117
},
{
title: 'Stairway To Heaven',
artist: 'Led Zeppelin',
release: 1971,
year: '1999',
rank: '4',
count: 1132
},
{
title: 'Paradise By The Dashboard Light',
artist: 'Meat Loaf',
release: 1978,
year: '1999',
rank: '5',
count: 1187
},
{
title: 'Yesterday',
artist: 'The Beatles',
release: 1965,
year: '1999',
rank: '6',
count: 909
},
{
title: 'Angie',
artist: 'The Rolling Stones',
release: 1973,
year: '1999',
rank: '8',
count: 1183
},
{
title: 'Bridge Over Troubled Water',
artist: 'Simon & Garfunkel',
release: 1970,
year: '1999',
rank: '9',
count: 1111
},
{
title: 'A Whiter Shade Of Pale',
artist: 'Procol Harum',
release: 1967,
year: '1999',
rank: '10',
count: 1190
},
{
title: 'Hey Jude',
artist: 'The Beatles',
release: 1968,
year: '1999',
rank: '11',
count: 1037
},
{
title: 'House Of The Rising Sun',
artist: 'The Animals',
release: 1964,
year: '1999',
rank: '13',
count: 543
},
{
title: 'Goodnight Saigon',
artist: 'Billy Joel',
release: 1983,
year: '1999',
rank: '14',
count: 748
},
{
title: 'Dancing Queen',
artist: 'ABBA',
release: 1976,
year: '1999',
rank: '16',
count: 1111
},
{
title: 'Another Brick In The Wall',
artist: 'Pink Floyd',
release: 1979,
year: '1999',
rank: '17',
count: 1266
},
{
title: 'Sunday Bloody Sunday',
artist: 'U2',
release: 1985,
year: '1999',
rank: '18',
count: 1087
},
{
title: 'Tears In Heaven',
artist: 'Eric Clapton',
release: 1992,
year: '1999',
rank: '21',
count: 435
},
{
title: 'Old And Wise',
artist: 'The Alan Parsons Project',
release: 1982,
year: '1999',
rank: '24',
count: 945
},
{
title: 'Losing My Religion',
artist: 'R.E.M.',
release: 1991,
year: '1999',
rank: '25',
count: 415
},
{
title: 'School',
artist: 'Supertramp',
release: 1974,
year: '1999',
rank: '26',
count: 1011
},
{
title: 'Who Wants To Live Forever',
artist: 'Queen',
release: 1986,
year: '1999',
rank: '30',
count: 836
},
{
title: 'Everybody Hurts',
artist: 'R.E.M.',
release: 1993,
year: '1999',
rank: '31',
count: 301
},
{
title: 'Over De Muur',
artist: 'Klein Orkest',
release: 1984,
year: '1999',
rank: '32',
count: 1166
},
{
title: 'Paint It Black',
artist: 'The Rolling Stones',
release: 1966,
year: '1999',
rank: '33',
count: 1077
},
{
title: 'The Winner Takes It All',
artist: 'ABBA',
release: 1980,
year: '1999',
rank: '35',
count: 926
},
{
title: 'Candle In The Wind (1997)',
artist: 'Elton John',
release: 1997,
year: '1999',
rank: '37',
count: 451
},
{
title: 'My Heart Will Go On',
artist: 'Celine Dion',
release: 1998,
year: '1999',
rank: '41',
count: 415
},
{
title: 'The River',
artist: 'Bruce Springsteen',
release: 1981,
year: '1999',
rank: '48',
count: 723
},
{
title: 'With Or Without You',
artist: 'U2',
release: 1987,
year: '1999',
rank: '51',
count: 816
},
{
title: 'Space Oddity',
artist: 'David Bowie',
release: 1969,
year: '1999',
rank: '59',
count: 1344
},
{
title: 'Stil In Mij',
artist: 'Van Dik Hout',
release: 1994,
year: '1999',
rank: '65',
count: 373
},
{
title: 'Nothing Compares 2 U',
artist: "Sinead O'Connor",
release: 1990,
year: '1999',
rank: '90',
count: 426
},
{
title: 'Wonderful Tonight',
artist: 'Eric Clapton',
release: 1988,
year: '1999',
rank: '91',
count: 515
},
{
title: 'Blowing In The Wind',
artist: 'Bob Dylan',
release: 1963,
year: '1999',
rank: '94',
count: 323
},
{
title: 'Eternal Flame',
artist: 'Bangles',
release: 1989,
year: '1999',
rank: '96',
count: 495
},
{
title: 'Non Je Ne Regrette Rien',
artist: 'Edith Piaf',
release: 1961,
year: '1999',
rank: '106',
count: 178
},
{
title: 'Con Te Partiro',
artist: 'Andrea Bocelli',
release: 1996,
year: '1999',
rank: '109',
count: 362
},
{
title: 'Conquest Of Paradise',
artist: 'Vangelis',
release: 1995,
year: '1999',
rank: '157',
count: 315
},
{
title: 'White Christmas',
artist: 'Bing Crosby',
release: 1954,
year: '1999',
rank: '218',
count: 10
},
{
title: "(We're gonna) Rock Around The Clock",
artist: 'Bill Haley & The Comets',
release: 1955,
year: '1999',
rank: '239',
count: 19
},
{
title: 'Jailhouse Rock',
artist: 'Elvis Presley',
release: 1957,
year: '1999',
rank: '247',
count: 188
},
{
title: 'Take Five',
artist: 'Dave Brubeck',
release: 1962,
year: '1999',
rank: '279',
count: 204
},
{
title: "It's Now Or Never",
artist: 'Elvis Presley',
release: 1960,
year: '1999',
rank: '285',
count: 221
},
{
title: 'Heartbreak Hotel',
artist: 'Elvis Presley',
release: 1956,
year: '1999',
rank: '558',
count: 109
},
{
title: 'One Night',
artist: 'Elvis Presley',
release: 1959,
year: '1999',
rank: '622',
count: 71
},
{
title: 'Johnny B. Goode',
artist: 'Chuck Berry',
release: 1958,
year: '1999',
rank: '714',
count: 89
},
{
title: 'Unforgettable',
artist: "Nat 'King' Cole",
release: 1951,
year: '1999',
rank: '1188',
count: 20
},
{
title: 'La Mer',
artist: 'Charles Trenet',
release: 1952,
year: '1999',
rank: '1249',
count: 24
},
{
title: 'The Road Ahead',
artist: 'City To City',
release: 1999,
year: '1999',
rank: '1999',
count: 262
},
{
title: 'What It Is',
artist: 'Mark Knopfler',
release: 2000,
year: '2000',
rank: '545',
count: 291
},
{
title: 'Overcome',
artist: 'Live',
release: 2001,
year: '2001',
rank: '879',
count: 111
},
{
title: 'Mooie Dag',
artist: 'Blof',
release: 2002,
year: '2003',
rank: '147',
count: 256
},
{
title: 'Clocks',
artist: 'Coldplay',
release: 2003,
year: '2003',
rank: '733',
count: 169
},
{
title: 'Sunrise',
artist: 'Norah Jones',
release: 2004,
year: '2004',
rank: '405',
count: 256
},
{
title: 'Nine Million Bicycles',
artist: 'Katie Melua',
release: 2005,
year: '2005',
rank: '23',
count: 250
},
{
title: 'Rood',
artist: 'Marco Borsato',
release: 2006,
year: '2006',
rank: '17',
count: 159
},
{
title: 'If You Were A Sailboat',
artist: 'Katie Melua',
release: 2007,
year: '2007',
rank: '101',
count: 256
},
{
title: 'Viva La Vida',
artist: 'Coldplay',
release: 2009,
year: '2009',
rank: '11',
count: 228
},
{
title: 'Dochters',
artist: 'Marco Borsato',
release: 2008,
year: '2009',
rank: '25',
count: 268
},
{
title: 'Need You Now',
artist: 'Lady Antebellum',
release: 2010,
year: '2010',
rank: '210',
count: 121
},
{
title: 'Someone Like You',
artist: 'Adele',
release: 2011,
year: '2011',
rank: '6',
count: 187
},
{
title: 'I Follow Rivers',
artist: 'Triggerfinger',
release: 2012,
year: '2012',
rank: '79',
count: 167
},
{
title: 'Get Lucky',
artist: 'Daft Punk',
release: 2013,
year: '2013',
rank: '357',
count: 141
},
{
title: 'Home',
artist: 'Dotan',
release: 2014,
year: '2014',
rank: '82',
count: 76
},
{
title: 'Hello',
artist: 'Adele',
release: 2015,
year: '2015',
rank: '23',
count: 29
}
]
import React, { useCallback, useRef } from 'react'
import { View, Text, Button, Image } from '@tarojs/components'
import F2Canvas from 'taro-f2-react'
import { Axis, Chart, Interval, Line, Point, ScrollBar } from '@antv/f2'
import './index.less'
import { data2 } from '../mock/data2'
import { useTouchHandlers, Point as PointType } from './useTouchHandlers'
type ScrollBarType = typeof ScrollBar
const Index = () => {
const scrollBarRef = useRef<any>()
const handleSwipeLeft = (point: PointType) => {
const { state } = scrollBarRef?.current || {}
const { range } = state || {}
console.log('左划', point)
console.log(scrollBarRef?.current)
console.log(range) //例如 [0.4,0.6] 表示当前的区间。即ScrollBar组件的range当前值
}
const handleSwipeRight = (point: PointType) => {
const { state } = scrollBarRef?.current || {}
const { range } = state || {}
console.log('右划', point)
console.log(scrollBarRef?.current)
console.log(range) //例如 [0.4,0.6] 表示当前的区间。即ScrollBar组件的range当前值
}
const { handleTouchStart, handleTouchEnd } = useTouchHandlers(handleSwipeLeft, handleSwipeRight)
return (
<View
style={{ width: '100%', height: '260px' }}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
<F2Canvas>
<Chart data={data2}>
<Axis field="release" tickCount={5} nice={false} />
<Axis field="count" />
<Line x="release" y="count" />
<Point x="release" y="count" />
<ScrollBar ref={scrollBarRef} mode="x" range={[0.1, 0.3]} />
</Chart>
</F2Canvas>
<Text>{}</Text>
</View>
)
}
export default Index
import { useRef } from 'react'
export type Point={
x:number
y:number
}
interface TouchHandlers {
handleTouchStart: (e) => void
handleTouchEnd: (e) => void
}
export const useTouchHandlers = (
handleSwipeLeft?: (point:Point) => void,
handleSwipeRight?: (point:Point) => void
): TouchHandlers => {
const startXRef = useRef<number | null>(null)
const startYRef = useRef<number | null>(null)
const handleTouchStart = (e) => {
const touch = e.changedTouches[0]
startXRef.current = touch.clientX
startYRef.current = touch.clientY
}
const handleTouchEnd = (e) => {
const touch = e.changedTouches[0]
const endX = touch.clientX
const endY = touch.clientY
if (startXRef.current === null || startYRef.current === null) return
const diffX = endX - startXRef.current
const diffY = endY - startYRef.current
const point={
x:endX,
y:endY
}
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 30) {
// Horizontal swipe detected
if (diffX < 0) {
// Left swipe
handleSwipeLeft && handleSwipeLeft(point)
} else {
// Right swipe
handleSwipeRight && handleSwipeRight(point)
}
}
startXRef.current = null
startYRef.current = null
}
return { handleTouchStart, handleTouchEnd }
} |
小程序分包分包根目录
import { useEffect, useRef } from 'react'
import Taro, { useRouter } from '@tarojs/taro'
import { View, Button } from '@tarojs/components'
import styles from './index.module.scss'
const Index = () => {
return <View>index</View>
}
export default Index
import { useEffect, useRef } from 'react'
import Taro, { useRouter } from '@tarojs/taro'
import { View, Button } from '@tarojs/components'
import styles from './index.module.scss'
const Home = () => {
return <View>home</View>
}
export default Home 配置 app.config.ts
export default defineAppConfig({
pages: [
'pages/index/index', //首页
],
// lazyCodeLoading: 'requiredComponents',
subPackages: [
{
root: "pages/healthRecords",
name: "healthRecords",
pages: ["index","home/index"]
}
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '122',
navigationBarTextStyle: 'black',
// navigationStyle: 'custom',
},
// 获取坐标控制
requiredPrivateInfos: [
'getLocation',
'onLocationChange',
'startLocationUpdate',
'chooseLocation',
'chooseAddress',
],
// 需要跳转的小程序appid
navigateToMiniProgramAppIdList: ['wx8735a8a39cf58b5e', 'wx2e4b495f6111764c'],
permission: {
'scope.userLocation': {
desc: '你的位置信息将用于小程序位置接口的效果展示',
},
},
tabBar: {
custom: true,
color: '#999999',
selectedColor: '#6e183e',
backgroundColor: '#f8f8f8',
list: [
{
pagePath: 'pages/index/index',
text: '首页',
},
{
pagePath: 'pages/personalCenterTab/index',
text: '个人中心',
},
],
},
}); |
state 与 ref 的监测测试测试 hooks 中的 模拟请求
{
"name": "taro-test",
"version": "1.0.0",
"private": true,
"description": "test",
"templateInfo": {
"name": "taro-hooks@2x",
"typescript": true,
"css": "Less",
"framework": "React"
},
"scripts": {
"build:weapp": "taro build --type weapp",
"build:swan": "taro build --type swan",
"build:alipay": "taro build --type alipay",
"build:tt": "taro build --type tt",
"build:h5": "taro build --type h5",
"build:rn": "taro build --type rn",
"build:qq": "taro build --type qq",
"build:jd": "taro build --type jd",
"build:quickapp": "taro build --type quickapp",
"dev:weapp": "npm run build:weapp -- --watch",
"dev:swan": "npm run build:swan -- --watch",
"dev:alipay": "npm run build:alipay -- --watch",
"dev:tt": "npm run build:tt -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"dev:rn": "npm run build:rn -- --watch",
"dev:qq": "npm run build:qq -- --watch",
"dev:jd": "npm run build:jd -- --watch",
"dev:quickapp": "npm run build:quickapp -- --watch"
},
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"author": "",
"dependencies": {
"@antv/f2": "^5.5.1",
"@babel/runtime": "^7.7.7",
"@taro-hooks/plugin-react": "2",
"@taro-hooks/shared": "2",
"@tarojs/components": "3.6.32",
"@tarojs/helper": "3.6.32",
"@tarojs/plugin-framework-react": "3.6.32",
"@tarojs/plugin-platform-alipay": "3.6.32",
"@tarojs/plugin-platform-h5": "3.6.32",
"@tarojs/plugin-platform-jd": "3.6.32",
"@tarojs/plugin-platform-qq": "3.6.32",
"@tarojs/plugin-platform-swan": "3.6.32",
"@tarojs/plugin-platform-tt": "3.6.32",
"@tarojs/plugin-platform-weapp": "3.6.32",
"@tarojs/react": "3.6.32",
"@tarojs/runtime": "3.6.32",
"@tarojs/shared": "3.6.32",
"@tarojs/taro": "3.6.32",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"taro-f2-react": "^1.2.0",
"taro-hooks": "2",
"taro-react-echarts": "^1.2.2"
},
"devDependencies": {
"@babel/core": "^7.8.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"@tarojs/cli": "3.6.32",
"@tarojs/taro-loader": "3.6.32",
"@tarojs/webpack5-runner": "3.6.32",
"@types/node": "^18.15.11",
"@types/react": "^18.0.0",
"@types/webpack-env": "^1.13.6",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"babel-plugin-import": "^1.13.3",
"babel-preset-taro": "3.6.32",
"eslint": "^8.12.0",
"eslint-config-taro": "3.6.32",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-react-hooks": "^4.2.0",
"postcss": "^8.4.18",
"react-refresh": "^0.11.0",
"stylelint": "9.3.0",
"ts-node": "^10.9.1",
"typescript": "^4.1.0",
"webpack": "^5.78.0"
},
"engines": {
"node": ">=12.0.0"
}
}
import React, { useEffect } from "react";
import useQuery from "../utils/useQuery";
import { mockApiCall } from "../utils/mock";
import { View, Text, Button, Image } from "@tarojs/components";
import { useEnv, useNavigationBar, useModal, useToast } from "taro-hooks";
const TestSingQuery = () => {
const { data, error, isLoading, exec } = useQuery<string, { userId: number }>(
mockApiCall,
{ userId: 123 }
);
useEffect(() => {
console.log(`监测 TestSingQuery data 更新:${data}`);
}, [data]);
return (
<View>
{isLoading && <Text>Loading...</Text>}
{error && <Text>Error: {error.message}</Text>}
{data && <Text>Data: {data}</Text>}
<Button onClick={() => exec({ userId: 456 })}>
Refetch with new params
</Button>
</View>
);
};
export default TestSingQuery;
//import useQuery from "../utils/useQuery";
import { mockApiCall } from "../utils/mock";
import { useEffect, useRef, useState } from "react";
const useTestHookQuery = (params: unknown, lazy = false) => {
const [data, setData] = useState<unknown>();
const dataRef = useRef<unknown>();
const [error, setError] = useState<Error>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const exec = async (newParams: unknown) => {
setIsLoading(true);
mockApiCall(newParams || params)
.then((response) => {
console.log(`响应 response:`);
console.table(response);
setData(response);
dataRef.current = response;
})
.catch((error) => {
setError(error as Error);
})
.finally(() => {
setIsLoading(false);
});
};
useEffect(() => {
if (lazy) return;
exec(params);
}, []);
useEffect(() => {
// useState的值 可以监测变更
console.log(`监测 useTestHookQuery data:-data${data}}`);
console.log(`监测 useTestHookQuery data:-dataRef:${dataRef.current}}`);
}, [data]);
useEffect(() => {
// useRef的值 无法监测变更
console.log(`监测 useTestHookQuery dataRef:-data${data}}`);
console.log(`监测 useTestHookQuery dataRef:-dataRef:${dataRef.current}}`);
}, [dataRef]);
return {
data,
error,
isLoading,
exec,
};
};
export default useTestHookQuery;
// 模拟请求函数
export const mockApiCall = (params: unknown): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Received params: ${JSON.stringify(params)}`);
}, 2000);
});
}
import { useState, useEffect } from 'react'
export interface QueryResult<T, P> {
data: T | null
error: Error | null
isLoading: boolean
exec: (p: P) => void
}
// 通用请求 hooks
const useQuery = <T, P>(queryFn: (params: P) => Promise<T>, params: P): QueryResult<T, P> => {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<Error | null>(null)
const [isLoading, setIsLoading] = useState<boolean>(false)
const exec = async (newParams: P) => {
setIsLoading(true)
// try {
// const response = await queryFn(newParams || params)
// console.log(`response--${JSON.stringify(response)}`)
// setData(response)
// } catch (error) {
// setError(error as Error)
// } finally {
// setIsLoading(false)
// }
queryFn(newParams || params).then((response)=>{
console.log(`queryFn response-${JSON.stringify(response)}`)
setData(response)
}).catch((error)=>{
setError(error as Error)
}).finally(()=>{
setIsLoading(false)
})
}
useEffect(() => {
console.log('queryFn 123')
exec(params)
}, [])
useEffect(() => {
console.log(' queryFn 111111111data:', data)
}, [data])
return { data, error, isLoading, exec }
}
export default useQuery
import React, { useCallback, useEffect } from "react";
import { View, Text, Button, Image } from "@tarojs/components";
import { useEnv, useNavigationBar, useModal, useToast } from "taro-hooks";
import TestSingQuery from "./components/TestQueryEffect";
import useTestHookQuery from "./hooks/useTestHookQuery";
import "./index.less";
const Index = () => {
const { data, exec } = useTestHookQuery({ a: 2222 }, true);
useEffect(() => {
exec({ a: 333 });
}, []);
return (
<View className="wrapper">
{/* <TestSingQuery /> */}
{data}
</View>
);
};
export default Index; |
Taro Issues不支持的语法
if (isLoading) return <View>加载中...</View>
if (error) return <View>错误: {error.message}</View>
if (data?.result === ResultStatus.Failure) {
return <View>错误: {data.msg}</View>
} 会报错: .._src_runtime_connect.ts:373 React 出现报错,请打开编译配置 mini.debugReact 查看报错详情:https://docs.taro.zone/docs/config-detail#minidebugreact
onError @ .._src_runtime_connect.ts:373
waitAppWrapper @ .._src_runtime_connect.ts:192
value @ .._src_runtime_connect.ts:377
getDerivedStateFromError @ .._src_runtime_connect.ts:118
Df.c.payload @ vendors.js? [sm]:18494
ud @ vendors.js? [sm]:18444
gg @ vendors.js? [sm]:18519
Sh @ vendors.js? [sm]:18593
Rh @ vendors.js? [sm]:18581
Qh @ vendors.js? [sm]:18581
Gh @ vendors.js? [sm]:18581
Lh @ vendors.js? [sm]:18572
Eh @ vendors.js? [sm]:18570
workLoop @ vendors.js? [sm]:20134
flushWork @ vendors.js? [sm]:20107
performWorkUntilDeadline @ vendors.js? [sm]:20401
setTimeout (async)
schedulePerformWorkUntilDeadline @ vendors.js? [sm]:20447
performWorkUntilDeadline @ vendors.js? [sm]:20406
setTimeout (async)
schedulePerformWorkUntilDeadline @ vendors.js? [sm]:20447
requestHostCallback @ vendors.js? [sm]:20456
unstable_scheduleCallback @ vendors.js? [sm]:20309
Dh @ vendors.js? [sm]:18598
Z @ vendors.js? [sm]:18569
Ad @ vendors.js? [sm]:18567
df @ vendors.js? [sm]:18485
(anonymous) @ ._src_hooks_useQuery.ts:30
Promise.then (async)
_callee$ @ ._src_hooks_useQuery.ts:28
tryCatch @ vendors.js? [sm]:21565
(anonymous) @ vendors.js? [sm]:21653
(anonymous) @ vendors.js? [sm]:21594
asyncGeneratorStep @ vendors.js? [sm]:20971
_next @ vendors.js? [sm]:20985
(anonymous) @ vendors.js? [sm]:20990
(anonymous) @ vendors.js? [sm]:20982
exec @ ._src_hooks_useQuery.ts:16
(anonymous) @ ._src_hooks_useQuery.ts:40
Gg @ vendors.js? [sm]:18540
Fh @ vendors.js? [sm]:18587
(anonymous) @ vendors.js? [sm]:18583
workLoop @ vendors.js? [sm]:20134
flushWork @ vendors.js? [sm]:20107
performWorkUntilDeadline @ vendors.js? [sm]:20401
setTimeout (async)
schedulePerformWorkUntilDeadline @ vendors.js? [sm]:20447
performWorkUntilDeadline @ vendors.js? [sm]:20406
setTimeout (async)
schedulePerformWorkUntilDeadline @ vendors.js? [sm]:20447
requestHostCallback @ vendors.js? [sm]:20456
unstable_scheduleCallback @ vendors.js? [sm]:20309
Dh @ vendors.js? [sm]:18598
Z @ vendors.js? [sm]:18569
Ad @ vendors.js? [sm]:18567
enqueueForceUpdate @ vendors.js? [sm]:18448
E.forceUpdate @ vendors.js? [sm]:19842
mount @ .._src_runtime_connect.ts:226
mount @ .._src_runtime_connect.ts:271
(anonymous) @ .._src_dsl_common.ts:300
vendors.js? [sm]:18493 Error: Minified React error #300; visit https://reactjs.org/docs/error-decoder.html?invariant=300 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at Ke (vendors.js? [sm]:18473)
at cg (vendors.js? [sm]:18515)
at Sh (vendors.js? [sm]:18593)
at Rh (vendors.js? [sm]:18581)
at Qh (vendors.js? [sm]:18581)
at Gh (vendors.js? [sm]:18581)
at Lh (vendors.js? [sm]:18572)
at Eh (vendors.js? [sm]:18570)
at workLoop (vendors.js? [sm]:20134)
at flushWork (vendors.js? [sm]:20107)(env: macOS,mp,1.06.2405020; lib: 2.25.3) 作为值使用的枚举类型,需要使用完整路径导入import { Blood } from '@/pages/healthRecords/utils/types/bloodPressure'
import { Blood } from '@/pages/healthRecords/utils/types/index process 无法再运行时解析[需要进一步探明原因和解决方案]因为这是编译时替换,运行时已经不存在process对象,
const appid=process.env.TARO_APP_VPID
const vpid: string = bMockMode
? process.env.TARO_APP_VPID
: vpidInUrl || visitorInfo.VISIT_PATIENT_ID webpack 不建议在前端项目中使用 process无法使用枚举的值
export enum UploadNumberType {
Camera = '1',
Album = '2'
}
const a=UploadNumberType.Camera //重新编译后才正常,否则 报错 UploadNumberType 未定义 Ref 类型的值不能作为属性传递
|
自定义颜色的彩色日志基础const a='123'
console.log(`%c ${a}`,'background-color: #25cbe9;color:white; ')
console.info(`%c ${a}`,'background-color: #25cbe9;color:white; ') 增强
type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'
const isProduction = process.env.NODE_ENV === 'production'
const logLevels: Record<LogLevel, { level: LogLevel; style: string }> = {
DEBUG: { level: 'DEBUG', style: 'background-color: #25cbe9; color: white;' },
INFO: { level: 'INFO', style: 'background-color: #28a745; color: white;' },
WARN: { level: 'WARN', style: 'background-color: #ffc107; color: black;' },
ERROR: { level: 'ERROR', style: 'background-color: #dc3545; color: white;' }
}
const getCurrentTimestamp = (): string => {
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = now.getDate().toString().padStart(2, '0')
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const seconds = now.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
export const createLogger = (isEnabled: boolean = !isProduction) => {
const log = (level: LogLevel, message: unknown, ...optionalParams: unknown[]): void => {
if (!isEnabled) return
const timestamp = getCurrentTimestamp()
const { style } = logLevels[level]
// Format message depending on its type
let formattedMessage: string
if (typeof message === 'string') {
formattedMessage = message
} else {
formattedMessage = JSON.stringify(message, null, 2)
}
console.log(`%c[${timestamp}] [${level}] ${formattedMessage}`, style, ...optionalParams)
}
return {
debug: (message: unknown, ...optionalParams: unknown[]): void => {
log(logLevels.DEBUG.level, message, ...optionalParams)
},
info: (message: unknown, ...optionalParams: unknown[]): void => {
log(logLevels.INFO.level, message, ...optionalParams)
},
warn: (message: unknown, ...optionalParams: unknown[]): void => {
log(logLevels.WARN.level, message, ...optionalParams)
},
error: (message: unknown, ...optionalParams: unknown[]): void => {
log(logLevels.ERROR.level, message, ...optionalParams)
}
}
}
// 示例使用
// const isProduction = process.env.NODE_ENV === 'production';
// const logger = createLogger(!isProduction);
// logger.debug("This is a debug message");
// logger.info({ key: "value", anotherKey: [1, 2, 3] });
// logger.warn(["This", "is", "a", "warning", "message"]);
// logger.error(new Error("This is an error message")); 使用日志
import { createLogger } from '@/utils/common'
const logger = createLogger()
const logger = createLogger(false)//所有环境都关闭日志
// 示例使用
logger.debug("This is a debug message");
logger.info({ key: "value", anotherKey: [1, 2, 3] });
logger.warn(["This", "is", "a", "warning", "message"]);
logger.error(new Error("This is an error message")); |
Taro Typescript 类型路由路由参数
export type RouterParams = {
vpid: string
}
const AnswerCatalogPage = () => {
const { vpid: vpidInUrl } = useRouter<RouterParams>().params //新版taro已支持泛型参数
const { vpid: vpidInUrl } = useRouter().params as unknown as RouterParams//旧版taro hack写法,不需要再使用
......
}
|
请求封装useQuery类型
// QueryOptions 接口支持泛型
export interface QueryOptions<
UrlParams extends object| undefined = undefined,
RequestBody extends object | undefined = undefined
> {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
params?: UrlParams
data?: RequestBody
headers?: Record<string, string>
debounce?: number
autoFetch?: boolean
}
// QueryResult 接口支持泛型
export interface QueryResult<
ResponseBody,
UrlParams extends object| undefined = undefined,
RequestBody extends object | undefined = undefined
> {
data: ResponseBody | null
error: Error | null
statusCode: number | null
isLoading: boolean
refetch: (options?: Partial<QueryOptions<UrlParams, RequestBody>>) => Promise<ResponseBody>
}
// ApiResponseConfig 接口
export interface ApiResponseConfig<ResponseBody> {
isSuccessful: (data: ResponseBody) => boolean
getErrorMessage: (statusCode: number, data: ResponseBody) => string
}
// 定义 ApiConfig 接口
export interface ApiConfig {
baseUrl: string
headers: Record<string, string>
timeout?: number
withCredentials?: boolean
}
export interface GetConfig<UrlParams extends object| undefined = undefined> {
url: string
urlParams?: UrlParams
} 配置默认配置
import { ApiConfig, ApiResponseConfig, GetConfig } from './type'
// 默认 API 配置
const defaultConfig: ApiConfig = {
baseUrl: process.env.TARO_APP_API || '',//https://ax
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer yourToken'
},
timeout: 5000,
withCredentials: false
}
// 动态获取 API 配置
export const getApiConfig = <UrlParams extends object>(config: GetConfig<UrlParams>): ApiConfig => {
// 这里可以添加逻辑以动态调整配置,例如基于环境变量或用户会话信息
return {
...defaultConfig,
baseUrl: config.url || defaultConfig.baseUrl
}
}
// 默认的业务规则配置对象
export const defaultApiResponseConfig: ApiResponseConfig<unknown> = {
isSuccessful: (data) => true,
getErrorMessage: (statusCode, data) => `Failed with status code ${statusCode}`
} 业务配置
import { encodeToLower, generateSignedUrl } from '../str'
import { getDevMode, getStore } from '../utils'
import Taro from '@tarojs/taro'
import { ApiConfig, ApiResponseConfig, GetConfig } from './type'
//会修改url
export function getFWRequestConfig<UrlParams extends object | undefined = undefined>(
config: GetConfig<UrlParams>
): ApiConfig {
let { url, urlParams = {} } = config
const access_token = Taro.getStorageSync('access_token')
// 根据请求的不同设置特定的请求头和参数
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'FW-Account-Id': getStore('AccountId'),
'fw-device-platform': process.env.TARO_APP_FW_DEVICE_PLATFORM || 'web',
'fw-push-token': getStore('openId'),
'fw-dev-mode': getDevMode() || ''
}
if (url.includes('MiniProgram') || url.includes('GetAccessToken')) {
headers['content-type'] = 'application/x-www-form-urlencoded'
const timestamp = new Date().getTime()
let sign
let queryString = ''
if (url.includes('GetAccessToken')) {
sign = generateSignedUrl({ ...urlParams, timestamp })
} else {
sign = generateSignedUrl({ ...urlParams, access_token, timestamp })
queryString = Object.keys(urlParams)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(urlParams[key])}`)
.join('&')
}
url += `?${queryString}×tamp=${timestamp}&sign=${sign}`
if (access_token) {
url += `&access_token=${encodeToLower(access_token)}`
}
}
// 构造请求配置对象
const requestConfig: ApiConfig = {
headers,
baseUrl: process.env.TARO_APP_API || '', //'https://apxlt', //process.env.TARO_APP_API
timeout: 50000,
withCredentials: true
}
return requestConfig
}
// 默认的业务规则配置对象
export const defaultApiResponseConfigFW: ApiResponseConfig<any> = {
isSuccessful: (data) => data.result === 1,
getErrorMessage: (statusCode, data) => data.msg || `Failed with status code ${statusCode}`
} useQuery
import { useEffect, useState, useRef, useCallback } from 'react'
import Taro from '@tarojs/taro'
import { defaultApiResponseConfigFW, getFWRequestConfig } from './apiConfigFW'
import { ApiConfig, ApiResponseConfig, GetConfig, QueryOptions, QueryResult } from './type'
import { defaultApiResponseConfig, getApiConfig } from './apiConfig'
import { createLogger } from '@/utils/common'
const logger = createLogger()
// Function to convert object to URL query parameters string
const toUrlParams = (urlParams: Record<string, any>): string => {
const queryString = Object.keys(urlParams)
.map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(urlParams[key]))
.join('&')
return queryString ? `?${queryString}` : ''
}
// useQuery 钩子支持泛型
export function useQuery<
ResponseBody,
UrlParams extends object | undefined = undefined,
RequestBody extends object | undefined = undefined
>(
endpoint: string,
initialOptions?: QueryOptions<UrlParams, RequestBody>,
apiConfigInput: ApiConfig | ((config: GetConfig<UrlParams>) => ApiConfig) = getFWRequestConfig,
responseConfig: ApiResponseConfig<ResponseBody> = defaultApiResponseConfigFW
): QueryResult<ResponseBody, UrlParams, RequestBody> {
const apiConfig =
typeof apiConfigInput === 'function'
? apiConfigInput({ url: endpoint, urlParams: initialOptions?.params })
: apiConfigInput
const [data, setData] = useState<ResponseBody | null>(null)
const [error, setError] = useState<Error | null>(null)
const [statusCode, setStatusCode] = useState<number | null>(null)
const [isLoading, setLoading] = useState<boolean>(false)
const lastCall = useRef<{ timestamp: number; key: string }>({ timestamp: 0, key: '' })
const fetchData = useCallback(
async (options: QueryOptions<UrlParams, RequestBody>): Promise<ResponseBody> => {
const now = Date.now()
const debounce = options.debounce || 300 // 默认防抖时间为 300 毫秒
const requestKey = `${endpoint}-${JSON.stringify(options.params || {})}-${JSON.stringify(
options.data || {}
)}`
if (now - lastCall.current.timestamp < debounce && lastCall.current.key === requestKey) {
return Promise.reject(new Error('Too many requests in a short time'))
}
lastCall.current = { timestamp: now, key: requestKey }
setLoading(true)
setError(null)
const { method = 'GET', headers, params, data } = options
const requestHeaders = { ...apiConfig.headers, ...headers }
const urlParams = method === 'GET' && params ? toUrlParams(params) : ''
const fullUrl = endpoint.startsWith('http')
? endpoint + urlParams
: `${apiConfig.baseUrl}${endpoint}${urlParams}`
const requestBody = method !== 'GET' ? data : undefined
try {
const response = await Taro.request<ResponseBody>({
url: fullUrl,
method,
data: requestBody,
header: requestHeaders,
timeout: apiConfig.timeout,
credentials: apiConfig.withCredentials ? 'include' : 'omit'
})
setStatusCode(response.statusCode)
if (
response.statusCode >= 200 &&
response.statusCode < 300 &&
responseConfig.isSuccessful(response.data)
) {
setData(response.data)
setError(null)
setLoading(false)
return response.data
} else {
const msg = responseConfig.getErrorMessage(response.statusCode, response.data)
const newError = new Error(msg)
setError(newError)
setData(null)
setLoading(false)
return Promise.reject(newError)
}
} catch (err) {
setError(err as Error)
setData(null)
setLoading(false)
return Promise.reject(err)
}
},
[apiConfig, endpoint, responseConfig]
)
useEffect(() => {
if (initialOptions?.autoFetch) {
fetchData(initialOptions).catch(console.error)
}
return () => {}
}, [initialOptions?.autoFetch])
const refetch = useCallback(
async (options: Partial<QueryOptions<UrlParams, RequestBody>> = {}) => {
const {
method,
params = {},
data = {},
headers = {},
debounce,
autoFetch
} = initialOptions || {}
const {
method: newMethod,
params: newParams = {},
data: newData = {},
headers: newHeaders = {},
debounce: newDebounce,
autoFetch: newAutoFetch
} = options || {}
const mergedParams: UrlParams = { ...params, ...newParams } as UrlParams
const mergedData = { ...data, ...newData } as RequestBody
const mergeOptions: QueryOptions<UrlParams, RequestBody> = {
method: newMethod || method,
params: mergedParams,
data: mergedData,
headers: { ...headers, ...newHeaders },
debounce: newDebounce !== undefined ? newDebounce : debounce,
autoFetch: newAutoFetch !== undefined ? newAutoFetch : autoFetch
}
// logger.info('options:', options)
// logger.info('initialOptions:', initialOptions)
// logger.info('mergedParams:', mergedParams)
// logger.info('mergedData:', mergedData)
// logger.info('mergeOptions:', mergeOptions)
return fetchData(mergeOptions)
},
[fetchData, initialOptions]
)
return { data, error, statusCode, isLoading, refetch }
}
useGet
import { QueryResult } from './type'
import { useQuery } from './useQuery'
export const useGet = <ResponseBody, UrlParams extends object>(
endpoint: string,
params?: UrlParams,
headers?: Record<string, string>,
debounce?: number,
autoFetch: boolean = false
): QueryResult<ResponseBody, UrlParams, undefined> => {
return useQuery<ResponseBody, UrlParams, undefined>(endpoint, {
method: 'GET',
params,
headers,
debounce,
autoFetch
})
} usePost
import { QueryResult } from './type'
import { useQuery } from './useQuery'
export const usePost = <ResponseBody, RequestBody extends object>(
endpoint: string,
data?: RequestBody,
headers?: Record<string, string>,
debounce?: number,
autoFetch: boolean = false
): QueryResult<ResponseBody, undefined, RequestBody> => {
return useQuery<ResponseBody, undefined, RequestBody>(endpoint, {
method: 'POST',
data,
headers,
debounce,
autoFetch
})
} usePut
import { QueryResult } from './type'
import { useQuery } from './useQuery'
export const usePut = <ResponseBody, RequestBody extends object | undefined = undefined>(
endpoint: string,
data?: RequestBody,
headers?: Record<string, string>,
debounce?: number,
autoFetch: boolean = false
): QueryResult<ResponseBody, undefined, RequestBody> => {
return useQuery<ResponseBody, undefined, RequestBody>(endpoint, {
method: 'PUT',
data,
headers,
debounce,
autoFetch
})
} useDelete
import { QueryResult } from './type'
import { useQuery } from './useQuery'
export const useDelete = <ResponseBody, UrlParams extends object>(
endpoint: string,
params?: UrlParams,
headers?: Record<string, string>,
debounce?: number,
autoFetch: boolean = false
): QueryResult<ResponseBody, UrlParams, undefined> => {
return useQuery<ResponseBody, UrlParams, undefined>(endpoint, {
method: 'DELETE',
params,
headers,
debounce,
autoFetch
})
} 示例
import Taro from '@tarojs/taro'
import { useState } from 'react'
import { useGet, usePost, usePut, useDelete } from '../' // 假设这些钩子定义在 'hooks' 文件中
// API 响应的数据类型
export interface MyDataType {
id: number
title: string
content: string
createdAt: string
}
// API GET 请求的参数类型
interface MyParamsType {
id: number
}
// POST 请求的体结构
interface MyPostBodyType {
title: string
content: string
}
// PUT 请求的体结构,假设与 POST 类似
interface MyPutBodyType {
title: string
content: string
}
const MyComponent = () => {
// 使用自定义钩子处理 API 请求
const {
data: getData,
error: getError,
isLoading: getLoading,
refetch: refetchGet
} = useGet<MyDataType, MyParamsType>('/data', { id: 123 }, {}, 300, true)
const {
data: postData,
error: postError,
isLoading: postLoading,
refetch: refetchPost
} = usePost<MyDataType, MyPostBodyType>('/data', {
title: '标题',
content: 'Hello World'
})
const {
data: putData,
error: putError,
isLoading: putLoading,
refetch: refetchPut
} = usePut<MyDataType, MyPutBodyType>('/data/123', {
title: '标题',
content: 'Updated Content'
})
const {
data: deleteData,
error: deleteError,
isLoading: deleteLoading,
refetch: refetchDelete
} = useDelete<MyDataType, MyParamsType>('/data/123')
return (
<view>
{getLoading ? (
<text>Loading data...</text>
) : getError ? (
<text>Error loading data: {getError.message}</text>
) : (
<view>
<text>Data Loaded:</text>
<pre>{JSON.stringify(getData, null, 2)}</pre>
<button onClick={() => refetchGet()}>Refresh Data</button>
</view>
)}
{postLoading ? (
<text>Sending data...</text>
) : postError ? (
<text>Error posting data: {postError.message}</text>
) : (
<view>
<text>Data Posted:</text>
<pre>{JSON.stringify(postData, null, 2)}</pre>
</view>
)}
{putLoading ? (
<text>Updating data...</text>
) : putError ? (
<text>Error updating data: {putError.message}</text>
) : (
<view>
<text>Data Updated:</text>
<pre>{JSON.stringify(putData, null, 2)}</pre>
</view>
)}
{deleteLoading ? (
<text>Deleting data...</text>
) : deleteError ? (
<text>Error deleting data: {deleteError.message}</text>
) : (
<view>
<text>Data Deleted:</text>
<pre>{JSON.stringify(deleteData, null, 2)}</pre>
</view>
)}
</view>
)
}
export default MyComponent
import { View, Button, Input, Text } from '@tarojs/components'
import { useState } from 'react'
import { useQuery } from '../useQuery' // 假设这个路径是你的 useQuery 钩子路径
interface User {
id: number
name: string
}
const FetchUsersComponent = () => {
const [search, setSearch] = useState('')
const { data, error, isLoading, refetch } = useQuery<User[], { name?: string }>('/users', {
autoFetch: false
})
const handleFetchClick = () => {
refetch({ params: { name: search } }) // 根据当前输入框的内容发起搜索
}
return (
<View>
<Input onInput={(e) => setSearch(e.detail.value)} placeholder="输入用户名进行搜索" />
<Button onClick={handleFetchClick}>加载用户</Button>
{isLoading && <Text>加载中...</Text>}
{error && <Text>错误:{error.message}</Text>}
{data && data.map((user) => <View key={user.id}>{user.name}</View>)}
</View>
)
}
export default FetchUsersComponent import React from 'react'
import Taro from '@tarojs/taro'
import { useState } from 'react'
import { useGet, usePost, usePut, useDelete } from '../'
import { Button, View, Text, Textarea, Input } from '@tarojs/components'
export interface MyDataType {
id: number
title: string
content: string
createdAt: string
}
// API GET 请求的参数类型
interface MyParamsType {
id: number
}
// POST 请求的体结构
interface MyPostBodyType {
title: string
content: string
}
// PUT 请求的体结构,假设与 POST 类似
interface MyPutBodyType {
title: string
content: string
}
const MyComponent = () => {
const [postInput, setPostInput] = useState({ title: '', content: '' })
const [deleteId, setDeleteId] = useState<number | null>(null)
const {
data: getData,
error: getError,
isLoading: getLoading,
refetch: refetchGet
} = useGet<MyDataType, MyParamsType>('/data', { id: 123 }, {}, 300, true)
const {
data: postData,
error: postError,
isLoading: postLoading,
refetch: refetchPost
} = usePost<MyDataType, MyPostBodyType>('/data', {
title: 'Hello World',
content: 'This is a new post'
})
const {
data: putData,
error: putError,
isLoading: putLoading,
refetch: refetchPut
} = usePut<MyDataType, MyPutBodyType>('/data/123', {
title: 'Updated Title',
content: 'Updated Content'
})
const {
data: deleteData,
error: deleteError,
isLoading: deleteLoading,
refetch: refetchDelete
} = useDelete<MyDataType, MyParamsType>('/data/123')
const handleInputChange = (field, value) => {
setPostInput((Viewv) => ({ ...Viewv, [field]: value }))
}
const handleSubmit = () => {
// Explicitly passing the latest data to refetchPost
refetchPost({ data: postInput })
}
const handleDelete = () => {
if (deleteId) {
refetchDelete({ params: { id: deleteId } })
}
}
return (
<View>
<View>
<Text>Data Loaded:</Text>
<View>{JSON.stringify(getData, null, 2)}</View>
<Button onClick={() => refetchGet({ params: { id: 123 } })}>Refresh Data</Button>
</View>
<View>
<Text>Data Posted:</Text>
<View>{JSON.stringify(postData, null, 2)}</View>
</View>
<View>
<Input
onInput={(e) => handleInputChange('title', e.detail.value)}
placeholder="Enter title"
/>
<Textarea
onInput={(e) => handleInputChange('content', e.detail.value)}
placeholder="Enter content"
/>
<Button onClick={handleSubmit}>Submit New Post</Button>
<Text>Data Posted:</Text>
<View>{JSON.stringify(postData, null, 2)}</View>
</View>
<View>
<Button
onClick={() =>
refetchPut({
data: { title: 'Updated Again', content: 'Further updated content' }
})
}
>
Update Data
</Button>
</View>
<View>
<Text>Data Deleted:</Text>
<View>{JSON.stringify(deleteData, null, 2)}</View>
<Button onClick={() => refetchDelete()}>Delete Data</Button>
</View>
<View>
{/* 其他组件代码不变 */}
<Input
type="number"
onInput={(e) => setDeleteId(Number(e.detail.value))}
placeholder="Enter ID to delete"
/>
<Button onClick={handleDelete}>Delete Data</Button>
{deleteLoading ? (
<Text>Deleting data...</Text>
) : deleteError ? (
<Text>Error deleting data: {deleteError.message}</Text>
) : (
<Text>Data Deleted: {JSON.stringify(deleteData, null, 2)}</Text>
)}
</View>
</View>
)
}
export default MyComponent import { View, Text } from '@tarojs/components'
import { useQuery } from '../useQuery'
// 定义要获取的数据类型
interface UserData {
id: number
name: string
email: string
}
// 定义请求参数类型
interface GetUserParams {
userId: number
}
const UserComponent = () => {
const params: GetUserParams = { userId: 1 }
const { data, error, isLoading, refetch } = useQuery<UserData, GetUserParams>('/users', {
method: 'GET',
params
})
if (isLoading) {
return <Text>Loading...</Text>
}
if (error) {
return (
<View>
<Text>Error: {error.message}</Text>
<Text onClick={() => refetch()}>Retry</Text>
</View>
)
}
return (
<View>
<Text>User Name: {data?.name}</Text>
<Text>User Email: {data?.email}</Text>
</View>
)
}
export default UserComponent import { View, Text } from '@tarojs/components'
import { useQuery } from '../useQuery'
const MyComponent = () => {
const { refetch } = useQuery<{ id: number; name: string }>('/data')
const handleRefetch = () => {
refetch()
.then((data) => {
console.log('Fetched data:', data)
})
.catch((error) => {
console.error('Error fetching data:', error)
})
}
const handleRefetchTry = async () => {
try {
const result = await refetch()
console.log('测试useQuery2 refetch 返回:', result)
} catch (error) {
console.log('测试useQuery2 refetch 异常:', error)
}
}
return <button onClick={handleRefetch}>Refetch Data</button>
} import { View, Text, Button } from '@tarojs/components';
import { useDelete } from '../useDelete';
const DeletePostComponent = () => {
const { data, error, isLoading, refetch } = useDelete<{ deleted: boolean },Record<string,string>>('/posts/1');
if (isLoading) return <Text>Deleting...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<View>
<Text>Post Deleted: {data?.deleted ? 'Yes' : 'No'}</Text>
<Button onClick={() => refetch()}>Delete Again</Button>
</View>
);
};
export default DeletePostComponent; import { View, Text } from '@tarojs/components';
import { useGet } from '../useGet';
interface UserData {
id: number;
name: string;
email: string;
}
interface UserParams {
userId: number;
}
const GetUserComponent = () => {
const params: UserParams = { userId: 1 };
const { data, error, isLoading } = useGet<UserData, UserParams>('/user', params);
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return <View><Text>Name: {data?.name}</Text><Text>Email: {data?.email}</Text></View>;
}; import { View, Text, Button } from '@tarojs/components';
import { usePost } from '../usePost';
interface PostData {
title: string;
content: string;
}
const PostComponent = () => {
const postData: PostData = { title: "New Post", content: "Hello World" };
const { data, error, isLoading, refetch } = usePost<{ success: boolean; id: number },PostData>('/posts', postData);
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<View>
<Text>Post Created: {data?.success ? 'Yes' : 'No'}</Text>
<Button onClick={() => refetch()}>Create Again</Button>
</View>
);
};
export default PostComponent; import { View, Text, Button } from '@tarojs/components';
import { usePut } from '../usePut';
interface UpdateData {
title: string;
}
const UpdatePostComponent = () => {
const updateData: UpdateData = { title: "Updated Title" };
const { data, error, isLoading, refetch } = usePut<{ updated: boolean },UpdateData>('/posts/1', updateData);
if (isLoading) return <Text>Updating...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<View>
<Text>Post Updated: {data?.updated ? 'Yes' : 'No'}</Text>
<Button onClick={() => refetch()}>Update Again</Button>
</View>
);
};
export default UpdatePostComponent; |
Toastimport React from 'react'
import Taro from '@tarojs/taro'
import { render, unmountComponentAtNode } from '@tarojs/react'
import { View, Text, Image, RootPortal } from '@tarojs/components'
import { document } from '@tarojs/runtime'
let toastIdCounter = 0; // 计数器用于生成唯一 ID
interface ToastProps {
visible: boolean
message: string
duration: number
icon?: string
}
const Toast = ({ visible, message, duration = 2000, icon }: ToastProps) => {
const containerStyle = {
position: 'fixed' as 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
display: visible ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: visible ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0)',
transition: 'all ease-in-out 0.1s'
}
const descStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
maxWidth: '80vw',
minHeight: '50px',
backgroundColor: 'rgba(0,0,0,0.8)',
color: '#fff',
padding: '10px 20px',
borderRadius: '5px'
}
return (
<RootPortal>
<View style={containerStyle}>
{icon && <Image style={{ marginRight: '10px' }} src={icon} />}
<Text style={descStyle}>{message}</Text>
</View>
</RootPortal>
)
}
export const TaroToast = ({ message, duration = 2000, icon = '' }) => {
const id = `toast-${toastIdCounter++}`; // 生成唯一 ID
const view = document.createElement('view');
view.id = id;
const currentPages = Taro.getCurrentPages();
const currentPage = currentPages[currentPages.length - 1];
const path = currentPage.$taroPath;
const pageElement = document.getElementById(path);
render(<Toast visible={true} message={message} duration={duration} icon={icon} />, view);
pageElement?.appendChild(view);
if (duration !== 0) {
setTimeout(() => {
destroyToast(view);
}, duration);
}
if (duration === 0) {
return () => destroyToast(view);
}
}
export const destroyToast = (node) => {
const currentPages = Taro.getCurrentPages();
const currentPage = currentPages[currentPages.length - 1];
const path = currentPage.$taroPath;
const pageElement = document.getElementById(path);
unmountComponentAtNode(node);
pageElement?.removeChild(node);
}
export const toast = (message: string, duration = 2000) => {
TaroToast({ message, duration });
}
//使用示例
// import { toast } from '@/components/common'
// const handleToast=()=>{
// toast('提示1')
// toast('提示2')
// toast('提示3')
// setTimeout(()=>{
// toast('提示4')
// },10)
// }
使用示例
import { toast } from '@/components/common'
const handleToast=()=>{
toast('提示1')
toast('提示2')
toast('提示3')
setTimeout(()=>{
toast('提示4')
},10)
} |
Taro Next
CLI 工具安装
注意事项
项目初始化
依赖迁移
The text was updated successfully, but these errors were encountered: