Taro实践 - 快速开发【知乎】多端应用

简介:

Taro 是由凹凸实验室打造的一套遵循 React 语法规范的多端统一开发框架。

使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、App 端等)运行的代码。目前 Taro 支持编译出支持微信小程序、H5 运行的代码,RN、快应用及支付宝小程序的支持还在开发中。

具体介绍请看文章 《多端统一开发框架 - Taro 》,以及GitHub仓库 https://github.com/NervJS/taro

2. 前言

为了学习 Taro,本人在 github 找了知乎的小程序demo,本文通过修改该代码,实现了 Taro 版的知乎H5、小程序 demo ,对 Taro 有兴趣的同学可以 star 或 fork 下来学习,GitHub地址

3. 安装

安装 Taro 开发工具 @tarojs/cli

使用 npm 或者 yarn 全局安装


npm install -g @tarojs/cli
// 或
yarn global add @tarojs/cli

下载代码

git clone https://github.com/jimczj/taro_zhihu
# 安装依赖
cd taro_zhihu
npm i

4. 运行代码

文件目录如下:

├── dist                   编译结果目录
├── config                 配置目录
|   ├── dev.js             开发时配置
|   ├── index.js           默认配置
|   └── prod.js            打包时配置
├── src                    源码目录
|   ├── pages              页面文件目录
|   |   ├── index          index页面目录
|   |   |   ├── index.js   index页面逻辑
|   |   |   └── index.css  index页面样式
|   ├── app.css            项目总通用样式
|   └── app.js             项目入口文件
└── package.json

进入项目目录开始开发,可以选择小程序预览模式,或者h5预览模式,若使用微信小程序预览模式,则需要自行下载并打开微信开发者工具,选择预览项目根目录。

微信小程序编译预览模式:


# npm script
npm run dev:weapp
# 或 仅限全局安装
taro build --type weapp --watch

H5编译预览模式:

# npm script
npm run dev:h5
# 或 仅限全局安装
taro build --type h5 --watch

5. 开发前注意

若使用 微信小程序预览模式 ,则需下载并使用 微信开发者工具 添加项目进行预览,此时需要注意微信开发者工具的项目设置

  • 需要设置关闭ES6转ES5功能,开启可能报错
  • 需要设置关闭上传代码时样式自动补全,开启可能报错
  • 需要设置关闭代码压缩上传,开启可能报错
88e46b2409313a4ac3ff95825551ef16615c2cf7

6. 功能实现详解

6.1 小程序全局配置

app.json文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。从原来的 app.json 转化为 Taro 项目的 app.js 几乎没有什么成本,配置基本一致。只是有一点需要注意,静态图片资源文件夹要放在src目录下面,这样代码在编译打包的时候,才会把图片资源给复制打包过去。我一开始将静态图片资源文件夹跟src同层级,然后各种找不到图片资源,浪费了许多时间。好了,话不多说,看看下面代码对比,你就清楚这迁移无比轻松,转写成 React 写法,看起来也顺眼多了。

原微信小程序 app.json (有删减)代码如下:


{
  'pages':[
    'pages/index/index',
    'pages/discovery/discovery',
    'pages/more/more',
    'pages/answer/answer',
    'pages/question/question'
  ],
  'window':{
    'backgroundTextStyle':'light',
    'navigationBarBackgroundColor': '#0068C4',
    'navigationBarTitleText': '知乎',
    'navigationBarTextStyle':'white',
    'enablePullDownRefresh':true
  },
  'tabBar': {
    'color': '#626567',
    'selectedColor': '#2A8CE5',
    'backgroundColor': '#FBFBFB',
    'borderStyle': 'white',
    'list': [{
      'pagePath': 'pages/index/index',
      'text': '首页',
      'iconPath': 'images/index.png',
      'selectedIconPath': 'images/index_focus.png'
    }, {
      'pagePath': 'pages/discovery/discovery',
      'text': '发现',
      'iconPath': 'images/discovery.png',
      'selectedIconPath': 'images/discovery_focus.png'
    },  {
      'pagePath': 'pages/more/more',
      'text': '我的',
      'iconPath': 'images/burger.png',
      'selectedIconPath': 'images/burger_focus.png'
    }]
  }
}

转写成Taro 代码如下:

import Taro, { Component } from '@tarojs/taro'
import Index from './pages/index'

import './app.scss'

