The original link

Personal blog – Welcome to visit

Effect preview:

The development of micro channel small program is a very hot field at present, there are a lot of development modes, to find a way of their own will make the development smooth.

This architecture uses Taro + DVA + typescript to build front-end development

  • Taro, the React framework of JINGdong Cave Lab, is very mature, and the big factory is maintaining and updating iteration. There is no need to worry about the problem that no one will maintain it. He has his own UI and material community, which is much more convenient than the native small program. Wechat applet, H5, Baidu applet, Alipay applet, Bytedance applet, QQ light application, fast application, ReactNative;
  • Data management is a DVA framework integrated with Redux, which is a data flow solution based on Redux and Redux-Saga. In order to simplify the development experience, DVA also has additional built-in React-Router and FETCH, so it can also be understood as a lightweight application framework.
  • TypeScript is what’s called a JavaScript superset. It is not a replacement for JavaScript, nor does it add any new functionality to JavaScript code. In contrast, TypeScript allows programmers to use object-oriented constructs in their code and then convert them to JavaScript. It also includes convenience features such as type safety and compile-time type checking.

data

Taro’s official website is https://taro.aotu.io/

Dva official website: https://dvajs.com/guide/

start

Preparatory work

Cli tool installation:

$yarn global add @tarojs/cli # OR install CNPM, $CNPM install -g@tarojs /cliCopy the code

Create a template project using the command:


$ taro init Taro_dva_Typescript

Copy the code

Installing configuration files

Install the dva

cnpm install --save dva-core dva-loading

  • dva-core: a plugin that wraps redux and Redux-Saga
  • dva-loading: Manages the loading state of a page

Install @ tarojs/story

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

Configuration project file

Delete unnecessary files and add some needed files. First, delete the index folder under./ SSRC /page. Later, use the command line to generate a complete structure folder.

In the ‘/ SRC’ directory according to their actual needs to do the following configuration:

  • assetsStatic resources, such as image and iconfont
  • config: Project configuration file
  • components: Common components written by the project
  • types: The Typescript type declaration common to the project
  • models: a reference to the project dVA plug-in model function or some common JS files
  • utils: Plugins that are packaged in the project

Item Specific configuration operations

1, in./src/configCreate index.ts and add the project configuration information

/ * * * here in order to facilitate the test use Easy Mock simulation interface data * * https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist * / export const ONLINEHOST = 'https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist'; / * * * * * the mock interface/export const MOCKHOST = 'https://www.easy-mock.com/mock/5d38269ffb233553ab0d10ad/getlist'; /** * export const ISMOCK = true; */ export const SHAREINFO = {'title': 'share title', 'path': 'path', 'imageUrl': 'image'}Copy the code

2, in./src/utilsCreate dvA. ts and configure DVA


import { create } from "dva-core";
import { createLogger } from "redux-logger";
import  createLoading  from "dva-loading";



let app
let store
let dispatch
let registered

function createApp(opt) {
    // Redux logs
    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
    }
}

Copy the code

3, in./src/utilsCreate tips.ts, integrate and package wechat native pop-ups

import Taro from "@tarojs/taro"; Import {node} from "_@[email protected]@@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:' loading ',force = false){if (this.isloading &&! force) { return } this.isLoading = true; if (Taro.showLoading) { Taro.showLoading({ title:title, Mask: true})} else {Taro. ShowNavigationBarLoading () / / navigation bar loading animation}} / loaded * * * * / static the loaded () {let duration = 0; if (this.isLoading) { this.isLoading = false; if (Taro.hideLoading) { Taro.hideLoading() } else { Taro.hideNavigationBarLoading(); } duration = 500; Return new Promise(resolve => setTimeout(resolve,duration))} /** * pop-up prompt */ 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)) } } }Copy the code

4, in./src/configCreate requestConfig.ts and configure the request interface

