taro+ts多端实践

4,229 阅读2分钟

Taro 是什么

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

现如今市面上端的形态多种多样,Web、App 端(React Native)、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。

使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、App 端等)运行的代码。同时 Taro 还提供开箱即用的语法检测和自动补全等功能,有效地提升了开发体验和开发效率。

Taro 能提供什么

Write once, run anywhere

写一套代码输出多端可运行的代码,节省了大量的学习成本及工作量。

taro遵循 React 语法标准,结合编译原理的思想,对代码文件进行一系列转换操作,最终获得可以在小程序运行的代码。而 React 最开始就是为了解决 Web 开发而生的,所以对代码稍加改动,也可以直接生成在 Web 端运行的代码,而同属 React 语法体系下的 React Native,也能够很便捷地提供支持。同理其他平台,如快应用、百度小程序等,将源码进行编译转换操作,也能获得该平台下的对应语法代码。

多端开发实践

CLI工具安装

# 使用 npm 安装 CLI
$ npm install -g @tarojs/cli
# OR 使用 yarn 安装 CLI
$ yarn global add @tarojs/cli
# OR 安装了 cnpm,使用 cnpm 安装 CLI
$ cnpm install -g @tarojs/cli

项目初始化

taro init myApp

运行

需确保Taro CLI的版本与你的项目的依赖版本一致,否则会出现编译错误或者运行时错误。除h5以外,其它各端均需下载相应的开发者工具

1、微信小程序
# yarn
$ yarn dev:weapp
$ yarn build:weapp
# npm script
$ npm run dev:weapp
$ npm run build:weapp

2、H5
# yarn
$ yarn dev:h5
$ yarn build:h5
# npm script
$ npm run dev:h5
$ npm run build:h5

其它端运行可见官网

项目配置文件config

dev.js

module.exports = {
    env: {
        NODE_ENV: '"development"'
    },
    defineConstants: {},
    mini: {},
    h5: {
        devServer: {
            proxy: {
                "/": {
                    target: "proxy address",
                    changeOrigin: true
                }
            }
        }
    }
};

index.js

const path = require('path')