class App extends Component {
  config = {
    pages: [
      'pages/index/index',
      'pages/discovery/discovery',
      'pages/more/more',
      'pages/answer/answer',
      'pages/question/question'
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#0068C4',
      navigationBarTitleText: 'Taro知乎',
      navigationBarTextStyle: 'white',
      enablePullDownRefresh: true
    },
    tabBar: {
      color: '#626567',
      selectedColor: '#2A8CE5',
      backgroundColor: '#FBFBFB',
      borderStyle: 'white',
      list: [{
        pagePath: 'pages/index/index',
        text: '首页',
        iconPath: './asset/images/index.png',
        selectedIconPath: './asset/images/index_focus.png'
      },{
        pagePath: 'pages/discovery/discovery',
        text: '发现',
        iconPath: './asset/images/discovery.png',
        selectedIconPath: './asset/images/discovery_focus.png'
      }, 
      {
        pagePath: 'pages/more/more',
        text: '我的',
        iconPath: './asset/images/burger.png',
        selectedIconPath: './asset/images/burger_focus.png'
      }]
    }
  }
  render () {
    return (
      <Index />
    )
  }
}

Taro.render(<App />, document.getElementById('app'))


6.2 首页

页面效果如下:

99f4799970b38c76f13548a4c24bdc26262477b7

功能描述:上滑刷新,下拉加载更多,数据请求,点击跳转

刷新及继续加载的动作, 依靠的是ScrollView组件,并在组件上绑定 onScrolltoupperonScrolltolower 来绑定滚动到顶部及底部所触发的事件, 同时 upperThresholdlowerThreshold 能够调整触发时距边界的距离。

数据请求 api 使用 Taro.request, 跟wx.request 使用方法基本一致,不同的是 Taro.request 天然支持 promise 化,mock 数据使用 easy-mock 来提供 。

点击跳转使用 Taro.navigateTo,跟 wx.navigateTo 也基本一致,在写跳转的时候,一开始想用匿名函数的形式写,发现 Taro 目前还不支持,据说就要支持了,Taro 的迭代速度还是很快的。

将首页进行页面重构的时候,遇到最费时间的问题,应该是wxss转化为scssTaro 的组件转化为微信小程序和 web 后,标签是不一样的,比如Image组件会变成imageimg标签,然而原来的wxss里面使用了标签名来命名 css,这样一来就造成了微信小程序跟 web 样式表现不一致。所以不建议使用标签名命名 css,建议直接用 class。


export default class Index extends Component {
  config = {
    navigationBarTitleText: '首页'
  }
  constructor() {
    super(...arguments)
    this.state = {
      loading:true,
      list:[]
    }
  }
  componentDidMount () { 
    // 获取远程数据
    this.updateList()
  }
  updateList() {
    Taro.showLoading({title: '加载中'})
    Taro.request({
      url: 'https://easy-mock.com/mock/5b21d97f6b88957fa8a502f2/example/feed'
    }).then(res => {
      Taro.hideLoading()
      if (res.data.success) {
        this.setState({
          loading:false,
          list:res.data.data
        })
      }
    })
  }
  appendNextPageList() {
    Taro.showLoading({title: '加载中'})
    Taro.request({
      url: 'https://easy-mock.com/mock/5b21d97f6b88957fa8a502f2/example/feed'
    }).then(res => {
      Taro.hideLoading()
      if (res.data.success) {
        this.setState({
          list: this.state.list.concat(res.data.data)
        })
      }
    })
  }
  render () {
    return (<ScrollView className='container'
        scrollY
        scrollWithAnimation
        scrollTop='0'
        lowerThreshold='10'
        upperThreshold='10'
        onScrolltoupper={this.updateList}
        onScrolltolower={this.appendNextPageList}
        >
        <View className='search flex-wrp'>
          <View className='search-left flex-item'>
              <View className='flex-wrp'>
                <View className='flex1'><Image src={searchPng}></Image></View>
                <View className='flex6'><Input type='text' placeholder='搜索话题, 问题或人' placeholderClass='search-placeholder'/></View>
              </View>
          </View>
          <View className='search-right flex-item'>
              <Image src={lightingPng}></Image>
          </View>
        </View>
        {
          this.state.loading 
          ? <View className='txcenter'><Text>加载中</Text></View>
          : this.state.list.map((item,index)=>{
          return <Feed key={item} />})
        }
      </ScrollView>
    )
  }
}