/** * request public parameters */ export const commonParame = {} /** * request mapping file */ export const requestConfig = { LoginUrl :'/ API /user/wechat-auth' // wechatCopy the code

5, in./src/utilsCreate common.ts and share the function


/** * share function */

export const repeat = (str = '0', times) => (new Array(times + 1)).join(str);
// time before +0
export const pad = (num, maxLength = 2) => repeat('0', maxLength - num.toString().length) + num;

// Global public variables
export let globalData: any = {

}

// Time format function

export const formatTime = time => {
    `${pad(time.getHours())}:${pad(time.getMinutes())}:${pad(time.getSeconds())}.${pad(time.getMilliseconds(), 3`)}}Copy the code

6, in./src/utilsCreate logger.ts and wrap the log function

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; // Message const headerCSS = ['color:gray; font-weight:lighter;'] const styles = s => `color ${s}; font-weight: bold`; // render logger.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 ? } function createLogger(options: LogEntry = {}){ const loggerOptions = Object.assign({}, defaults, options) const logEntry = options logEntry.started = new Date(); printBuffer(logEntry, Object.assign({}, loggerOptions)) } export { defaults, createLogger, }Copy the code

7, in./src/utilsRequest. Ts is created to encapsulate the HTTP request

import Taro,{ Component } from "@tarojs/taro"; import { ISMOCK,MAINHOST } from ".. /config"; import { commonParame,requestConfig } from ".. /config/requestConfig"; import Tips from "./tips"; / / packaging request to declare the 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 {// static loginReadyPromise: Promise<any> = promise.resolve () // isLoading static isLoading: Boolean = false static apiLists: {[key: string]: () => any; } = {} // token static token: string = "// start processing options static 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} // login static login(){if (! this.isLoading) { this.loginReadyPromise = this.onLogining() } return this.loginReadyPromise } static onLogining(){ this.isLoading = true; Return new Promise(async (resolve, reject) => {// get code const {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}})} /** * Request request based on Taro. Request ** */ static async Request (OPts: Options) {// tarpaulin. Request const res = await tarpaulin. Request (opts); // Whether mock if(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) | | 'network Error ~'} Tips. The toast (edata. Err) throw new Error (edata. Err)} / * * * * / static build request function 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; 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 anyCopy the code

Note:

Here tsLint will report an error: the attribute “$API” does not exist on type “Component

“. App-shim.d.ts will be created in the./ SRC directory because no declaration is added
,>

/** * Add custom types such as taro */ import taro,{Component} from '@tarojs/taro' // Define custom method types declare Module on Component {interface Component {$API: any}} // declare let require: any; declare let dispatch: anyCopy the code

8, in./src/configBelow create taroconfig. ts to encapsulate some methods of the taro applet

import Taro,{ Component } from '@tarojs/taro' import { SHAREINFO } from '.. /config/index' /** * encapsulates the taro applet some methods * - method overwrite * -utils mount */ / navigateTo more than 8 times, NavigateTo = (data) => {if (taro.getCurrentPages ().length > 8) {if (taro.getCurrentPages ().length > 8) { Return Taro. RedirectTo (data)} return nav (data)} / / mount Component sharing method Component. The prototype. OnShareAppMessage = function () { return SHAREINFO }Copy the code

Configuration file generation script

1, create scripts folder in root directory, add./scripts/template.js

*/ const fs = require('fs') const dirName = process.argv[2] const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1); if (! DirName) {console.log(' filename cannot be empty '); Console. log(' usage: NPM run tem test'); process.exit(0); } // page template build 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: 'page title'} constructor(props: ${capPirName}Props) { super(props) this.state = {} } componentDidMount() { } render() { return ( <View ClassName ='fx-${dirName}-wrap'> page content </View>)}} export default ${capPirName} '// SCSS file template const scssTep =' @import' ".. /.. /assets/scss/variables"; .#{$prefix} { &-${dirName}-wrap { width: 100%; min-height: 100Vh; }} '/ / config interface address configuration template const configTep = 'export default {test:'/wechat/perfect-info', //XX interface} '// interface request template const serviceTep =' import Api from '.. /.. /utils/request' export const testApi = data => api.test (data) '// model template const modelTep =' // import Taro from '@tarojs/taro'; // import * as ${dirName}Api from './service'; export default { namespace: '${dirName}', state: { }, effects: {}, reducers: {}} 'const interfaceTep =' /** ** ${dirName}. State Parameter type ** @export * @interface ${capPirName} state */ export Interface ${capPirName}State {} /** * ${dirName}. Parameter type ** @export * @interface ${capPirName} props */ export interface ${capPirName}Props {} ` fs.mkdirSync(`./src/pages/${dirName}`); // mkdir $1 process.chdir(`./src/pages/${dirName}`); // cd $1 fs.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);Copy the code

The last

Add the corresponding command to the scripts of package.json in the root directory


"scripts": {..."tep": "node scripts/template"."com": "node scripts/component"
}

Copy the code

2, automatically generate script folder

cnpm run tep index

The page folder generates an index folder, which contains

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

Configuring the service code

1, at firstsrcDirectory creationmodelsFolders, from the collection projectmodelRelationship.

import index from '.. /pages/index/model'; export default[ index ]Copy the code

Currently, the project only has the index page, and the array in export default only has index. It should be noted that this is the [] array.

2. Modify very important filesapp.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' // If you need to enable React Devtools in h5 // Uncomment: // 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 {/** * specifies that the type declaration of config is: Taro.Config * * Since typescript can only derive basic types of Key for object types * for things like navigationBarTextStyle: 'black' such a derived type is string * navigationBarTextStyle tips and statement: 'black' | 'white' type of conflict, the need to display the statement type * / config: config = {pages: [ 'pages/index/index' ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'WeChat', navigationBarTextStyle: 'black' } } /** * * 1. The applet opens with the parameter Globaldata.extradata. Xx * 2. The parameter GlobalData.extradata. Xx * 3 is entered from the QR code. Globaldata.systeminfo */ Async componentDidMount () {const referrerInfo = this.$router.params.referrerInfo const query = this.$router.params.query ! globalData.extraData && (globalData.extraData = {}) if (referrerInfo && referrerInfo.extraData) { globalData.extraData =  referrerInfo.extraData } if (query) { globalData.extraData = { ... globalData.extraData, ... 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'))Copy the code

