原文链接

个人博客-欢迎访问

效果预览图:

微信小程序的开发目前是很热的一个领域,有很多的开发模式,找到一种属于自己的方法才会使得开发顺心顺利。

此架构是使用 Taro + dva + typescript 构建前端开发

  • 京东凹凸实验室的React框架Taro很成熟,又是大厂在维护更新迭代,不用担心没人维护的问题,他有自己的UI还有物料社区,比起原生小程序方便很多,支持多端,一处代码,多处运行,微信小程序、H5、百度小程序、支付宝小程序、字节跳动小程序、QQ轻应用、快应用、ReactNative;
  • 数据管理是Redux集成的dva框架,是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架;
  • TypeScript就是所谓的JavaScript超集。它不是JavaScript的替代品,也不会为JavaScript代码添加任何新功能。相反,TypeScript允许程序员在其代码中使用面向对象的构造,然后将其转换为JavaScript。它还包括类型安全和编译时类型检查等便利功能。

资料

Taro官网地址:https://taro.aotu.io/

dva官网地址:https://dvajs.com/guide/

开始

前期工作准备

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 Taro_dva_Typescript复制代码

安装配置文件

安装dva

cnpm install --save dva-core dva-loading

  • dva-core:封装了 redux 和 redux-saga的一个插件
  • dva-loading:管理页面的loading状态

安装@tarojs/redux

cnpm install --save redux @tarojs/redux @tarojs/redux-h5 redux-thunk redux-logger

配置项目文件

去除不需要的文件,添加实际需要的一些文件,先删除./ssrc/page下的index文件夹,后期使用命令行生成完整结构的文件夹。