6.3 Taro 组件

其实我一直搞不懂微信小程序的 Component 组件跟Page 页面的生命周期函数要搞的不一样,页面的生命周期方法有 onLoad、onReady、onUnload 等,而到了组件中则是 created、attached 、ready 等,相比之下 Taro 就比较统一了,不管是页面还是组件,写法都跟 React 的生命周期一致,统一的api开发起来也顺畅了许多。

然而 Taro 组件目前还是有很多局限,比如,不支持直接渲染 children, 即不支持 this.props.childrenprops 不能传递jsx

在抽象组件的时候,主要有以下注意点

  • 在写法上,Taro 组件首字母要大写并采用驼峰命名法,比如在 wxml里面的标签是view、scroll-view、image,在 Taro 要写成View、ScrollView、ImageTaro 的事件绑定事件绑定都以 on 开头并采用驼峰命名法

// 小程序代码
<scroll-view scroll-y='true' class='container' bindscrolltoupper='upper' upper-threshold='10' lower-threshold='5' bindscrolltolower='lower'>
</scroll-view>

// Taro 代码
<ScrollView className='container'
        scrollY
        scrollWithAnimation
        scrollTop='0'
        lowerThreshold='10'
        upperThreshold='10'
        onScrolltoupper={this.upper.bind(this)}
        onScrolltolower={this.lower.bind(this)}
        >
</ScrollView>

  • 小程序引用本地静态资源直接在 src 写上相对路径,Taro 引用本地静态资源需要先 import 进来再使用,为了让 h5 部署的时候图片路径不出错,最好把图片放在服务器上,然后直接写 http 路径

  • // 小程序 引用本地静态资源
    <image src='../../images/search.png'></image>
    
    // Taro 引用本地静态资源
    import searchPng from '../../asset/images/search.png'
    // ...此处省略无数代码
    <Image src={searchPng}></Image>
    
    // 最好把图片放在服务器上,然后写http 路径
    <Image src='https://image.ibb.co/kUissy/search.png'></Image> 
    


  • 遍历列表的区别,小程序使用模版语言,而 Taro 使用 jsx

// 小程序
<block wx:for='{{feed}}' wx:for-index='idx' wx:for-item='item' data-idx='{{idx}}'>
    <view class='feed-item'>
        ...
    </view>
</block>

// Taro 代码
{
    this.state.list.map((item,index)=>{
       return <Feed {...item} key={index} />
    })
}


答案组件在抽象过程中,想直接写成纯函数形式

// 暂不支持这种写法
const Text = ({ answer }) => 
  <p>{answer}</p>

发现目前还不支持,于是老老实实写成class的形式了:

export default class Feed extends Component {
  navigateTo(url) {
    Taro.navigateTo({url:url})
  }
  render() {
    return (
      <View className='feed-item'>
        <View className='feed-source'>
          <View className='avatar flex1'>
              <Image src={this.props.feed_source_img}></Image>
          </View>
          <View className='flex8'>
            <Text className='feed-source-txt'>{this.props.feed_source_name}{this.props.feed_source_txt}</Text>
          </View>
          <View className='flex1'>
            <Image className='item-more' mode='aspectFit' src={more}></Image>
          </View>
        </View>
        <View className='feed-content'>
            <View className='question' onClick={this.navigateTo.bind(this,'/pages/question/question')}>
                <View className='question-link'>
                    <Text>{this.props.question}</Text>
                </View>
            </View>
            <View className='answer-body'>
                <View>
                    <Text className='answer-txt' onClick={this.navigateTo.bind(this,'/pages/answer/answer')} >{this.props.answer_ctnt}</Text>
                </View>
                <View className='answer-actions'>
                    <View className='like dot'>
                        <View>{this.props.good_num} 赞同 </View>
                    </View>
                    <View className='comments dot'>
                        <View>{this.props.comment_num} 评论 </View>
                    </View>
                    <View className='follow-it'>
                        <View>关注问题</View>
                    </View>
                </View>
            </View>
        </View>
      </View>
    )
  }
}

在使用的组件的时候,想使用 {...item} 的方式来解构赋值,发现也暂时不支持,内心有点奔溃。
108f7dbe60b7991a8e3c9755813ec73ac3085cbb
不过所幸的是,Taro的错误提醒很人性化,没有让我在这里浪费很多时间调试,于是我就老老实实一个一个赋值啦。

 
 

