Taro+dva+Typescript 搭建微信小程序架構(gòu)

原文鏈接

個(gè)人博客-歡迎訪問(wèn)

效果預(yù)覽圖:

Taro

微信小程序的開(kāi)發(fā)目前是很熱的一個(gè)領(lǐng)域,有很多的開(kāi)發(fā)模式,找到一種屬于自己的方法才會(huì)使得開(kāi)發(fā)順心順利淫奔。

此架構(gòu)是使用 Taro + dva + typescript 構(gòu)建前端開(kāi)發(fā)

  • 京東凹凸實(shí)驗(yàn)室的React框架Taro很成熟灶平,又是大廠在維護(hù)更新迭代,不用擔(dān)心沒(méi)人維護(hù)的問(wèn)題雅宾,他有自己的UI還有物料社區(qū)养涮,比起原生小程序方便很多,支持多端,一處代碼贯吓,多處運(yùn)行懈凹,微信小程序、H5悄谐、百度小程序介评、支付寶小程序、字節(jié)跳動(dòng)小程序爬舰、QQ輕應(yīng)用们陆、快應(yīng)用、ReactNative情屹;
  • 數(shù)據(jù)管理是Redux集成的dva框架坪仇,是一個(gè)基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡(jiǎn)化開(kāi)發(fā)體驗(yàn)屁商,dva 還額外內(nèi)置了 react-router 和 fetch烟很,所以也可以理解為一個(gè)輕量級(jí)的應(yīng)用框架;
  • TypeScript就是所謂的JavaScript超集蜡镶。它不是JavaScript的替代品雾袱,也不會(huì)為JavaScript代碼添加任何新功能。相反官还,TypeScript允許程序員在其代碼中使用面向?qū)ο蟮臉?gòu)造芹橡,然后將其轉(zhuǎn)換為JavaScript。它還包括類型安全和編譯時(shí)類型檢查等便利功能望伦。
Taro

資料

Taro官網(wǎng)地址:https://taro.aotu.io/

dva官網(wǎng)地址:https://dvajs.com/guide/

開(kāi)始

前期工作準(zhǔn)備

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


使用命令創(chuàng)建模板項(xiàng)目:


$ taro init Taro_dva_Typescript

Taro

安裝配置文件

安裝dva

cnpm install --save dva-core dva-loading

  • dva-core:封裝了 redux 和 redux-saga的一個(gè)插件
  • dva-loading:管理頁(yè)面的loading狀態(tài)

安裝@tarojs/redux

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

配置項(xiàng)目文件

去除不需要的文件,添加實(shí)際需要的一些文件屯伞,先刪除./ssrc/page下的index文件夾腿箩,后期使用命令行生成完整結(jié)構(gòu)的文件夾。

