Taro+React跨端开发实战指南
Taro跨端开发各阶段具体实现(React版)
本文提供基于React+Taro 3.x的全阶段可落地实现方案,包含完整代码示例、操作命令和验证步骤,所有代码均可直接复制运行,并适配微信小程序和H5双端。
前置环境验证
在开始之前,请先确认开发环境符合要求,执行以下命令进行验证:
# 验证Node版本(v14+)
node -v # 输出如 v16.18.0
# 验证npm版本
npm -v # 输出如 8.19.2
# 验证Taro CLI版本(v3+)
taro -v # 输出如 taro/cli: 3.6.18
# 配置淘宝镜像以加速依赖安装
npm config set registry https://registry.npmmirror.com
阶段一:基础准备 - 具体实现
步骤1:初始化项目
执行以下命令创建并初始化项目:
# 创建项目(命名为myFirstTaro)
taro init myFirstTaro
# 初始化时的交互选择(按回车确认):
# ? 请输入项目介绍!→ (直接回车)
# ? 请选择框架 React
# ? 请选择CSS预处理器 Sass
# ? 请选择模板源 github
# ? 请选择模板 default
# ? 请选择包管理器 npm
# 进入项目目录
cd myFirstTaro
# 安装依赖
npm install
步骤2:解读初始项目结构(核心文件)
了解初始项目结构对于后续开发至关重要,以下为核心文件及其作用:
src/app.ts:项目入口文件,负责全局配置和生命周期管理。src/app.config.ts:全局配置文件,对应小程序的app.json,用于配置页面路由、窗口样式等。src/pages/index:首页目录,包含页面逻辑(index.tsx)、样式(index.scss)和配置(index.config.ts)。config/index.ts:Taro编译配置文件,可自定义多端打包规则。
步骤3:实现“显示当前时间”的自定义页面
3.1 添加新页面路由
修改 src/app.config.ts,在pages数组中添加 time 页面路由:
export default defineAppConfig({
pages: [
'pages/index/index', // 原有首页
'pages/time/time' // 新增时间页面
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'Taro学习',
navigationBarTextStyle: 'black'
}
})
3.2 创建时间页面文件
在 src/pages 下新建 time 目录,并创建以下三个文件:
- time.config.ts(页面配置):
export default definePageConfig({
navigationBarTitleText: '当前时间'
})
- time.tsx(页面逻辑):
import { useState, useEffect } from 'react'
import { View, Text } from '@tarojs/components'
import './time.scss'
export default function TimePage() {
const [currentTime, setCurrentTime] = useState('')
useEffect(() => {
const updateTime = () => {
const now = new Date()
setCurrentTime(now.toLocaleString('zh-CN'))
}
updateTime()
const timer = setInterval(updateTime, 1000)
return () => clearInterval(timer)
}, [])
return (
<View className="time-page">
<Text className="title">当前时间</Text>
<Text className="time-text">{currentTime}</Text>
</View>
)
}
- time.scss(页面样式):
.time-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
.title {
font-size: 32rpx;
color: #333;
margin-bottom: 20rpx;
}
.time-text {
font-size: 28rpx;
color: #666;
}
}
步骤4:运行项目验证
4.1 微信小程序端
启动微信小程序开发服务:
taro build --type weapp --watch
打开微信开发者工具,导入项目并选择 myFirstTaro/dist 目录,进入“当前时间”页面即可看到每秒刷新的时间。
4.2 H5端
启动H5开发服务:
taro build --type h5 --watch
浏览器访问 http://localhost:10086,点击底部导航或手动输入路由 /pages/time/time,验证时间显示正常。
阶段二:项目实战 - 具体实现
本阶段目标是实现一个“简易待办事项”Demo,包含组件封装、状态管理和多页面通信。
步骤1:封装通用UI组件
在 src 下新建 components 目录,并创建以下通用组件:
- 按钮组件(Button/index.tsx):
import { FC, ReactNode } from 'react'
import { Button as TaroButton } from '@tarojs/components'
import './index.scss'
interface MyButtonProps {
text: ReactNode;
type?: 'primary' | 'default' | 'danger';
size?: 'large' | 'middle' | 'small';
onClick?: () => void;
}
const MyButton: FC<MyButtonProps> = ({
text,
type = 'default',
size = 'middle',
onClick
}) => {
return (
<TaroButton
className={`my-button my-button--${type} my-button--${size}`}
onClick={onClick}
>
{text}
</TaroButton>
)
}
export default MyButton
- 按钮样式(Button/index.scss):
.my-button {
border-radius: 8rpx;
border: none;
&--primary {
background-color: #1677ff;
color: #fff;
}
&--default {
background-color: #f5f5f5;
color: #333;
}
&--danger {
background-color: #ff4d4f;
color: #fff;
}
&--large {
width: 100%;
height: 88rpx;
font-size: 32rpx;
}
&--middle {
width: 200rpx;
height: 68rpx;
font-size: 28rpx;
}
&--small {
width: 120rpx;
height: 48rpx;
font-size: 24rpx;
}
}
- 输入组件(Input/index.tsx):
import { FC, useState } from 'react'
import { View, Input as TaroInput, Text } from '@tarojs/components'
import './index.scss'
interface MyInputProps {
placeholder?: string;
value: string;
onChange: (val: string) => void;
label?: string;
required?: boolean;
}
const MyInput: FC<MyInputProps> = ({
placeholder = '请输入内容',
value,
onChange,
label,
required = false
}) => {
return (
<View className="my-input-wrapper">
{label && (
<Text className="my-input-label">
{label}
{required && <Text className="required">*</Text>}
</Text>
)}
<TaroInput
className="my-input"
placeholder={placeholder}
value={value}
onInput={(e) => onChange(e.detail.value)}
/>
</View>
)
}
export default MyInput
- 输入组件样式(Input/index.scss):
.my-input-wrapper {
width: 100%;
margin-bottom: 20rpx;
.my-input-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
}
.my-input {
width: 100%;
height: 72rpx;
padding: 0 20rpx;
border: 1px solid #e5e5e5;
border-radius: 8rpx;
font-size: 28rpx;
}
}
- 卡片组件(Card/index.tsx):
import { FC, ReactNode } from 'react'
import { View, Text } from '@tarojs/components'
import './index.scss'
interface MyCardProps {
title?: ReactNode;
content: ReactNode;
extra?: ReactNode;
}
const MyCard: FC<MyCardProps> = ({
title,
content,
extra
}) => {
return (
<View className="my-card">
{title && <Text className="my-card__title">{title}</Text>}
<View className="my-card__content">{content}</View>
{extra && <Text className="my-card__extra">{extra}</Text>}
</View>
)
}
export default MyCard
- 卡片样式(Card/index.scss):
.my-card {
background-color: #fff;
border-radius: 8rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
&__title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
}
&__content {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
&__extra {
font-size: 26rpx;
color: #1677ff;
margin-top: 12rpx;
text-align: right;
}
}
步骤2:状态管理(内置provide/inject)
2.1 创建全局状态上下文(src/contexts/TodoContext.tsx)
import { createContext, useContext, useState, ReactNode } from 'react'
import Taro from '@tarojs/taro'
export interface TodoItem {
id: string;
content: string;
createTime: string;
completed: boolean;
}
interface TodoContextType {
todos: TodoItem[];
addTodo: (content: string) => void;
deleteTodo: (id: string) => void;
toggleTodo: (id: string) => void;
}
const TodoContext = createContext<TodoContextType | null>(null)
export const TodoProvider = ({ children }: { children: ReactNode }) => {
const [todos, setTodos] = useState<TodoItem[]>(() => {
const storedTodos = Taro.getStorageSync('todos') || []
return storedTodos
})
const addTodo = (content: string) => {
if (!content.trim()) return
const newTodo: TodoItem = {
id: Date.now().toString(),
content,
createTime: new Date().toLocaleString('zh-CN'),
completed: false
}
const newTodos = [...todos, newTodo]
setTodos(newTodos)
Taro.setStorageSync('todos', newTodos)
}
const deleteTodo = (id: string) => {
const newTodos = todos.filter(todo => todo.id !== id)
setTodos(newTodos)
Taro.setStorageSync('todos', newTodos)
}
const toggleTodo = (id: string) => {
const newTodos = todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
setTodos(newTodos)
Taro.setStorageSync('todos', newTodos)
}
return (
<TodoContext.Provider value={{ todos, addTodo, deleteTodo, toggleTodo }}>
{children}
</TodoContext.Provider>
)
}
export const useTodo = () => {
const context = useContext(TodoContext)
if (!context) {
throw new Error('useTodo must be used within a TodoProvider')
}
return context
}
2.2 全局注入Provider(修改src/app.tsx)
import { Component } from 'react'
import { TodoProvider } from './contexts/TodoContext'
import './app.scss'
class App extends Component {
componentDidMount() {}
componentDidShow() {}
componentDidHide() {}
render() {
return (
<TodoProvider>
{this.props.children}
</TodoProvider>
)
}
}
export default App
步骤3:实现待办列表页面(src/pages/todo/list.tsx)
import { View, Text, ScrollView } from '@tarojs/components'
import MyCard from '@/components/Card'
import MyButton from '@/components/Button'
import { useTodo } from '@/contexts/TodoContext'
import Taro from '@tarojs/taro'
import './list.scss'
export default function TodoList() {
const { todos, deleteTodo, toggleTodo } = useTodo()
const goToAdd = () => {
Taro.navigateTo({ url: '/pages/todo/add' })
}
const renderTodos = () => {
if (todos.length === 0) {
return <Text className="empty-tip">暂无待办事项,点击添加吧~</Text>
}
return todos.map(todo => (
<MyCard
key={todo.id}
title={todo.content}
content={`创建时间:${todo.createTime}`}
extra={
<View className="todo-actions">
<MyButton
text={todo.completed ? '未完成' : '已完成'}
type={todo.completed ? 'default' : 'primary'}
size="small"
onClick={() => toggleTodo(todo.id)}
/>
<MyButton
text="删除"
type="danger"
size="small"
onClick={() => deleteTodo(todo.id)}
style={{ marginLeft: 10 }}
/>
</View>
}
/>
))
}
return (
<View className="todo-list-page">
<MyButton
text="添加待办"
type="primary"
size="large"
onClick={goToAdd}
style={{ marginBottom: 20 }}
/>
<ScrollView className="todo-list" scrollY>
{renderTodos()}
</ScrollView>
</View>
)
}
列表页面样式(list.scss):
.todo-list-page {
padding: 20rpx;
min-height: 100vh;
background-color: #f8f8f8;
.todo-list {
height: calc(100vh - 120rpx);
}
.empty-tip {
display: block;
text-align: center;
font-size: 28rpx;
color: #999;
margin-top: 100rpx;
}
.todo-actions {
display: flex;
justify-content: flex-end;
}
}
步骤4:实现添加待办页面(src/pages/todo/add.tsx)
import { useState } from 'react'
import { View } from '@tarojs/components'
import MyInput from '@/components/Input'
import MyButton from '@/components/Button'
import { useTodo } from '@/contexts/TodoContext'
import Taro from '@tarojs/taro'
import './add.scss'
export default function AddTodo() {
const [content, setContent] = useState('')
const { addTodo } = useTodo()
const handleSubmit = () => {
addTodo(content)
setContent('')
Taro.navigateBack()
Taro.showToast({
title: '添加成功',
icon: 'success'
})
}
return (
<View className="add-todo-page">
<MyInput
label="待办内容"
required
placeholder="请输入待办事项"
value={content}
onChange={setContent}
/>
<MyButton
text="提交"
type="primary"
size="large"
onClick={handleSubmit}
/>
</View>
)
}
添加页面样式(add.scss):
.add-todo-page {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 100vh;
}
步骤5:配置待办页面路由(修改src/app.config.ts)
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/time/time',
'pages/todo/list',
'pages/todo/add'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'Taro待办',
navigationBarTextStyle: 'black'
}
})
步骤6:验证待办功能
重启开发服务:taro build --type weapp --watch,在微信开发者工具中进入 /pages/todo/list 页面,进行以下验证:
- 点击“添加待办”,输入内容后提交,列表显示新待办。
- 点击“已完成/未完成”按钮,待办状态切换。
- 点击“删除”按钮,待办项移除。
- 刷新页面后,待办数据从本地存储加载,不丢失。
阶段三:进阶能力 - 具体实现
步骤1:调用Taro原生API(添加“上传图片”功能)
修改添加待办页面(add.tsx),增加图片上传功能:
import { useState } from 'react'
import { View, Image } from '@tarojs/components'
import MyInput from '@/components/Input'
import MyButton from '@/components/Button'
import { useTodo } from '@/contexts/TodoContext'
import Taro from '@tarojs/taro'
import './add.scss'
export default function AddTodo() {
const [content, setContent] = useState('')
const [imageUrl, setImageUrl] = useState('')
const { addTodo } = useTodo()
const chooseImage = () => {
Taro.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
setImageUrl(res.tempFilePaths[0])
},
fail: (err) => {
Taro.showToast({
title: '选择图片失败',
icon: 'none'
})
console.error('选择图片失败:', err)
}
})
}
const handleSubmit = () => {
addTodo(`${content} ${imageUrl ? '[图片]' : ''}`)
setContent('')
setImageUrl('')
Taro.navigateBack()
Taro.showToast({
title: '添加成功',
icon: 'success'
})
}
return (
<View className="add-todo-page">
<MyInput
label="待办内容"
required
placeholder="请输入待办事项"
value={content}
onChange={setContent}
/>
<View className="image-upload">
<MyButton
text="选择图片"
type="default"
size="middle"
onClick={chooseImage}
style={{ marginBottom: 10 }}
/>
{imageUrl && (
<Image
src={imageUrl}
className="preview-image"
mode="widthFix"
/>
)}
</View>
<MyButton
text="提交"
type="primary"
size="large"
onClick={handleSubmit}
/>
</View>
)
}
更新添加页面样式(add.scss):
.add-todo-page {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 100vh;
.image-upload {
margin-bottom: 20rpx;
}
.preview-image {
width: 200rpx;
height: auto;
border-radius: 8rpx;
margin-top: 10rpx;
}
}
步骤2:多端条件编译(区分小程序/H5显示不同按钮)
修改待办列表页面(list.tsx),添加仅小程序显示的分享按钮:
import { ENV_TYPE, getEnv } from '@tarojs/taro'
// 在renderTodos函数中修改extra部分
extra={
<View className="todo-actions">
<MyButton
text={todo.completed ? '未完成' : '已完成'}
type={todo.completed ? 'default' : 'primary'}
size="small"
onClick={() => toggleTodo(todo.id)}
/>
<MyButton
text="删除"
type="danger"
size="small"
onClick={() => deleteTodo(todo.id)}
style={{ marginLeft: 10 }}
/>
{getEnv() === ENV_TYPE.WEAPP && (
<MyButton
text="分享"
type="default"
size="small"
onClick={() => {
Taro.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
}}
style={{ marginLeft: 10 }}
/>
)}
</View>
}
步骤3:性能优化
3.1 包体积分析
安装分析插件:
npm install webpack-bundle-analyzer --save-dev
修改 config/index.ts,添加打包分析配置:
import { defineConfig } from '@tarojs/cli'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
export default defineConfig({
mini: {
webpackChain(chain, webpack) {
if (process.env.NODE_ENV === 'production') {
chain.plugin('bundle-analyzer').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
}
])
}
}
},
h5: {
webpackChain(chain, webpack) {
if (process.env.NODE_ENV === 'production') {
chain.plugin('bundle-analyzer').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'h5-bundle-report.html'
}
])
}
}
}
})
执行生产打包后,打开生成的分析报告文件(如 dist/bundle-report.html),检查体积过大的依赖并进行优化,例如移除无用依赖、压缩图片资源。
3.2 按需加载(路由懒加载)
修改 src/app.config.ts,开启路由懒加载:
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/time/time',
'pages/todo/list',
'pages/todo/add'
],
window: {
// 原有配置...
},
lazyCodeLoading: 'requiredComponents'
})
3.3 渲染优化(避免列表重复渲染)
使用 React.memo 优化待办项组件,在待办列表页面中提取独立组件:
import { memo } from 'react'
const TodoItem = memo(({ todo, toggleTodo, deleteTodo }: {
todo: TodoItem;
toggleTodo: (id: string) => void;
deleteTodo: (id: string) => void;
}) => {
return (
<MyCard
key={todo.id}
title={todo.content}
content={`创建时间:${todo.createTime}`}
extra={
<View className="todo-actions">
<MyButton
text={todo.completed ? '未完成' : '已完成'}
type={todo.completed ? 'default' : 'primary'}
size="small"
onClick={() => toggleTodo(todo.id)}
/>
<MyButton
text="删除"
type="danger"
size="small"
onClick={() => deleteTodo(todo.id)}
style={{ marginLeft: 10 }}
/>
{getEnv() === ENV_TYPE.WEAPP && (
<MyButton
text="分享"
type="default"
size="small"
onClick={() => {
Taro.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
}}
style={{ marginLeft: 10 }}
/>
)}
</View>
}
/>
)
})
// 在renderTodos中使用TodoItem组件
const renderTodos = () => {
if (todos.length === 0) {
return <Text className="empty-tip">暂无待办事项,点击添加吧~</Text>
}
return todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
))
}
阶段四:工程化 - 具体实现
步骤1:深度定制config/index.ts
import { defineConfig } from '@tarojs/cli'
import path from 'path'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
export default defineConfig({
projectName: 'myFirstTaro',
date: '2025-12-18',
designWidth: 750,
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
},
sourceRoot: 'src',
outputRoot: `dist/${process.env.NODE_ENV === 'production' ? 'prod' : 'dev'}`,
plugins: [],
alias: {
'@': path.resolve(__dirname, '..', 'src'),
'@components': path.resolve(__dirname, '..', 'src/components'),
'@contexts': path.resolve(__dirname, '..', 'src/contexts')
},
mini: {
postcss: {
pxtransform: {
enable: true,
config: {}
},
url: {
enable: true,
config: {
limit: 1024
}
},
cssModules: {
enable: false,
config: {
namingPattern: 'module',
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
},
webpackChain(chain, webpack) {
if (process.env.NODE_ENV === 'production') {
chain.plugin('bundle-analyzer').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
}
])
}
chain.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
}
}
})
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
postcss: {
autoprefixer: {
enable: true,
config: {}
},
cssModules: {
enable: false,
config: {
namingPattern: 'module',
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
},
webpackChain(chain, webpack) {
if (process.env.NODE_ENV === 'production') {
chain.plugin('bundle-analyzer').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'h5-bundle-report.html'
}
])
}
}
}
})
步骤2:多环境配置
-
创建环境变量文件:
- 根目录新建
.env.development(开发环境):TARO_ENV=development REACT_APP_API_BASE_URL=https://test-api.example.com - 根目录新建
.env.production(生产环境):TARO_ENV=production REACT_APP_API_BASE_URL=https://api.example.com
- 根目录新建
-
封装环境变量工具(src/utils/env.ts):
import Taro from '@tarojs/taro'
export const env = {
mode: process.env.NODE_ENV || 'development',
apiBaseUrl: process.env.REACT_APP_API_BASE_URL || '',
isDev: process.env.NODE_ENV === 'development',
isProd: process.env.NODE_ENV === 'production'
}
export const request = (url: string, options = {}) => {
const fullUrl = `${env.apiBaseUrl}${url}`
return Taro.request({
url: fullUrl,
...options,
header: {
'Content-Type': 'application/json',
...options.header
}
})
}
- 使用环境变量(示例):
import { env } from '@/utils/env'
console.log('当前环境:', env.mode)
console.log('API地址:', env.apiBaseUrl)
步骤3:代码规范配置
- 安装ESLint/Prettier依赖:
npm install eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
- 根目录新建
.eslintrc.js:
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier'
],
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn'
},
env: {
browser: true,
node: true,
es6: true
}
}
- 根目录新建
.prettierrc.js:
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'avoid',
endOfLine: 'lf'
}
步骤4:单元测试示例
- 安装测试依赖:
npm install jest @tarojs/test-utils @testing-library/react react-test-renderer --save-dev
- 测试待办添加功能(src/contexts/tests/TodoContext.test.tsx):
import { render, act } from '@testing-library/react'
import { TodoProvider, useTodo } from '../TodoContext'
const TestComponent = () => {
const { addTodo, todos } = useTodo()
return (
<div>
<button onClick={() => addTodo('测试待办')}>添加</button>
<span>{todos.length}</span>
</div>
)
}
describe('TodoContext', () => {
it('should add todo correctly', () => {
const { getByText } = render(
<TodoProvider>
<TestComponent />
</TodoProvider>
)
expect(getByText('0')).toBeTruthy()
act(() => {
getByText('添加').click()
})
expect(getByText('1')).toBeTruthy()
})
})
- 运行测试:
npx jest
最终验证与交付
-
运行所有环境验证:
- 开发环境:
npm run dev:weapp/npm run dev:h5 - 生产环境:
npm run build:weapp/npm run build:h5
- 开发环境:
-
检查交付物:
- 完整的跨端项目(包含基础、组件、原生API和工程化配置)。
- 可部署的代码(dist目录)。
- 学习笔记和优化清单。
常见问题解决
- 小程序授权失败:在微信开发者工具中,进入详情→本地设置,勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。
- 样式跨端差异:优先使用rpx单位,避免使用
position: fixed,因为其在H5和小程序端表现可能不同。 - 包体积过大:删除无用依赖、压缩图片、开启代码分割。
- 状态同步问题:确保本地存储修改后同步更新全局状态,避免数据不一致。