this.state.list.map((item,index) => {
    return <Feed 
        feed_source_img={item.feed_source_img}
        feed_source_txt={item.feed_source_txt}
        question={item.question}
        answer_ctnt={item.answer_ctnt} 
        good_num={item.good_num} 
        comment_num={item.comment_num} 
        key={index} />
})

6.4 “发现页面”的 tab 切换功能

367f94446adf119e9f96c1a46488c1440f0c5faf

tab切换原理:  在组件上绑定  onClick  事件 改变  this.state.currentNavtab  值,再通过判断来实现  tab  切换,函数参数传递方式为  this.switchTab.bind(this,index) ,具体代码如下:

export default class Discovery extends Component {
    constructor() {
    super(...arguments)
    this.state = {
        currentNavtab: 0,
        navTab: ['推荐', '圆桌', '热门', '收藏'],
    }
  }
  switchTab(index,e) {
    this.setState({
      currentNavtab: index
    });
  }
  render () {
    return (
      <View>
        <View className='top-tab flex-wrp flex-tab' >
        {
          this.state.navTab.map((item,index) => {
            return (<View className={this.state.currentNavtab === index ? 'toptab flex-item active' : 'toptab flex-item' } key={index} onClick={this.switchTab.bind(this,index)}>
              {item}
            </View>)
          })
        }
        </View>
        <ScrollView scroll-y className='container discovery withtab'>
          <View className='ctnt0' hidden={this.state.currentNavtab==0 ? false : true}>
              ...
          </View>
            <View className='txcenter' hidden={this.state.currentNavtab==1 ? false : true}>
              <Text>圆桌</Text>
            </View>
            <View className='txcenter' hidden={this.state.currentNavtab==2 ? false : true}>
              <Text>热门</Text>
            </View>
            <View className='txcenter' hidden={this.state.currentNavtab==3 ? false : true}>
              <Text>收藏</Text>
            </View>
        </ScrollView>
      </View> 
    )
  }
}


6.5 轮播功能

轮播页使用了 Swiper 组件,参数跟小程序都是一一对应,具体可以查看详细文档,在重构过程也主要是把 wxml 换成 jsx 的形式。

 

<Swiper className='activity' indicatorDots='true'
    autoplay='true' interval='5000' duration='500'>
    {this.state.imgUrls.map((item,index) => {
        return (<SwiperItem key={index}>
            <Image src={item} className='slide-image' width='355' height='375' />
        </SwiperItem>)
    })}
</Swiper>

7. 使用中发现的问题总结

  • Taro 的组件转化为微信小程序跟web后,标签是不一样的,比如Image组件会变成imageimg标签,所以不建议使用标签名命名css,建议直接用 class
  • 目前的事件绑定不支持匿名函数


// 不支持
<View onClick={()=> this.navigateTo('/pages/answer/answer')} >  </View>

  • Taro 组件在 web 端有许多属性和事件暂不支持
  • 运行环境不同,某些 api 需要根据环境不同处理,比如 wx.getUserInfo 在 web 端是不存在的,此时我们需要判断环境来执行代码
 

if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP) {
    // 小程序环境
} else if (Taro.getEnv() === Taro.ENV_TYPE.WEB ) {
    // WEB(H5)环境
}

  • Taro 目前不支持 SVG(毕竟小程序不支持) 目前的第三方库还比较少(毕竟 Taro 刚出来不久,希望接下来社区能出来各种 ui 库)
  • Taro 组件暂时不支持纯函数,不支持解构赋值 {...item}

8. 总结

将该项目迁移到 Taro 成本并不高,绝大多数工作是做语法的变换。Taro采用 React 的写法写微信小程序,总体体验是非常不错的,有 React 开发经验的小伙伴相信能很快上手。同一份代码就能同时支持web端跟小程序端,确实很让人惊艳,虽然目前 web 端的支持还有待完善,但方向是没有错的,接下来只是时间问题。



原文发布时间为:2018年06月27日
原文作者:凹凸实验室
本文来源: 掘金    如需转载请联系原作者