在``/src`目錄下根據(jù)自己的實(shí)際需求進(jìn)行一下配置:

  • assets: 一些靜態(tài)資源劣摇,比如:image珠移、iconfont
  • config: 項(xiàng)目配置文件
  • components: 項(xiàng)目編寫(xiě)的一些共用組件
  • types: 項(xiàng)目公共的Typescript類型聲明
  • models: 項(xiàng)目dva插件model函數(shù)的引用或者是一些共用的js文件
  • utils: 項(xiàng)目里封裝的一些插件

項(xiàng)目一些具體配置操作

1、在./src/config下創(chuàng)建index.ts末融,添加項(xiàng)目配置信息

/** 
 * 這里為了方便測(cè)試使用 Easy Mock 模擬接口數(shù)據(jù)
 * 
 * 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;


/**
 * 這是一個(gè)全局的分享信息 不用每一個(gè)都去寫(xiě)
 */
export const SHAREINFO = {
    'title': '分享標(biāo)題',
    'path': '路徑',
    'imageUrl': '圖片'
  }


2钧惧、在./src/utils下創(chuàng)建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 registered

function 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下創(chuàng)建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
        });
        // 去除結(jié)束回調(diào)函數(shù)
        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() //導(dǎo)航條加載動(dòng)畫(huà)
        }
    }

    /** 
     * 加載完成
    */
    static loaded(){
        let duration = 0;
        if (this.isLoading) {
            this.isLoading = false;
            if (Taro.hideLoading) {
                Taro.hideLoading()
            } else {
                Taro.hideNavigationBarLoading(); //導(dǎo)航條加載動(dòng)畫(huà)
            }
            duration = 500;
        }
        // 設(shè)定隱藏的動(dòng)畫(huà)時(shí)長(zhǎng)為500ms,防止直接toast時(shí)出現(xiàn)問(wèn)題
        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下創(chuàng)建requestConfig.ts涂乌,統(tǒng)一配置請(qǐng)求接口

/** 
 * 請(qǐng)求公共參數(shù)
*/

export const commonParame = {}

/** 
 * 請(qǐng)求的映射文件
*/

export const requestConfig = {
    loginUrl:'/api/user/wechat-auth' // 微信的登陸接口
}


5、在./src/utils下創(chuàng)建common.ts钮孵,共用函數(shù)


/** 
 * 共用函數(shù)
*/

export const repeat = (str = '0', times) => (new Array(times + 1)).join(str);
// 時(shí)間前面 +0 
export const pad = (num, maxLength = 2) => repeat('0', maxLength - num.toString().length) + num;

// 全局的公共變量
export let globalData: any = {

}

// 時(shí)間格式裝換函數(shù)

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

6骂倘、在./src/utils下創(chuàng)建logger.ts,封裝log函數(shù)


/** 
 * 封裝logo函數(shù)
*/

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 ? : object  // 觸發(fā)時(shí)間
}

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,
}

7巴席、在./src/utils下創(chuàng)建request.ts历涝,封裝http請(qǐng)求


import Taro,{ Component } from "@tarojs/taro";
import { ISMOCK,MAINHOST } from "../config";
import { commonParame,requestConfig } from "../config/requestConfig";
import Tips from "./tips";


// 封裝請(qǐng)求


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 {
    // 登陸時(shí)的promise
    static loginReadyPromise: Promise<any> = Promise.resolve()

    // 正在登陸
    static isLoading: boolean = false

    // 導(dǎo)出的API對(duì)象
    static apiLists: { [key: string]: () => any;} = {}

    // token
    static token: string = ''

    // 開(kāi)始處理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
    }


    // 登陸
    static login(){
        if (!this.isLoading) {
            this.loginReadyPromise = this.onLogining()
        }
        return this.loginReadyPromise
    }

    static onLogining(){
        this.isLoading = true;
        return new Promise(async (resolve, reject) => {
            // 獲取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
            }
        })

    }

    /** 
     * 基于 Taro.request 的 request 請(qǐng)求
     * 
     * */ 
    static async request(opts: Options) {
        
        // Taro.request 請(qǐng)求
        const res = await Taro.request(opts);

        // 是否mock
        if(ISMOCK) return res.data;

        // 請(qǐng)求失敗
        if (res.data.code === 99999) {
            await this.login();
            return this.request(opts)
        }

        // 請(qǐng)求成功
        if (res.data) {
            return res.data
        }

        // 請(qǐng)求錯(cuò)誤
        const edata = { ...res.data, err : (res.data && res.data.msg) || '網(wǎng)絡(luò)錯(cuò)誤 ~'}
        Tips.toast(edata.err)
        throw new Error(edata.err)

    }


    /** 
     * 創(chuàng)建請(qǐng)求函數(shù)
    */
   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會(huì)報(bào)這樣的錯(cuò):類型“Component<any, any>”上不存在屬性“$api”。漾唉,因?yàn)闆](méi)有添加聲明荧库,需在./src目錄下創(chuàng)建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下創(chuàng)建taroConfig.ts赵刑,封裝taro小程序的一些方法

import Taro,{ Component } from '@tarojs/taro'
import { SHAREINFO } from '../config/index'



/** 
 * 封裝taro小程序的一些方法
 *  - 方法改寫(xiě)
 *  - utils 掛載
*/


// navigateTo 超過(guò)8次后分衫,強(qiáng)行進(jìn)行redirectTo,避免頁(yè)面卡頓

 const nav = Taro.navigateTo
 Taro.navigateTo = (data) => {
     if (Taro.getCurrentPages().length > 8) {
         return Taro.redirectTo(data)
     }
     return nav(data)
 }


// 掛載分享方法 Component

Component.prototype.onShareAppMessage = function () {
    return SHAREINFO
}


配置文件生成腳本

1、在根目錄下創(chuàng)建scripts文件夾般此,添加./scripts/template.js


/** 
 *  pages 頁(yè)面快速生成腳本
 *  
 *  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);
}

// 頁(yè)面模板構(gòu)建

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: '頁(yè)面標(biāo)題'
    }
    constructor(props: ${capPirName}Props) {
        super(props)
        this.state = {}
    }

    componentDidMount() {
        
    }

    render() {
        return (
        <View className='fx-${dirName}-wrap'>
            頁(yè)面內(nèi)容
        </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接口
    }
`

// 接口請(qǐng)求模板

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 參數(shù)類型
 *
 * @export
 * @interface ${capPirName}State
 */
export interface ${capPirName}State {}

/**
 * ${dirName}.props 參數(shù)類型
 *
 * @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);



最后

在根目錄的package.json的scripts里加上對(duì)應(yīng)的命令


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

2蚪战、自動(dòng)生成腳本文件夾

cnpm run tep index

page文件夾下生成了一個(gè)index的文件夾,里面包含

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

配置業(yè)務(wù)代碼

1铐懊、先在src目錄下創(chuàng)建models文件夾邀桑,集合項(xiàng)目里的model關(guān)系壁畸。


import index from '../pages/index/model';


export default[
    index
]

項(xiàng)目目前只有index頁(yè)面,export default這里的數(shù)組就只有index空闲,需要注意這里是[]數(shù)組碴倾。

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 環(huán)境中開(kāi)啟 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 對(duì)于 object 類型推導(dǎo)只能推出 Key 的基本類型
   * 對(duì)于像 navigationBarTextStyle: 'black' 這樣的推導(dǎo)出的類型是 string
   * 提示和聲明 navigationBarTextStyle: 'black' | 'white' 類型沖突, 需要顯示聲明類型
   */
  config: Config = {
    pages: [
      'pages/index/index'
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#fff',
      navigationBarTitleText: 'WeChat',
      navigationBarTextStyle: 'black'
    }
  }

  /**
   *
   *  1.小程序打開(kāi)的參數(shù) globalData.extraData.xx
   *  2.從二維碼進(jìn)入的參數(shù) globalData.extraData.xx
   *  3.獲取小程序的設(shè)備信息 globalData.systemInfo
   */
  async componentDidMount () {
    // 獲取參數(shù)
    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,
        ...query
      }
    }

    // 獲取設(shè)備信息
    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残邀、修改接口請(qǐng)求./src/pages/index/config.ts文件