Modify the interface request./src/pages/index/config.tsfile

An interface to get list data

Export default {getList: '/ getList ', // getList}Copy the code

4, modify,./src/config/requestConfig.tsFile mapping

Introduces the newly created config file for the Index page

import index from ".. /pages/index/config"; Export const commonParame = {} /** * Export const commonParame = {} /** * LoginUrl :'/ API /user/ weike-auth ', // wechat login interface... index }Copy the code

5, modify,./src/pages/index/service.tsInterface request in

Again, the getList interface

import Api from '.. /.. /utils/request' export const getList = (data) => { return Api.getList(data) }Copy the code

6, modify,./src/pages/index/index.interface.tsThe parameter type in

Configure the parameters based on specific project parameters


/** * index.state Parameter type * @interface IndexState */
export interface IndexState {

}

/** * index. Props Parameter type ** @export * @interface IndexProps */
exportinterface IndexProps { dispatch? : any, data? : Array<DataInterface> }export interface DataInterface {
    des:string,
    lunar:string,
    thumbnail_pic_s:string,
    title:string,
    _id:string
}

Copy the code

7, modify,./src/pages/index/model.tsIn theeffectsfunction

Create the interface that the page needs to request, and link the interface in the Service to make the data request, using getList as an example.

// 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(' data interface returns ',result); if (! error) { yield put({ type: 'save', payload: { data:result.data }, }) } } }, reducers: { save(state, { payload }) { return { ... state, ... payload }; }}},Copy the code

8, modify,./src/pages/index/index.tsxInside page structure

Here’s a simple implementation list news page.

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.props console.log('this.props===>>',data); Return (<View className='fx-index-wrap'> <View className='index-topbar'>New info </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 IndexCopy the code

9, modify,./src/pages/index/index.scssStyle of the home page

This is written as syntactic sugar for 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: #Awesome!;
        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; }}}}Copy the code

Project start

Run the applets to compile commands

cnpm run dev:weapp

After the compilation of the project is completed, a DIST will be generated in the root directory of the project. Open the wechat applet developer, import the local just generated DIST file, and successfully start the project.

Effect preview:


If have what question welcome to discuss, study together.

Item Example Github Address: github.com/Duanruilong…