相关文章
|
5月前
|
开发框架 缓存 API
【Uniapp 专栏】通过 Uniapp 构建移动办公应用案例分享
【5月更文挑战第12天】使用Uniapp开发的移动办公应用案例展示了其在提升工作效率和协作上的强大能力。应用涵盖日程管理、任务分配、文件共享、即时通讯等功能,适应跨平台需求,节省开发成本。借助Uniapp的组件和API,打造用户友好的界面,同时确保数据安全和稳定性。优化的界面设计及移动设备适应性,即使在网络不稳定时也能保证基本功能使用。此案例证明Uniapp是构建高效移动办公应用的理想选择,为企业数字化转型赋能。
125 5
|
5月前
|
缓存 JavaScript 测试技术
【Uniapp 专栏】在 Uniapp 中实现复杂交互的实战技巧
【5月更文挑战第12天】在Uniapp开发复杂交互时,需掌握组件化、事件机制、状态管理(如Vuex)及布局设计。利用动画增强用户体验,注意性能优化,有效处理与后端数据交互,并通过全面测试确保正确性。持续学习和借鉴社区资源,提升在复杂交互方面的技能。这些实战技巧有助于打造出色Uniapp应用。
96 5
|
5月前
|
编解码 缓存 UED
【Uniapp 专栏】Uniapp 开发实战:打造高效页面布局技巧
【5月更文挑战第12天】在 Uniapp 开发中,高效页面布局关乎用户体验和应用性能。关键技巧包括:规划清晰的页面结构,利用 Flex 布局组件,精确控制元素尺寸和位置,实现响应式设计,保持布局简洁,优化加载性能,恰当运用色彩和字体,添加交互性动画,以及组织良好代码结构。通过不断学习和实践,开发者能创建出美观且高性能的页面,提升应用的整体质量。
328 5
|
5月前
|
前端开发 定位技术 API
【Flutter前端技术开发专栏】Flutter中的第三方服务集成(如支付、地图等)
【4月更文挑战第30天】本文介绍了在Flutter中集成第三方服务,如支付和地图,以增强应用功能和用户体验。开发者可通过官方或社区插件集成服务,关注服务选择、API调用、错误处理和用户体验。支付集成涉及选择服务、获取API密钥、引入插件、调用API及处理结果。地图集成则需选择地图服务、获取API密钥、初始化地图并添加交互功能。集成时注意选择稳定插件、阅读文档、处理异常、优化性能和遵循安全规范。随着Flutter生态发展,更多第三方服务将可供选择。
71 0
【Flutter前端技术开发专栏】Flutter中的第三方服务集成(如支付、地图等)
|
移动开发 小程序 前端开发
【前端】taro 跨端应用环境搭建
【前端】taro 跨端应用环境搭建
12056 1
|
前端开发
前端学习笔记202304学习笔记第九天-脚手架开发痛点1
前端学习笔记202304学习笔记第九天-脚手架开发痛点1
65 0
|
数据采集 JSON 小程序
《智能前端技术与实践》——第 2 章 前端开发基础 ——2.7 微信小程序开发——2.7.1 微信小程序框架结构分析
《智能前端技术与实践》——第 2 章 前端开发基础 ——2.7 微信小程序开发——2.7.1 微信小程序框架结构分析
152 0
|
移动开发 资源调度 小程序
「Taro开发」前端多端开发,Taro观赏指南
最近接到多端开发,因为老项目使用的React,考虑到迁移成本,选择了Taro,迁移成本相对较低,且上手较快。
783 1
|
存储 小程序 前端开发
【零基础微信小程序入门开发五】基础能力(一)
在学习小程序中,我们必须要接触小程序的一些基础能力,从而实现一些业务的开发,例如上传图片下载文件,或者是通过接口返回的数据进行渲染来完成一些事件的触发,基础能力包括:网络请求、文件读写、存储,以及渲染画布等,这里呢我们着重讲几个,其他的例如分包下载、按需注入等因为不满足这次的零基础开发,对于还没.........
123 0
【零基础微信小程序入门开发五】基础能力(一)
|
存储 缓存 小程序
【零基础微信小程序入门开发六】基础能力(二)
同【服务器域名】配置一样在小程序界面配置即可,不过他是作为嵌入的、承载网页的容器。会自动铺满整个小程序页面,小游戏和个人类型的小程序暂不支持使用,我们可以在调试机上看一下效果,它的用法很简单 效果 我们滑动到最底部,可以看到他是铺满全屏的 可...
116 0
【零基础微信小程序入门开发六】基础能力(二)
下一篇
无影云桌面