const config = {
    projectName: "taro-template",
    date: "2020-1-26",
    designWidth: 750,
    deviceRatio: {
        "640": 2.34 / 2,
        "750": 1,
        "828": 1.81 / 2
    },
    sourceRoot: "src",
    outputRoot: `dist/taro-template-${process.env.TARO_ENV}`,
    alias: {
        "@/service": path.resolve(__dirname, "..", "src/service"),
        "@/utils": path.resolve(__dirname, "..", "src/utils")
    },
    copy: {
        patterns: [
            { from: "src/static/", to: `dist/taro-template-${process.env.TARO_ENV}` } // 指定需要 copy 的文件
        ]
    },
    babel: {
        sourceMap: true,
        presets: [
            [
                "env",
                {
                    modules: false
                }
            ]
        ],
        plugins: [
            "transform-decorators-legacy",
            "transform-class-properties",
            "transform-object-rest-spread",
            [
                "transform-runtime",
                {
                    helpers: false,
                    polyfill: false,
                    regenerator: true,
                    moduleName: "babel-runtime"
                }
            ]
        ]
    },
    defineConstants: {},
    mini: {
        postcss: {
            autoprefixer: {
                enable: true,
                config: {
                    browsers: ["last 3 versions", "Android >= 4.1", "ios >= 8"]
                }
            },
            pxtransform: {
                enable: true,
                config: {}
            },
            url: {
                enable: true,
                config: {
                    limit: 10240 // 设定转换尺寸上限
                }
            },
            cssModules: {
                enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
                config: {
                    namingPattern: "module", // 转换模式,取值为 global/module
                    generateScopedName: "[name]__[local]___[hash:base64:5]"
                }
            }
        }
    },
    h5: {
        publicPath: "/",
        staticDirectory: "static",
        output: {
            filename: "js/[name].[hash:8].js",
            chunkFilename: "js/[name].[chunkhash:8].js"
        },
        imageUrlLoaderOption: {
            limit: 5000,
            name: 'static/images/[name].[hash].[ext]'
         },
        miniCssExtractPluginOption: {
            filename: "css/[name].[hash:8].css",
            chunkFilename: "css/[name].[chunkhash:8].css"
        },
        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'));
};

prod.js

module.exports = {
    env: {
        NODE_ENV: '"production"'
    },
    defineConstants: {},
    mini: {},
    h5: {
        /**
         * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。
         * 参考代码如下:
         * webpackChain (chain) {
         *   chain.plugin('analyzer')
         *     .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
         * }
         */
    }
};

多端实践

不同端的需求多少会有差异性,会存在页面样式,交互形式,页面逻辑的区别,这个时候就需要根据编译的环境来执行不同的代码。

内置环境变量

process.env.TARO_ENV

用来判断当前编译类型,目前有weapp / swan / alipay / h5 / rn / tt / qq / quickapp 八个取值,可以通过这个变量,来编写不同环境下不同的代码。

  • 不同端引用不同资源
if(process.env.TARO_ENV === 'weapp'){
    require('path/to/weapp/name')
}else if(process.env.TARO_ENV === 'h5'){
    require('path/to/h5/name')
}
  • JSX 中使用,不同端加载不同组件
render(){
    return (
        <View>
            {process.env.TARO_ENV === 'weapp' && <ScrollViewWeapp />}
            {process.env.TARO_ENV === 'h5' && <ScrollViewH5 />}
        </View>
    )
}

统一接口的多端文件(1.2.17版本开始支持)

内置环境变量虽然可以解决大部分跨端的问题,但是代码中会充斥着逻辑判断的代码,影响代码的可维护性,而且也让代码变得愈发丑陋,为了解决这种问题,自 1.2.17 开始,Taro 提供了另外一种跨端开发的方式作为补充。

开发者可以通过使用统一接口的多端文件,来解决跨端差异的功能。针对这一项功能,如果多个端之间都有差异,那么开发者可以通过将文件修改成 原文件名+端类型 的命名方式,不同端的文件代码对外保持统一接口,而引用的时候仍然是import 原文件名的文件,Taro在编译时,会根据需要编译平台类型,将加载的文件变更为带有对应端类型文件名的文件,从而达到不同的端加载对应文件的目的。

端类型对应着process.env.TARO_ENV的值

通常有以下两种使用场景

1、 多端组件

假如有一个test组件存在微信小程序,h5两个个不同版本,那么就可以像如下组织代码。

test.js 文件,这是Test组件默认的形式,编译到微信小程序,h5l两端之外的端使用的版本。
test.h5.js 文件,这是Test组件的H5版本。
test.weapp.js 文件,这是Test组件的微信小程序版本。

三个文件,对外暴露的是统一的接口,它们接受一致的参数,只有内部有针对各自平台的代码的实现。使用的方式仍和之前保持一致,import的是不带端类型的文件名,在编译的时候会自动识别并添加端类型后缀

import Test from '../../components/test'

<Text argA={1} argA={2} />

2、多端脚本

与多端组件类似,假如有需要针对不同的端写不同的脚本逻辑代码,我们也可以进行类似的相应处理,遵守的唯一原则就算多端文件对外的接口保持一致。

例如微信小程序上使用Taro.setNavigationBarTitle来设置页面标题,H5使用document.title,那么可以封装一个setTitle方法来抹平两个平台的差异。

增加set_title.h5.js,代码如下

export default function  setTitle(title){
    document.title = title
}

增加set_title.weapp.js,代码如下

import Taro from '@tarojs/taro'
export default function setTitle(title){
    Taro.setNavigationBarTitle({
        title
    })
}

调用的时候,如下使用

import setTitle from '../utils/set_title'
setTitle('页面标题')

app.js中使用不同的pages(1.3.11版本开始支持)

根据不同环境返回不同的pages,可以这么写

config:Config = {
    "pages":preval`
        module.exports = (function() {
            if(process.env.TARO_ENV === 'weapp') {
                return [
                    '/pages/index/index'
                ]
            }
            if(process.env.TARO_ENV === 'swan'){
                return [
                    '/pages/indexswan/indexswan'
                ]
            }
        })()
    `
}

样式的条件编译(1.3+版本支持)

1、样式文件条件编译

假设目录中同时存在以下文件:

-index.scss
-index.rn.scss

当JS文件中引用样式文件:import './index.scss'时,RN平台会找到并引入index.rn.scss,其他平台会引入:index.scss,方便大家书写跨端样式,更好的兼容RN(其它端使用无效)

2、样式文件的条件编译

为了方便大家书写样式跨端的样式代码,添加了样式条件编译的特性

指定平台保留

/*  #ifdef  %PLATFORM%  */
样式代码
/*  #endif  */

指定平台剔除

/*  #ifndef  %PLATFORM%  */
样式代码
/*  #endif  */

多个平台之间可以使用空格隔开

多端同步调试(1.3.5版本开始支持)

从1.3.5版本开始,可以在dist目录下创建一个与编译的目标平台名同名的目录,并将结果放在这个目录下,例如编译到微信小程序,最终结果是在dist/weapp目录下,这样做的好处是,各个平台使用独立的目录互不影响,从而达到多端同步调试的目的,在config/index.js配置如下:

outputRoot:`dist/${process.env.TARO_ENV}`

参考地址:
1、官网 taro.aotu.io/
2、aotu.io/notes/2018/…