Skip to content
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

Open
WangShuXian6 opened this issue Feb 19, 2020 · 10 comments
Open

Taro Next #97

WangShuXian6 opened this issue Feb 19, 2020 · 10 comments
Labels

Comments

@WangShuXian6
Copy link
Owner

WangShuXian6 commented Feb 19, 2020

Taro Next

https://taro-docs.jd.com/taro/next/docs/migration.html

CLI 工具安装

首先,你需要使用 npm 或者 yarn 全局安装@tarojs/cli,或者直接使用npx:

使用 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

项目初始化

使用命令创建模板项目

$ taro init myApp

npm 5.2+ 也可在不全局安装的情况下使用 npx 创建模板项目

$ npx @tarojs/cli init myApp

依赖迁移

项目里只需要加几个依赖,把

import Taro, { useEffect, useLayoutEffect, useReducer, useState, useContext, useRef, useCallback, useMemo, useRouter, useScope, useTabItemTap, useResize, useReachBottom, usePullDownRefresh, useDidHide, useDidShow, usePageScroll } from '@tarojs/taro'

换成

import React, { Component, useEffect, useLayoutEffect, useReducer, useState, useContext, useRef, useCallback, useMemo, } from 'react'
import Taro, { Current, useRouter, useScope, useTabItemTap, useResize, useReachBottom, usePullDownRefresh, useDidHide, useDidShow, usePageScroll } from '@tarojs/taro'

就可以完成迁移,其他代码都不用动

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Feb 19, 2020

多平台独立目录编译配置

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"
}

tsconfig.json

{
  "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
}

README.md

## 开发前必备

>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

文件目录

src/adapters

适配器

faceAdapter

src/assets

静态资源 图片 字体

src/components

组件

src/config

配置,常量

src/pages

页面

src/services

服务[网络通信]

src/store

全局单例存储

src/types

类型定义

src/utils

工具函数

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented May 18, 2020

Taro 组件

异形 轮播图

image

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);
    }
  }
}

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jun 27, 2024

Taro 图表

Taro 使用 Echarts:taro-react-echarts

安装

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;

Taro 使用 taro-f2-react

domisooo/taro-f2-react

安装

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,
    },
  },
}

否则可能会报错“
domisolo/taro-f2-react#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" }}>
      <F2Canvas>
        <Chart data={data}>
          <Interval x="genre" y="sold" />
        </Chart>
      </F2Canvas>
    </View>
  );
};

export default Index;

注意

如果出现错误:折线图平移:拖动图表事件未定义 Event is not defined #21
taro-f2-react 错误详情:domisolo/taro-f2-react#21

或者等待官方 antvis 修复 antvis/F2#1980

解决方案1:等待 官方 antvis 修复。

解决方案2: 锁定版本

{
  ...
  "dependencies": {
    "@antv/f2": "4.0.51",
    "taro-f2-react": "1.1.1"
  }
}

滚动后获取 ScrollBar 当前的区间 range

image

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

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 }
}

@WangShuXian6
Copy link
Owner Author

小程序分包

分包根目录

配置路径必须包含index.tsx文件即 index
src/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 <View>index</View>
}

export default Index

src/pages/healthRecords/home/index.tsx
配置路径必须包含index.tsx文件即 home/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

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: '个人中心',
      },
    ],
  },
});

@WangShuXian6
Copy link
Owner Author

state 与 ref 的监测测试

测试 hooks 中的 模拟请求

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

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<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;

src/pages/query/utils/mock.ts

// 模拟请求函数
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<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

src/pages/query/index.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;

image

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jul 5, 2024

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)

image

作为值使用的枚举类型,需要使用完整路径导入

import { Blood } from '@/pages/healthRecords/utils/types/bloodPressure'

错误
虽然 index 中也引入了枚举 export * from './bloodPressure'

import { Blood } from '@/pages/healthRecords/utils/types/index

process 无法再运行时解析[需要进一步探明原因和解决方案]

因为这是编译时替换,运行时已经不存在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

webpack 不建议在前端项目中使用 process

NervJS/taro#12764

无法使用枚举的值

新定义的枚举值需要重新运行编译命令 npm run dev:weapp
疑似taro将枚举静态替换

export enum UploadNumberType {
  Camera = '1',
  Album = '2'
}

const a=UploadNumberType.Camera //重新编译后才正常,否则 报错 UploadNumberType 未定义

Ref 类型的值不能作为属性传递

因为不会触发重新渲染,也不会触发更新

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jul 15, 2024

自定义颜色的彩色日志

基础

const a='123'
console.log(`%c ${a}`,'background-color: #25cbe9;color:white; ')
console.info(`%c ${a}`,'background-color: #25cbe9;color:white; ')

image

增强

注意 在 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"));

image

使用日志

引入并初始化

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"));

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jul 19, 2024

Taro Typescript 类型

路由

路由参数

路由中 xxxx/index?vpid=123 包含 vpid 参数

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写法,不需要再使用
......
}

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jul 25, 2024

请求封装

useQuery

类型

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<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
}

配置

默认配置

src\utils\query\apiConfig.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<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}&timestamp=${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

src\utils\query\useQuery.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 }
}

useGet

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

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

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

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;

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jul 26, 2024

Toast

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)
}

图片

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant