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日
原文作者:凹凸实验室
本文来源: 掘金    如需转载请联系原作者

相关文章
|
4月前
|
移动开发 小程序 JavaScript
开源的微信小程序框架
【8月更文挑战第22天】开源的微信小程序框架
216 65
|
2月前
|
小程序 数据可视化 前端开发
个人开发者可视化开发自己的微信小程序
个人开发者可视化开发自己的微信小程序
71 5
|
4月前
|
小程序 前端开发 API
微信小程序全栈开发中的多端适配与响应式布局是一种高效的开发模式。
探讨小程序全栈开发中的多端适配与响应式布局,旨在实现统一的用户体验。多端适配包括平台和设备适配,确保小程序能在不同环境稳定运行。响应式布局利用媒体查询和弹性布局技术,使界面适应各种屏幕尺寸。实践中需考虑兼容性、性能优化及用户体验,借助跨平台框架如Taro或uni-app可简化开发流程,提升效率。
69 1
|
7月前
|
开发框架 缓存 API
【Uniapp 专栏】通过 Uniapp 构建移动办公应用案例分享
【5月更文挑战第12天】使用Uniapp开发的移动办公应用案例展示了其在提升工作效率和协作上的强大能力。应用涵盖日程管理、任务分配、文件共享、即时通讯等功能,适应跨平台需求,节省开发成本。借助Uniapp的组件和API,打造用户友好的界面,同时确保数据安全和稳定性。优化的界面设计及移动设备适应性,即使在网络不稳定时也能保证基本功能使用。此案例证明Uniapp是构建高效移动办公应用的理想选择,为企业数字化转型赋能。
149 5
|
7月前
|
数据安全/隐私保护 Android开发 UED
【Uniapp 专栏】Uniapp 在社交应用开发中的案例研究
【5月更文挑战第12天】本文探讨了一个使用Uniapp开发的社交应用案例,该应用提供用户注册登录、个人资料管理、好友关系、动态发布、消息聊天等功能。Uniapp的跨平台特性和丰富的组件简化了开发过程,确保应用在iOS和Android上的兼容性。特色功能如话题标签、点赞评论和附近的人增加了用户互动。设计上追求简洁美观,同时重视数据安全。此案例展示了Uniapp在社交应用开发的潜力和优势。
116 4
|
7月前
|
前端开发 定位技术 API
【Flutter前端技术开发专栏】Flutter中的第三方服务集成(如支付、地图等)
【4月更文挑战第30天】本文介绍了在Flutter中集成第三方服务,如支付和地图,以增强应用功能和用户体验。开发者可通过官方或社区插件集成服务,关注服务选择、API调用、错误处理和用户体验。支付集成涉及选择服务、获取API密钥、引入插件、调用API及处理结果。地图集成则需选择地图服务、获取API密钥、初始化地图并添加交互功能。集成时注意选择稳定插件、阅读文档、处理异常、优化性能和遵循安全规范。随着Flutter生态发展,更多第三方服务将可供选择。
106 0
【Flutter前端技术开发专栏】Flutter中的第三方服务集成(如支付、地图等)
|
移动开发 小程序 前端开发
【前端】taro 跨端应用环境搭建
【前端】taro 跨端应用环境搭建
12080 1
|
存储 JSON 小程序
微信小程序开发实践
微信小程序开发实践
106 0
|
JSON 小程序 JavaScript
微信小程序开发入门与实战(组件的使用)
微信小程序开发入门与实战(组件的使用)
微信小程序开发入门与实战(组件的使用)
|
移动开发 资源调度 小程序
「Taro开发」前端多端开发,Taro观赏指南
最近接到多端开发,因为老项目使用的React,考虑到迁移成本,选择了Taro,迁移成本相对较低,且上手较快。
817 1