在``/src`目录下根据自己的实际需求进行一下配置:

  • assets: 一些静态资源,比如:image、iconfont
  • config: 项目配置文件
  • components: 项目编写的一些共用组件
  • types: 项目公共的Typescript类型声明
  • models: 项目dva插件model函数的引用或者是一些共用的js文件
  • utils: 项目里封装的一些插件

项目一些具体配置操作

1、在./src/config下创建index.ts,添加项目配置信息

/** * 这里为了方便测试使用 Easy Mock 模拟接口数据* * https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist
*/export const ONLINEHOST = 'https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist';/** * mock 接口* */
export const MOCKHOST = 'https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist';/** * 是否mock
*/export const ISMOCK = true;/*** 这是一个全局的分享信息 不用每一个都去写*/
export const SHAREINFO = {'title': '分享标题','path': '路径','imageUrl': '图片'}复制代码

2、在./src/utils下创建dva.ts,配置dva


import { create } from "dva-core";
import { createLogger } from "redux-logger";
import  createLoading  from "dva-loading";let app
let store
let dispatch
let registeredfunction createApp(opt) {// redux 的日志opt.onAction = [createLogger()]app = create(opt)app.use(createLoading({}))if (!registered) {opt.models.forEach(model => app.model(model));}registered = true;app.start()store = app._store;app.getStore = () => store;app.use({onError(err){console.log(err);}})dispatch = store.dispatch;app.dispatch = dispatch;return app;
}export default{createApp,getDispatch(){return app.dispatch}
}复制代码

3、在./src/utils下创建tips.ts,整合封装微信原生弹窗


import Taro from "@tarojs/taro";
import { node } from "_@types_prop-types@15.7.1@@types/prop-types";/** * 整合封装微信的原生弹窗* 提示、加载、工具类
*/export default class Tips {static isLoading = false;/** * 提示信息*/static toast(title: string, onHide?: () => void) {Taro.showToast({title: title,icon: 'node',mask: true,duration: 1500});// 去除结束回调函数if (onHide) {setTimeout(() => {onHide();}, 500);}}/** * 加载提示弹窗*/static loding(title:'加载中',force = false){if (this.isLoading && !force) {return}this.isLoading = true;if (Taro.showLoading) {Taro.showLoading({title:title,mask:true})}else{Taro.showNavigationBarLoading() //导航条加载动画}}/** * 加载完成*/static loaded(){let duration = 0;if (this.isLoading) {this.isLoading = false;if (Taro.hideLoading) {Taro.hideLoading()} else {Taro.hideNavigationBarLoading(); //导航条加载动画}duration = 500;}// 设定隐藏的动画时长为500ms,防止直接toast时出现问题return new Promise(resolve => setTimeout(resolve,duration))}/** * 弹出提示框*/static success(title,duration = 1500){Taro.showToast({title: title,icon: 'success',duration: duration,mask:true})if (duration > 0) {return new Promise(resolve => setTimeout(resolve,duration))}}}复制代码

4、在./src/config下创建requestConfig.ts,统一配置请求接口

/** * 请求公共参数
*/export const commonParame = {}/** * 请求的映射文件
*/export const requestConfig = {loginUrl:'/api/user/wechat-auth' // 微信的登陆接口
}复制代码

5、在./src/utils下创建common.ts,共用函数


/** * 共用函数
*/export const repeat = (str = '0', times) => (new Array(times + 1)).join(str);
// 时间前面 +0
export const pad = (num, maxLength = 2) => repeat('0', maxLength - num.toString().length) + num;// 全局的公共变量
export let globalData: any = {}// 时间格式装换函数export const formatTime = time => {`${pad(time.getHours())}:${pad(time.getMinutes())}:${pad(time.getSeconds())}.${pad(time.getMilliseconds(), 3)}`
}复制代码

6、在./src/utils下创建logger.ts,封装log函数


/** * 封装logo函数
*/import { formatTime } from './common';const defaults = {level: 'log',logger: console,logErrors: true,colors: {title:'logger',req:'#9e9e9e',res:'#4caf50',error:'#f20404',}
}function printBuffer(logEntry, options){const {logger,colors} = options;let {title,started,req,res} = logEntry;// Messageconst headerCSS = ['color:gray; font-weight:lighter;']const styles = s => `color ${s}; font-weight: bold`;// renderlogger.group(`%c ${title} @${formatTime(started)}`, ...headerCSS);logger.log('%c req', styles(colors.req), req)logger.log('%c res', styles(colors.res), res)logger.groupEnd()}interface LogEntry{started ? : object  // 触发时间
}function createLogger(options: LogEntry = {}){const loggerOptions = Object.assign({}, defaults, options)const logEntry = optionslogEntry.started = new Date();printBuffer(logEntry, Object.assign({}, loggerOptions))
}export {defaults,createLogger,
}复制代码

7、在./src/utils下创建request.ts,封装http请求


import Taro,{ Component } from "@tarojs/taro";
import { ISMOCK,MAINHOST } from "../config";
import { commonParame,requestConfig } from "../config/requestConfig";
import Tips from "./tips";// 封装请求declare type Methohs = "GET" | "OPTIONS" | "HEAD" | "PUT" | "DELETE" | "TRACE" | "CONNECT";
declare type Headers = { [key :string]:string};
declare type Datas = {method : Methohs; [key: string] : any;};
interface Options{url: string;host?: string;method?: Methohs;data?: Datas;header?: Headers;
}export class Request {// 登陆时的promisestatic loginReadyPromise: Promise<any> = Promise.resolve()// 正在登陆static isLoading: boolean = false// 导出的API对象static apiLists: { [key: string]: () => any;} = {}// tokenstatic token: string = ''// 开始处理optionsstatic conbineOptions(opts, data: Datas, method: Methohs): Options {typeof opts ===  'string' && (opts = {url: opts})return {data: { ...commonParame, ...opts.data, ...data },method: opts.method || data.method || method || 'GET',url: `${opts.host || MAINHOST}${opts.url}`}}static getToken(){!this.token && (this.token = Taro.getStorageSync('token'))return this.token}// 登陆static login(){if (!this.isLoading) {this.loginReadyPromise = this.onLogining()}return this.loginReadyPromise}static onLogining(){this.isLoading = true;return new Promise(async (resolve, reject) => {// 获取codeconst { code } = await Taro.login();const { data } = await Taro.request({url: `${MAINHOST}${requestConfig.loginUrl}`,data:{code: code}})if (data.code !== 0 || !data.data || !data.data.token) {reject()return}})}/** * 基于 Taro.request 的 request 请求* * */ static async request(opts: Options) {// Taro.request 请求const res = await Taro.request(opts);// 是否mockif(ISMOCK) return res.data;// 请求失败if (res.data.code === 99999) {await this.login();return this.request(opts)}// 请求成功if (res.data) {return res.data}// 请求错误const edata = { ...res.data, err : (res.data && res.data.msg) || '网络错误 ~'}Tips.toast(edata.err)throw new Error(edata.err)}/** * 创建请求函数*/static creatRequests(opts: Options | string) : () => {} {console.log('opts==>',opts);return async (data={}, method: Methods = "GET") => {const _opts = this.conbineOptions(opts, data, method)const res = await this.request(_opts)return res;}}/** * 抛出API方法*/static getApiList(requestConfig){if (!Object.keys(requestConfig).length) {return {}}Object.keys(requestConfig).forEach((key)=>{this.apiLists[key] = this.creatRequests(requestConfig[key])})return this.apiLists}}const Api = Request.getApiList(requestConfig)
Component.prototype.$api = Api
export default Api as any复制代码

注:

在这里tslint会报这样的错:类型“Component<any, any>”上不存在属性“$api”。,因为没有添加声明,需在./src目录下创建app-shim.d.ts


/** * 添加taro等自定义类型
*/import Taro,{ Component } from '@tarojs/taro'// 在Component上定义自定义方法类型
declare module '@tarojs/taro' {interface Component {$api: any}
}// 声明
declare let require: any;
declare let dispatch: any复制代码

8、在./src/config下创建taroConfig.ts,封装taro小程序的一些方法

import Taro,{ Component } from '@tarojs/taro'
import { SHAREINFO } from '../config/index'/** * 封装taro小程序的一些方法*  - 方法改写*  - utils 挂载
*/// navigateTo 超过8次后,强行进行redirectTo,避免页面卡顿const nav = Taro.navigateToTaro.navigateTo = (data) => {if (Taro.getCurrentPages().length > 8) {return Taro.redirectTo(data)}return nav(data)}// 挂载分享方法 ComponentComponent.prototype.onShareAppMessage = function () {return SHAREINFO
}复制代码

配置文件生成脚本

1、在根目录下创建scripts文件夹,添加./scripts/template.js


/** *  pages 页面快速生成脚本*  *  npm run tem '文件名‘
*/const fs = require('fs')
const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1);if (!dirName) {console.log('文件名不能为空');console.log('用法:npm run tem test');process.exit(0);
}// 页面模板构建const indexTep = `import Taro, { Component, Config } from '@tarojs/taro'import { View } from '@tarojs/components'// import { connect } from '@tarojs/redux'// import Api from '../../utils/request'// import Tips from '../../utils/tips'import { ${capPirName}Props, ${capPirName}State } from './${dirName}.interface'import './${dirName}.scss'// import {  } from '../../components'// @connect(({ ${dirName} }) => ({//     ...${dirName},// }))class ${capPirName} extends Component<${capPirName}Props,${capPirName}State > {config:Config = {navigationBarTitleText: '页面标题'}constructor(props: ${capPirName}Props) {super(props)this.state = {}}componentDidMount() {}render() {return (<View className='fx-${dirName}-wrap'>页面内容</View>)}}export default ${capPirName}
`// scss 文件模板const scssTep = `@import "../../assets/scss/variables";.#{$prefix} {&-${dirName}-wrap {width: 100%;min-height: 100Vh;}}
`// config 接口地址配置模板const configTep =`export default {test:'/wechat/perfect-info',  //XX接口}
`// 接口请求模板const serviceTep =`import Api from '../../utils/request'export const testApi = data => Api.test(data)
`// model 模板const modelTep = `// import Taro from '@tarojs/taro';// import * as ${dirName}Api from './service';export default {namespace: '${dirName}',state: {},effects: {},reducers: {}}`const interfaceTep = `
/*** ${dirName}.state 参数类型** @export* @interface ${capPirName}State*/
export interface ${capPirName}State {}/*** ${dirName}.props 参数类型** @export* @interface ${capPirName}Props*/
export interface ${capPirName}Props {}
`fs.mkdirSync(`./src/pages/${dirName}`); // mkdir $1
process.chdir(`./src/pages/${dirName}`); // cd $1fs.writeFileSync(`${dirName}.tsx`, indexTep); //tsx
fs.writeFileSync(`${dirName}.scss`, scssTep); // scss
fs.writeFileSync('config.ts', configTep); // config
fs.writeFileSync('service.ts', serviceTep); // service
fs.writeFileSync('model.ts', modelTep); // model
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep); // interface
process.exit(0);复制代码

最后

在根目录的package.json的scripts里加上对应的命令


"scripts": {..."tep": "node scripts/template","com": "node scripts/component"
}复制代码

2、自动生成脚本文件夹

cnpm run tep index

page文件夹下生成了一个index的文件夹,里面包含

  • config.ts
  • index.interface.ts
  • index.scss
  • index.tsx
  • model.ts
  • service.ts

配置业务代码

1、先在src目录下创建models文件夹,集合项目里的model关系。


import index from '../pages/index/model';export default[index
]复制代码

项目目前只有index页面,export default这里的数组就只有index,需要注意这里是[]数组。

2、修改非常主要的文件app.tsx


import Taro, { Component, Config } from '@tarojs/taro'
import "@tarojs/async-await";
import { Provider } from "@tarojs/redux";
import dva from './utils/dva';
import './utils/request';
import { globalData } from './utils/common';import models from './models'
import Index from './pages/index'
import './app.scss'// 如果需要在 h5 环境中开启 React Devtools
// 取消以下注释:
// if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5')  {
//   require('nerv-devtools')
// }const dvaApp = dva.createApp({initialState:{},models:  models,
})const store = dvaApp.getStore();class App extends Component {/*** 指定config的类型声明为: Taro.Config** 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型* 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string* 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型*/config: Config = {pages: ['pages/index/index'],window: {backgroundTextStyle: 'light',navigationBarBackgroundColor: '#fff',navigationBarTitleText: 'WeChat',navigationBarTextStyle: 'black'}}/****  1.小程序打开的参数 globalData.extraData.xx*  2.从二维码进入的参数 globalData.extraData.xx*  3.获取小程序的设备信息 globalData.systemInfo*/async componentDidMount () {// 获取参数const referrerInfo = this.$router.params.referrerInfoconst query = this.$router.params.query!globalData.extraData && (globalData.extraData = {})if (referrerInfo && referrerInfo.extraData) {globalData.extraData = referrerInfo.extraData}if (query) {globalData.extraData = {...globalData.extraData,...query}}// 获取设备信息const sys = await Taro.getSystemInfo()sys && (globalData.systemInfo = sys)}componentDidShow () {}componentDidHide () {}componentDidCatchError () {}render () {return (<Provider store={store}><Index /></Provider>)}
}Taro.render(<App />, document.getElementById('app'))复制代码

3、修改接口请求./src/pages/index/config.ts文件

一个获取列表数据接口


export default {getList: '/getlist', //getlist接口
}复制代码

4、修改./src/config/requestConfig.ts文件的映射关系

引入index页面的刚刚创建的config文件


import index from "../pages/index/config"; // index的接口/** * 请求公共参数
*/
export const commonParame = {}/** * 请求的映射文件
*/export const requestConfig = {loginUrl:'/api/user/wechat-auth', // 微信的登陆接口...index
}复制代码

5、修改./src/pages/index/service.ts里的接口请求

还是依据之前的getlist接口


import Api from '../../utils/request'export const getList = (data) => {return Api.getList(data)}复制代码

6、修改./src/pages/index/index.interface.ts里的参数类型

根据项目具体的参数,自行进行配置


/*** index.state 参数类型* @interface IndexState*/
export interface IndexState {}/*** index.props 参数类型** @export* @interface IndexProps*/
export interface IndexProps {dispatch?: any,data?: Array<DataInterface>
}export interface DataInterface {des:string,lunar:string,thumbnail_pic_s:string,title:string,_id:string
}复制代码

7、修改./src/pages/index/model.tseffects函数

在这里创建页面需要请求的接口,链接service里的接口发起数据请求,这里以getList为例。


// import Taro from '@tarojs/taro';
import * as indexApi from './service';export default {namespace: 'index',state: {data:[],v:'1.0',},effects: {*getList({ payload },{select, call, put}){const { error, result} = yield call(indexApi.getList,{...payload})console.log('数据接口返回',result);if (!error) {yield put({type: 'save',payload: {data:result.data},})}}},reducers: {save(state, { payload }) {return { ...state, ...payload };},}}复制代码

8、修改./src/pages/index/index.tsx里页面结构

这里简单的实现列表新闻页面。


import Taro, { Component, Config } from '@tarojs/taro'
import { View, Text} from '@tarojs/components'
import { connect } from '@tarojs/redux'
// import Api from '../../utils/request'
// import Tips from '../../utils/tips'
import { IndexProps, IndexState } from './index.interface'
import './index.scss'
// import {  } from '../../components'@connect(({ index }) => ({...index,
}))class Index extends Component<IndexProps,IndexState > {config:Config = {navigationBarTitleText: 'taro_dva_typescript'}constructor(props: IndexProps) {super(props)this.state = {}}async getList() {await this.props.dispatch({type: 'index/getList',payload: {}})}componentDidMount() {this.getList()}render() {const { data } = this.propsconsole.log('this.props===>>',data);return (<View className='fx-index-wrap'><View className='index-topbar'>New资讯</View><View className='index-data'>{data && data.map((item,index) => {return (<View className='index-list' key={index}><View className='index-title'>{item.title}</View><View className='index-img' style={`background-image: url(${item.thumbnail_pic_s})`}></View></View>)})}</View></View>)}
}export default Index复制代码

9、修改./src/pages/index/index.scss首页的样式

这里的写法是sass的语法糖


@import "../../assets/scss/variables";.#{$prefix} {&-index-wrap {width: 100%;min-height: 100vh;.index {&-topbar {padding: 10rpx 50rpx;text-align: center;font-weight: bold;color: #333;font-size: 30rpx;}// &-data {// }&-title {font-size: 28rpx;color: #666;width: 100%;font-weight: bold;}&-list{border-bottom: 1rpx solid #eee;padding-bottom: 20rpx;margin: 20rpx 24rpx;display: flex;flex-direction: row;justify-content: space-between;align-items: center}&-img {width: 70%;height: 200rpx;background-repeat: no-repeat;background-size: contain;background-position: right center;}}}}复制代码

项目启动

运行小程序编译命令

cnpm run dev:weapp

等待项目编译完成,会在项目根目录下生成一个dist,打开微信小程序开发者根据,导入本地刚刚生成的dist文件,就成功启动了项目。

效果预览图:


如有啥问题欢迎讨论,共同学习。

项目示例Github地址:github.com/Duanruilong…

转载于:https://juejin.im/post/5d3e6046f265da1bd146a0ba

Taro+dva+Typescript 搭建微信小程序架构相关推荐

  1. 使用TypeScript开发微信小程序的方法

    TypeScript是C#之父Anders Hejlsberg的又一力作,很多喜欢c#语法的朋友对typescript都爱不释手,今天小编给大家介绍下TypeScript开发微信小程序的方法,感兴趣的 ...

  2. 微信小程序python flask_Python Flask 搭建微信小程序后台详解

    前言: 近期需要开发一个打分的微信小程序,涉及到与后台服务器的数据交互,因为业务逻辑相对简单,故选择Python的轻量化web框架Flask来搭建后台程序.因为是初次接触小程序,经过一番摸索和尝试,个 ...

  3. 如何用TypeScript开发微信小程序

    微信小程序来了!这个号称干掉传统app的玩意儿虽然目前处于内测阶段,不过目前在应用号的官方文档里已经放出了没有内测号也能使用的模拟器了. 工具和文档可以参考官方文档:https://mp.weixin ...

  4. 阿里云服务搭建微信小程序开发环境

    最近微信小程序异常火爆,很多人在学习,下面带着大家搭建下微信小程序的调试环境(client+server),并调试一套demo源码(JavaScript和node.js基础即可,微信推荐使用的语言,无 ...

  5. 搭建微信小程序HTTPS服务器

    微信小程序是目前热门,学习及应用的人非常多,但很多人不知道小程序接口域名HTTPS怎么做,下面我们将详细介绍搭建微信小程序每一个步骤,希望可以快速的帮助你如何搭建微信小程序与HTTPS服务器. 一.申 ...

  6. 基于阿里云搭建微信小程序服务器(HTTPS)

    https://www.jianshu.com/p/132eed84bc4f 本来是想写基于腾讯云搭建微信小程序服务器的,可惜腾讯云让我有点不好的体验,所以就先放弃使用腾讯云了.所以转而在阿里云上注册 ...

  7. 阿里云服务器上搭建微信小程序服务端环境。

    无论是搭建个人博客空间也好,微信小程序也罢,搭建环境必需的两点:云服务器.域名,下面一步步给搭建演示如果在一台阿里云服务器上搭建微信小程序服务端环境. 1.云服务器准备:可在阿里云购买ECS服务器   ...

  8. 手把手教搭建微信小程序教程

    小白式手把手教搭建微信小程序教程 宝塔软件商店下载如下:php7.3  Nginx 1.20.2  MySQL 8.0.24 phpMyAdmin 5.0 搭建准备1:一台服务器(压缩包里附购买教程) ...

  9. 如何搭建微信小程序?

    随着微信小程序被应用得越来越多,很多未入局的企业商家都在考虑搭建自己的微信小程序.那么如何搭建微信小程序呢?下面跟大家聊一聊. 如何搭建微信小程序? 第一步:准备好小程序账号 搭建微信小程序之前,我们 ...

  10. 基于滴滴云搭建微信小程序

    微信小程序自 2017 年推出以来,以其轻量级级特性,为传统移动互联网格局带来了极大的震动.相对于传统 APP,小程序无需下载,即扫即用,用完即走,极大的节省了手机的空间,提高了用户使用的便利性. 本 ...

最新文章

  1. C++中给二维指针分配内存
  2. linux用数字方式显示文件权限,全面解析Linux数字文件权限
  3. 程序 峰谷值 提取_医学影像组学特征值(Radiomics Features)提取之Pyradiomics(一)理论篇...
  4. 2019年末逆向复习系列之今日头条WEB端_signature、as、cp参数逆向分析
  5. jzoj4229-学习神技【逆元,费马小定理】
  6. [Poi2011]Tree Rotations线段树合并
  7. 基于POLARDB数据库的压测实践
  8. 安装python37路径报错_Robot framework安装python3.7导入HttpLibrary.HTTP报错
  9. Oracle VM + Windows2003 Server 配置
  10. C++ delete的三种面貌
  11. 坚持就是成功,没有成功就是你失败的次数太少
  12. php pathinfo 解析,php 解析pathinfo 类
  13. linux怎样使用小米线刷工具,小米线刷工具推荐及使用教程
  14. LAMP架构简介与概述 及服务安装
  15. git与svn的区别
  16. linux 64 输入法下载,搜狗输入法 for Linux
  17. 实测发现,微软 Win11 并不比 Win10 更快
  18. Shell - mkdir
  19. java 根据点画不规则封闭图形
  20. nodejs使用fluent-ffmpeg下载m3u8视频

热门文章

  1. [AIR] 获取U盘,打开U盘
  2. G-Sensor 校准标准
  3. 【ACL2020】用于改进文本分类的特征投影
  4. 【面向工业界】京东NLP算法工程师培养计划
  5. 【赛尔原创】用对比集成式方法理解基于文档的对话
  6. 【AAAI 2020】微软亚洲研究院6篇精选论文在家必看!
  7. 【ACL2019】知识图谱的全方位总结
  8. 20191226_1_淘宝口红商品分析
  9. 模型调参(AutoML)— optuna
  10. 3.3 决策树分类与回归实战