Open WangShuXian6 opened 4 years ago
config/index.js
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"));
};
package.json
{
"name": "myAppnext",
"version": "1.0.0",
"private": true,
"description": "next test",
"templateInfo": {
"name": "default",
"typescript": true,
"css": "less"
},
"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:quickapp": "taro build --type quickapp",
"build:jd": "taro build --type jd",
"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:quickapp": "npm run build:quickapp -- --watch",
"dev:jd": "npm run build:jd -- --watch"
},
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"author": "",
"dependencies": {
"@babel/runtime": "^7.7.7",
"@tarojs/components": "3.0.0-alpha.2",
"@tarojs/runtime": "3.0.0-alpha.2",
"@tarojs/taro": "3.0.0-alpha.2",
"@tarojs/react": "3.0.0-alpha.2",
"react": "^16.10.0",
"@tbmp/mp-cloud-sdk": "^1.2.2",
"dayjs": "^1.8.20",
"regenerator-runtime": "^0.11.1"
},
"devDependencies": {
"@types/webpack-env": "^1.13.6",
"@types/react": "^16.0.0",
"@tarojs/mini-runner": "3.0.0-alpha.2",
"@babel/core": "^7.8.0",
"babel-preset-taro": "3.0.0-alpha.2",
"eslint-config-taro": "3.0.0-alpha.2",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react-hooks": "^1.6.1",
"stylelint": "9.3.0",
"@typescript-eslint/parser": "^2.x",
"@typescript-eslint/eslint-plugin": "^2.x",
"typescript": "^3.7.0"
}
}
.editorconfig
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
.eslintrc
{
"extends": ["taro"],
"rules": {
"no-unused-vars": ["error", { "varsIgnorePattern": "Taro" }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }]
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"useJSXTextNode": true,
"project": "./tsconfig.json"
}
}
.gitignore
node_modules
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
dist/
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# temp
.temp/
.rn_temp/
deploy_versions/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/
#
dist/
deploy_versions/
.temp/
.rn_temp/
node_modules/
.DS_Store
dist/
dist_weapp/
dist_alipay/
dist_swan/
dist_tt/
dist_h5/
deploy_versions/
.temp/
.rn_temp/
node_modules/
.DS_Store
.idea/
.npmrc
registry=https://registry.npm.taobao.org
disturl=https://npm.taobao.org/dist
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver
operadriver_cdnurl=https://npm.taobao.org/mirrors/operadriver
selenium_cdnurl=https://npm.taobao.org/mirrors/selenium
node_inspector_cdnurl=https://npm.taobao.org/mirrors/node-inspector
fsevents_binary_host_mirror=http://npm.taobao.org/mirrors/fsevents/
babel.config.js
// babel-preset-taro 更多选项和默认值:
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
module.exports = {
presets: [
['taro', {
framework: 'react',
ts: true
}]
]
}
global.d.ts
declare module '*.png';
declare module '*.gif';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.svg';
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.styl';
// @ts-ignore
declare const process: {
env: {
TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq';
[key: string]: any;
}
}
project.config.json
{
"miniprogramRoot": "./dist_alipay/client",
"projectname": "app",
"description": "app",
"appid": "touristappid",
"setting": {
"urlCheck": true,
"es6": false,
"postcss": false,
"minified": false
},
"compileType": "miniprogram"
}
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"removeComments": false,
"preserveConstEnums": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"outDir": "lib",
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"sourceMap": true,
"baseUrl": ".",
"rootDir": ".",
"jsx": "react",
"jsxFactory": "React.createElement",
"allowJs": true,
"resolveJsonModule": true,
"typeRoots": [
"node_modules/@types",
"global.d.ts"
]
},
"exclude": [
"node_modules",
"dist"
],
"compileOnSave": false
}
## 开发前必备
>Taro v3.0.0-alpha.2
>https://taro-docs.jd.com/taro/next/docs/GETTING-STARTED.html
### CLI 工具安装
>全局安装```@tarojs/cli```,或者直接使用npx:
```bash
# 使用 npm 安装 CLI
npm install -g @tarojs/cli@next
# OR 使用 yarn 安装 CLI
yarn global add @tarojs/cli@next
# OR 安装了 cnpm,使用 cnpm 安装 CLI
cnpm install -g @tarojs/cli@next
值得一提的是,如果安装过程出现sass相关的安装错误,请在安装mirror-config-china后重试。
npm install -g mirror-config-china
适配器
faceAdapter
静态资源 图片 字体
组件
配置,常量
页面
服务[网络通信]
全局单例存储
类型定义
工具函数
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);
}
}
}
安装
npm install taro-react-echarts
导入组件
import Echarts from 'taro-react-echarts'
定制下载 Echarts js库 https://echarts.apache.org/zh/builder.html
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;
安装
yarn add taro-f2-react @antv/f2 -S
详细使用文档参考 https://f2.antv.antgroup.com/ @antv/f2
关键
config/index.ts
const config = {
compiler: {
type: 'webpack5',
prebundle: {
enable: false,
},
},
}
否则可能会报错“ https://github.com/domisooo/taro-f2-react/issues/3
页面
src/pages/echarts/index.tsx
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" }}>
</View>
); };
export default Index;
### 注意
>如果出现错误:折线图平移:拖动图表事件未定义 Event is not defined #21
taro-f2-react 错误详情:https://github.com/domisooo/taro-f2-react/issues/21
或者等待官方 antvis 修复 https://github.com/antvis/F2/issues/1980
解决方案1:等待 官方 antvis 修复。
解决方案2: 锁定版本
```json
{
...
"dependencies": {
"@antv/f2": "4.0.51",
"taro-f2-react": "1.1.1"
}
}
src\pages\mock\data2.ts
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 } ]
`src\pages\f3\index.tsx`
```tsx
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
src\pages\f3\useTouchHandlers.ts
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 } }
配置路径必须包含
index.tsx
文件即 indexsrc/pages/healthRecords/index.tsx
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
export default Index
>`src/pages/healthRecords/home/index.tsx`
>配置路径必须包含`index.tsx`文件即 `home/index`
```tsx
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
src/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: '个人中心',
},
],
},
});
node v22.4.0
测试无论嵌套多少hooks,组件,都可以监测 useState 值变更,无法监测useRef值变更
package.json
{ "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" } }
>`src/pages/query/components/TestQueryEffect.tsx`
```tsx
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;
src/pages/query/hooks/useTestHookQuery.ts
//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
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;
>`src/pages/query/utils/mock.ts`
```tsx
// 模拟请求函数
export const mockApiCall = (params: unknown): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Received params: ${JSON.stringify(params)}`);
}, 2000);
});
}
src/pages/query/utils/useQuery.ts
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
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
>`src/pages/query/index.tsx`
```tsx
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;
未严格测试
if (isLoading) return <View>加载中...</View> if (error) return <View>错误: {error.message}</View>
if (data?.result === ResultStatus.Failure) {
return
会报错:
```lua
.._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'
错误 虽然 index 中也引入了枚举
export * from './bloodPressure'
import { Blood } from '@/pages/healthRecords/utils/types/index
因为这是编译时替换,运行时已经不存在process对象, 且webpack5不在前端环境中保留process对象,仅在node环境中存在。
可用
const appid=process.env.TARO_APP_VPID
不可用 会报错 process 未定义
const vpid: string = bMockMode ? process.env.TARO_APP_VPID : vpidInUrl || visitorInfo.VISIT_PATIENT_ID
新定义的枚举值需要重新运行编译命令
npm run dev:weapp
疑似taro将枚举静态替换
export enum UploadNumberType {
Camera = '1',
Album = '2'
}
const a=UploadNumberType.Camera //重新编译后才正常,否则 报错 UploadNumberType 未定义
因为不会触发重新渲染,也不会触发更新
const a='123'
console.log(`%c ${a}`,'background-color: #25cbe9;color:white; ')
console.info(`%c ${a}`,'background-color: #25cbe9;color:white; ')
注意 在 taro中
process.env.NODE_ENV
在编译时直接静态替换 为.env
指定的值,所以每次使用process.env.NODE_ENV
后需要重新执行npm run dev:weapp
等编译命令使其生效,否则该变量将会未定义导致报错或在所有环境该值为假。
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"));
路由中
xxxx/index?vpid=123
包含 vpid 参数export type RouterParams = { vpid: string }
const AnswerCatalogPage = () => {
const { vpid: vpidInUrl } = useRouter
src\utils\query\type.ts
// 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
}
// ApiResponseConfig 接口
export interface ApiResponseConfig
// 定义 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 }
### 配置
#### 默认配置
>`src\utils\query\apiConfig.ts`
```ts
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}`
}
src\utils\query\apiConfigFW.ts
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
// 根据请求的不同设置特定的请求头和参数 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: ApiResponseConfigFailed with status code ${statusCode}
}
### useQuery
>`src\utils\query\useQuery.ts`
```ts
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 }
}
src\utils\query\useGet.ts
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
>`src\utils\query\usePost.ts`
```ts
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
})
}
src\utils\query\usePut.ts
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
>`src\utils\query\useDelete.ts`
```ts
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 (
{JSON.stringify(getData, null, 2)}
{JSON.stringify(postData, null, 2)}
{JSON.stringify(putData, null, 2)}
{JSON.stringify(deleteData, null, 2)}
) }
export default MyComponent
>``
```ts
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;
import 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 工具安装
注意事项
项目初始化
依赖迁移