跳到主要内容

2 篇博文 含有标签「Taro」

查看所有标签

1. 创建项目

npm i -g @tarojs/cli

npm info @tarojs/cli

>taro init 01-base-taro
Taro 即将创建一个新项目!
Need help? Go and open issue: https://tls.jd.com/taro-issue-helper

? 请输入项目介绍 01-base-taro
? 请选择框架 (Use arrow keys)
React
PReact
Vue3
Solid
? 是否需要使用 TypeScript (Y/n)n
? 是否需要编译为 ES5No
? 请选择 CSS 预处理器(Sass/Less/Stylus) (Use arrow keys)
Sass
Less
Stylus

? 请选择包管理工具 (Use arrow keys)
❯ yarn
pnpm
npm
cnpm
? 请选择编译工具 (Use arrow keys)
Webpack5
Vite
? 请选择模板源 (Use arrow keys)
Gitee(最快)
Github(最新)
CLI 内置默认模板
自定义
社区优质模板源
❯ 默认模板
mobx
pwa
react-NutUI(NutUI + React 模板(https://nutui.jd.com/react/))
react-native
react-native-harmony
redux
(Move up and down to reveal more choices)


✔ 初始化 git 成功
执行安装项目依赖 yarn install, 需要一会儿...
yarn install v1.22.22
info No lockfile found.
[1/4] Resolving packages...
warning @tarojs/helper > @swc/register@0.1.10: Use @swc-node/register instead

...



[4/4] Building fresh packages...
success Saved lockfile.
$ husky
Done in 145.78s.
安装项目依赖成功
创建项目 01-base-taro 成功!
请进入项目目录 01-base-taro 开始工作吧!😝
(base) jiexu:002-coder-why-taro/ (master✗) $ cd 01-base-taro                                                                                   [20:56:14]
(base) jiexu:01-base-taro/ (master✗) $ yarn [21:00:14]
yarn install v1.22.22
[1/4] 🔍 Resolving packages...
success Already up-to-date.
$ husky
Done in 0.84s.

1.1 项目运行

  • VSCODE需要安装插件:ESLint, Prettier

  • 用H5打开

yarn dev:h5
  • 小程序打开
yarn dev:weapp

1.1.1 编译运行

  • Taro编译分为 devbuild模式

    • dev 模式(增加 --watch参数) 将会监听文件修改
    • build模式(去掉 --watch参数) 将不会监听文件修改,并会对代码进行压缩打包
  • dev命令启动Taro项目的开发环境

    • pnpm run dev:h5 启动H5端
    • pnpm run dev:weapp 启动小程序端
  • build 命令可以把Taro代码编译成不同端的代码,然后再对应的开发工具中查看效果

    • H5直接在浏览器中可以查看效果
    • 微信小程序需要在《微信开发工具》打开根目录下dist查看效果
    • RN应用需要参考《React Native端开发流程》

1.1.2 项目目录结构

1.2 Taro+React开发规范

  • 为了实现多端兼容,综合考虑编译速度,运行性能等因素,Taro约定了如下开发规范
    • 页面文件遵循 React组件(JSX)规范
    • 组件标签靠近小程序规范(但遵从大驼峰,并导包),详见Taro组件规范
    • 接口能力(JS API)靠近小程序规范,但需要将前缀wx替换为Taro(需要导包),详见Taro接口规范
    • 数据绑定及事件处理同React规范,同时补充了APP及页面的生命周期
    • 为了兼容多端运行,建议使用Flex布局进行开发,推荐使用px单位(750设计稿)
    • 在React中使用Taro内置组件前,必须从 @tarojs/components进行引入
    • 文档直接查看Taro的官网文档: https://docs.taro.zone/docs

1.3 webpack编译配置(config)

  • https://docs.taro.zone/docs/config

  • https://docs.taro.zone/docs/config-detail

  • 编译配置存放于项目根目录下的config目录中,包含三个文件:

    • index.js 是通用配置
    • dev.js 是项目开发时的配置
    • prod.js 是项目生产环境时的配置
  • 常用的配置

    • projectName: 项目名称
    • date: 项目创建时间
    • designWidth: 设计稿尺寸
    • sourceRoot: 项目源码目录
    • outputRoot: 项目产出目录
    • defineConstants: 定义全局的变量(DefinePlugin)
    • alias: 配置路径别名
    • h5:webpackChain: webpack配置
    • h5:devServer: 开发者服务配置

1.3.1 定义常量

// config/index.js
defineConstants: {
VERSION: "'1.0.0'"
},
// .eslintrc
"globals": {
"definePageConfig": "readonly",
"defineAppConfig": "readonly",
"VERSION": "readonly"
}
// src/pages/index/index.jsx
console.log(VERSION)

1.3.2 alias

import path from 'path'

...

alias: {
"@": path.resolve(__dirname, '..', 'src'),
},

1.4 全局配置(app.config.js)

Taro学习笔记前端阅读需 3 分钟

0. 快速链接

1. 安装Taro

  • 目前Taro仅提供一种开发方式:安装 Taro 命令行工具(Taro CLI)进行开发

  • Taro CLI 依赖于 Node.js 环境。

  • 如果Node.js环境存在,输入 npm i -g @tarojs/cli 安装, 安装好之后,输入 taro验证:

(base) jiexu:~/ $ taro                                               [12:01:03]
👽 Taro v4.1.6

2. 初始化项目

2.1 命令行初始化

  • taro init
Taro 即将创建一个新项目!
Need help? Go and open issue: https://tls.jd.com/taro-issue-helper

? 请输入项目名称! 001-docs-larning-notes
? 请输入项目介绍 will add latter
? 请选择框架 (Use arrow keys)
❯ React
PReact
Vue3
Solid
? 是否需要使用 TypeScript ? (Y/n)
? 请选择 CSS 预处理器(Sass/Less/Stylus) 无
? 请选择包管理工具 npm
? 请选择编译工具 Webpack5
? 请选择模板源 Github(最新)
✔ 拉取远程模板仓库成功!
? 请选择模板
wxcloud(云开发模板)
wxplugin
youshu(腾讯有数统计模板(https://nervjs.github.io/taro/docs/youshu))
❯ 默认模板
mobx
pwa
react-NutUI(NutUI + React 模板(https://nutui.jd.com/react/))
(Move up and down to reveal more choices)

3. 认识项目

3.1 入口组件

  • 每一个 Taro 项目都有一个入口组件和一个入口配置,我们可以在入口组件中设置全局状态/全局生命周期,一个最小化的入口组件会是这样:
// src/app.js
import React, { Component } from 'react'
import './app.css'

class App extends Component {
render() {
// this.props.children 是将要会渲染的页面
return this.props.children
}
}

// 每一个入口组件都必须导出一个 React 组件
export default App
// src/app.js
import Vue from 'vue'
import './app.css'

const App = {
render(h) {
// this.$slots.default 是将要会渲染的页面
return h('block', this.$slots.default)
},
}

export default App

每一个入口组件(例如 app.js)总是伴随一个全局配置文件(例如 app.config.js),我们可以在全局配置文件中设置页面组件的路径、全局窗口、路由等信息,一个最简单的全局配置如下:

export default {
pages: ['pages/index/index'],
}
export default {
pages: ['pages/index/index'],
}

你可能会注意到,不管是 React 还是 Vue,两者的全局配置是一样的。

这是因为在配置文件中,Taro 并不关心框架的区别,Taro CLI 会直接在编译时在 Node.js 环境直接执行全局配置的代码,并把 export default 导出的对象序列化为一个 JSON 文件。

因此,我们必须保证配置文件是在 Node.js 环境中是可以执行的,不能使用一些在 H5 环境或小程序环境才能运行的包或者代码,否则编译将会失败。

3.2 页面组件

页面组件是每一项路由将会渲染的页面,Taro 的页面默认放在 src/pages 中,每一个 Taro 项目至少有一个页面组件

一个简单的页面组件如下:

//src/pages/index/index.jsx

import { View } from '@tarojs/components'
class Index extends Component {
state = {
msg: 'Hello World!',
}

onReady() {
console.log('onReady')
}

render() {
return <View>{this.state.msg}</View>
}
}

export default Index
// src/pages/index/index.vue

<template>
<view> {{ msg }} </view>
</template>

<script>
export default {
data() {
return {
msg: 'Hello World!',
}
},
onReady() {
console.log('onReady')
},
}
</script>

与react和vue的细微差别

  • onReady 生命周期函数。这是来源于微信小程序规范的生命周期,表示组件首次渲染完毕,准备好与视图交互。Taro 在运行时将大部分小程序规范页面生命周期注入到了页面组件中,同时 React 或 Vue 自带的生命周期也是完全可以正常使用的。

  • View 组件。这是来源于 @tarojs/components 的跨平台组件。相对于我们熟悉的 div、span 元素而言,在 Taro 中我们要全部使用这样的跨平台组件进行开发。

和入口组件一样,每一个页面组件(例如 index.vue)也会有一个页面配置(例如 index.config.js),我们可以在页面配置文件中设置页面的导航栏、背景颜色等参数,一个最简单的页面配置如下:

// src/pages/index/index.config.js
export default {
navigationBarTitleText: '首页',
}

3.3 自定义组件

  • 我们先把首页写好,首页的逻辑很简单:把论坛最新的帖子展示出来。
// src/pages/index/index.jsx

import Taro from '@tarojs/taro'
import React from 'react'
import { View } from '@tarojs/components'
import { ThreadList } from '../../components/thread_list'
import api from '../../utils/api'

import './index.css'

class Index extends React.Component {
config = {
navigationBarTitleText: '首页',
}

state = {
loading: true,
threads: [],
}

async componentDidMount() {
try {
const res = await Taro.request({
url: api.getLatestTopic(),
})
this.setState({
threads: res.data,
loading: false,
})
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
}

render() {
const { loading, threads } = this.state
return (
<View className="index">
<ThreadList threads={threads} loading={loading} />
</View>
)
}
}

export default Index
// src/pages/index/index.vue

<template>
<view class="index">
<thread-list :threads="threads" :loading="loading" />
</view>
</template>

<script>
import Vue from 'vue'
import Taro from '@tarojs/taro'
import api from '../../utils/api'
import ThreadList from '../../components/thread_list.vue'
export default {
components: {
'thread-list': ThreadList,
},
data() {
return {
loading: true,
threads: [],
}
},
async created() {
try {
const res = await Taro.request({
url: api.getLatestTopic(),
})
this.loading = false
this.threads = res.data
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
},
}
</script>
  • 在我们的首页组件里,还引用了一个 ThreadList 组件,我们现在来实现它:
// src/components/thread_list.jsx

import React from 'react'
import { View, Text } from '@tarojs/components'
import { Thread } from './thread'
import { Loading } from './loading'

import './thread.css'

class ThreadList extends React.Component {
static defaultProps = {
threads: [],
loading: true,
}

render() {
const { loading, threads } = this.props

if (loading) {
return <Loading />
}

const element = threads.map((thread, index) => {
return (
<Thread
key={thread.id}
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
/>
)
})

return <View className="thread-list">{element}</View>
}
}

export { ThreadList }
src/components/thread.jsx



import Taro, { eventCenter } from '@tarojs/taro'
import React from 'react'
import { View, Text, Navigator, Image } from '@tarojs/components'

import api from '../utils/api'
import { timeagoInst, Thread_DETAIL_NAVIGATE } from '../utils'

class Thread extends React.Component {
handleNavigate = () => {
const { tid, not_navi } = this.props
if (not_navi) {
return
}
eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.props)
// 跳转到帖子详情
Taro.navigateTo({
url: '/pages/thread_detail/thread_detail',
})
}

render() {
const { title, member, last_modified, replies, node, not_navi } = this.props
const time = timeagoInst.format(last_modified * 1000, 'zh')
const usernameCls = `author ${not_navi ? 'bold' : ''}`

return (
<View className="thread" onClick={this.handleNavigate}>
<View className="info">
<View>
<Image src={member.avatar_large} className="avatar" />
</View>
<View className="middle">
<View className={usernameCls}>{member.username}</View>
<View className="replies">
<Text className="mr10">{time}</Text>
<Text>评论 {replies}</Text>
</View>
</View>
<View className="node">
<Text className="tag">{node.title}</Text>
</View>
</View>
<Text className="title">{title}</Text>
</View>
)
}
}

export { Thread }
// src/components/thread_list.vue

<template>
<view className="thread-list">
<loading v-if="loading" />
<thread
v-else
v-for="t in threads"
:key="t.id"
:node="t.node"
:title="t.title"
:last_modified="t.last_modified"
:replies="t.replies"
:tid="t.id"
:member="t.member"
/>
</view>
</template>

<script>
import Vue from 'vue'
import Loading from './loading.vue'
import Thread from './thread.vue'
export default {
components: {
loading: Loading,
thread: Thread,
},
props: {
threads: {
type: Array,
default: [],
},
loading: {
type: Boolean,
default: true,
},
},
}
</script>
// src/components/thread.vue

<template>
<view class="thread" @tap="handleNavigate">
<view class="info">
<view>
<image :src="member.avatar_large | url" class="avatar" />
</view>
<view class="middle">
<view :class="usernameCls"> {{member.username}} </view>
<view class="replies">
<text class="mr10">{{time}}</text>
<text>评论 {{replies}}</text>
</view>
</view>
<view class="node">
<text class="tag">{{node.title}}</text>
</view>
</view>
<text class="title">{{title}}</text>
</view>
</template>

<script>
import Vue from 'vue'
import { eventCenter } from '@tarojs/taro'
import Taro from '@tarojs/taro'
import { timeagoInst, Thread_DETAIL_NAVIGATE } from '../utils'
import './thread.css'
export default {
props: ['title', 'member', 'last_modified', 'replies', 'node', 'not_navi', 'tid'],
computed: {
time() {
return timeagoInst.format(this.last_modified * 1000, 'zh')
},
usernameCls() {
return `author ${this.not_navi ? 'bold' : ''}`
},
},
filters: {
url(val) {
return 'https:' + val
},
},
methods: {
handleNavigate() {
const { tid, not_navi } = this.$props
if (not_navi) {
return
}
eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.$props)
// 跳转到帖子详情
Taro.navigateTo({
url: '/pages/thread_detail/thread_detail',
})
},
},
}
</script>

  • 这里可以发现我们把论坛帖子渲染逻辑拆成了两个组件,并放在 src/components 文件中,因为这些组件是会在其它页面中多次用到。 拆分组件的力度是完全由开发者决定的,Taro 并没有规定组件一定要放在 components 文件夹,也没有规定页面一定要放在 pages 文件夹。

  • 另外一个值得注意的点是:我们并没有使用 div/span 这样的 HTML 组件,而是使用了 View/Text 这样的跨平台组件。

3.4 路由与 Tabbar

  • src/components/thread 组件中,我们通过
Taro.navigateTo({ url: '/pages/thread_detail/thread_detail' })

跳转到帖子详情,但这个页面仍未实现,现在我们去入口文件配置一个新的页面:

// src/app.config.js

export default {
pages: ['pages/index/index', 'pages/thread_detail/thread_detail'],
}

然后在路径 src/pages/thread_detail/thread_detail 实现帖子详情页面,路由就可以跳转,我们整个流程就跑起来了:

// src/pages/thread_detail/thread_detail

import Taro from '@tarojs/taro'
import React from 'react'
import { View, RichText, Image } from '@tarojs/components'
import { Thread } from '../../components/thread'
import { Loading } from '../../components/loading'
import api from '../../utils/api'
import { timeagoInst, GlobalState } from '../../utils'

import './index.css'

function prettyHTML (str) {
const lines = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']

lines.forEach(line => {
const regex = new RegExp(`<${line}`, 'gi')

str = str.replace(regex, `<${line} class="line"`)
})

return str.replace(/<img/gi, '<img class="img"')
}

class ThreadDetail extends React.Component {
state = {
loading: true,
replies: [],
content: '',
thread: {}
} as IState

config = {
navigationBarTitleText: '话题'
}

componentWillMount () {
this.setState({
thread: GlobalState.thread
})
}

async componentDidMount () {
try {
const id = GlobalState.thread.tid
const [{ data }, { data: [ { content_rendered } ] } ] = await Promise.all([
Taro.request({
url: api.getReplies({
'topic_id': id
})
}),
Taro.request({
url: api.getTopics({
id
})
})
])
this.setState({
loading: false,
replies: data,
content: prettyHTML(content_rendered)
})
} catch (error) {
Taro.showToast({
title: '载入远程数据错误'
})
}
}

render () {
const { loading, replies, thread, content } = this.state

const replieEl = replies.map((reply, index) => {
const time = timeagoInst.format(reply.last_modified * 1000, 'zh')
return (
<View className='reply' key={reply.id}>
<Image src={reply.member.avatar_large} className='avatar' />
<View className='main'>
<View className='author'>
{reply.member.username}
</View>
<View className='time'>
{time}
</View>
<RichText nodes={reply.content} className='content' />
<View className='floor'>
{index + 1} 楼
</View>
</View>
</View>
)
})

const contentEl = loading
? <Loading />
: (
<View>
<View className='main-content'>
<RichText nodes={content} />
</View>
<View className='replies'>
{replieEl}
</View>
</View>
)

return (
<View className='detail'>
<Thread
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
not_navi={true}
/>
{contentEl}
</View>
)
}
}

export default ThreadDetail


// src/pages/thread_detail/thread_detail.vue



<template>
<view class="detail">
<thread
:node="topic.node"
:title="topic.title"
:last_modified="topic.last_modified"
:replies="topic.replies"
:tid="topic.id"
:member="topic.member"
:not_navi="true"
/>
<loading v-if="loading" />
<view v-else>
<view class="main-content">
<rich-text :nodes="content | html" />
</view>
<view class="replies">
<view v-for="(reply, index) in replies" class="reply" :key="reply.id">
<image :src="reply.member.avatar_large" class="avatar" />
<view class="main">
<view class="author"> {{reply.member.username}} </view>
<view class="time"> {{reply.last_modified | time}} </view>
<rich-text :nodes="reply.content_rendered | html" class="content" />
<view class="floor"> {{index + 1}} 楼 </view>
</view>
</view>
</view>
</view>
</view>
</template>

<script>
import Vue from 'vue'
import Taro from '@tarojs/taro'
import api from '../../utils/api'
import { timeagoInst, GlobalState, IThreadProps, prettyHTML } from '../../utils'
import Thread from '../../components/thread.vue'
import Loading from '../../components/loading.vue'
import './index.css'
export default {
components: {
loading: Loading,
thread: Thread,
},
data() {
return {
topic: GlobalState.thread,
loading: true,
replies: [],
content: '',
}
},
async created() {
try {
const id = GlobalState.thread.tid
const [
{ data },
{
data: [{ content_rendered }],
},
] = await Promise.all([
Taro.request({
url: api.getReplies({
topic_id: id,
}),
}),
Taro.request({
url: api.getTopics({
id,
}),
}),
])
this.loading = false
this.replies = data
this.content = content_rendered
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
},
filters: {
time(val) {
return timeagoInst.format(val * 1000)
},
html(val) {
return prettyHTML(val)
},
},
}
</script>

到目前为止,我们已经实现了这个应用的所有逻辑,除去「节点列表」页面(在进阶指南我们会讨论这个页面组件)之外,剩下的页面都可以通过我们已经讲解过的组件或页面快速抽象完成。按照我们的计划,这个应用会有五个页面,分别是:

  1. 首页,展示最新帖子(已完成)
  2. 节点列表
  3. 热门帖子(可通过组件复用)
  4. 节点帖子 (可通过组件复用)
  5. 帖子详情 (已完成)

其中前三个页面我们可以把它们规划在 tabBar 里,tabBar 是 Taro 内置的导航栏,可以在 app.config.js 配置,配置完成之后处于的 tabBar 位置的页面会显示一个导航栏。最终我们的 app.config.js 会是这样:

// app.config.js


export default {
pages: [
'pages/index/index',
'pages/nodes/nodes',
'pages/hot/hot',
'pages/node_detail/node_detail',
'pages/thread_detail/thread_detail',
],
tabBar: {
list: [
{
iconPath: 'resource/latest.png',
selectedIconPath: 'resource/lastest_on.png',
pagePath: 'pages/index/index',
text: '最新',
},
{
iconPath: 'resource/hotest.png',
selectedIconPath: 'resource/hotest_on.png',
pagePath: 'pages/hot/hot',
text: '热门',
},
{
iconPath: 'resource/node.png',
selectedIconPath: 'resource/node_on.png',
pagePath: 'pages/nodes/nodes',
text: '节点',
},
],
color: '#000',
selectedColor: '#56abe4',
backgroundColor: '#fff',
borderStyle: 'white',
},
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'V2EX',
navigationBarTextStyle: 'black',
},
}
Taro学习笔记前端阅读需 10 分钟