一個(gè)獲取列表數(shù)據(jù)接口


export default {
  getList: '/getlist', //getlist接口
}

4、修改./src/config/requestConfig.ts文件的映射關(guān)系

引入index頁(yè)面的剛剛創(chuàng)建的config文件


import index from "../pages/index/config"; // index的接口



/** 
 * 請(qǐng)求公共參數(shù)
*/
export const commonParame = {}

/** 
 * 請(qǐng)求的映射文件
*/

export const requestConfig = {
    loginUrl:'/api/user/wechat-auth', // 微信的登陸接口
    ...index
}


5耻台、修改./src/pages/index/service.ts里的接口請(qǐng)求

還是依據(jù)之前的getlist接口


import Api from '../../utils/request'

export const getList = (data) => {

  return Api.getList(data)

}
  

6盆耽、修改./src/pages/index/index.interface.ts里的參數(shù)類型

根據(jù)項(xiàng)目具體的參數(shù)摄杂,自行進(jìn)行配置


/**
 * index.state 參數(shù)類型
 * @interface IndexState
 */
export interface IndexState {

}

/**
 * index.props 參數(shù)類型
 *
 * @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函數(shù)

在這里創(chuàng)建頁(yè)面需要請(qǐng)求的接口,鏈接service里的接口發(fā)起數(shù)據(jù)請(qǐng)求,這里以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('數(shù)據(jù)接口返回',result);
      
      if (!error) {
        yield put({
          type: 'save',
          payload: {
            data:result.data
          },
        })
      }
    }
  },

  reducers: {
    save(state, { payload }) {
      return { ...state, ...payload };
    },
  }

}

8椎组、修改./src/pages/index/index.tsx里頁(yè)面結(jié)構(gòu)

這里簡(jiǎn)單的實(shí)現(xiàn)列表新聞頁(yè)面寸癌。


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資訊</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首頁(yè)的樣式

這里的寫(xiě)法是sass的語(yǔ)法糖


@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;
      }
    }
  }
 
}


項(xiàng)目啟動(dòng)

運(yùn)行小程序編譯命令

cnpm run dev:weapp

等待項(xiàng)目編譯完成,會(huì)在項(xiàng)目根目錄下生成一個(gè)dist,打開(kāi)微信小程序開(kāi)發(fā)者根據(jù)檬嘀,導(dǎo)入本地剛剛生成的dist文件鸳兽,就成功啟動(dòng)了項(xiàng)目揍异。

效果預(yù)覽圖:

Taro

如有啥問(wèn)題歡迎討論衷掷,共同學(xué)習(xí)。

項(xiàng)目示例Github地址:https://github.com/Duanruilong/taro_dva_typescript

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市镜悉,隨后出現(xiàn)的幾起案子医瘫,更是在濱河造成了極大的恐慌醇份,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锥债,居然都是意外死亡哮肚,警方通過(guò)查閱死者的電腦和手機(jī)允趟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)分唾,“玉大人改含,你說(shuō)我怎么就攤上這事捍壤【榫酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵疗隶,是天一觀的道長(zhǎng)斑鼻。 經(jīng)常有香客問(wèn)我坚弱,道長(zhǎng)荒叶,這世上最難降的妖魔是什么些楣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮埋市,結(jié)果婚禮上道宅,老公的妹妹穿的比我還像新娘污茵。我一直安慰自己,他們只是感情好襟士,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布陋桂。 她就那樣靜靜地躺著宣渗,像睡著了一般痕囱。 火紅的嫁衣襯著肌膚如雪鞍恢。 梳的紋絲不亂的頭發(fā)上有序,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音崇败,去河邊找鬼盅称。 笑死,一個(gè)胖子當(dāng)著我的面吹牛后室,可吹牛的內(nèi)容都是我干的缩膝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼岸霹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疾层!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起贡避,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痛黎,失蹤者是張志新(化名)和其女友劉穎湖饱,沒(méi)想到半個(gè)月后仅仆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年擎淤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孝冒。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡焕梅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出义钉,到底是詐尸還是另有隱情删壮,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布粘秆,位于F島的核電站,受9級(jí)特大地震影響巩趁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一越驻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸戒祠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至地淀,卻和暖如春帮毁,著一層夾襖步出監(jiān)牢的瞬間爷肝,已是汗流浹背对嚼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兢仰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓剂碴,卻偏偏與公主長(zhǎng)得像把将,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忆矛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容