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-Sagadva-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:
assets
Static resources, such as image and iconfontconfig
: Project configuration filecomponents
: Common components written by the projecttypes
: The Typescript type declaration common to the projectmodels
: a reference to the project dVA plug-in model function or some common JS filesutils
: Plugins that are packaged in the project
Item Specific configuration operations
1, in./src/config
Create 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/utils
Create 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/utils
Create 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/config
Create 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/utils
Create 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/utils
Create 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/utils
Request. 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/config
Below 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 firstsrc
Directory creationmodels
Folders, from the collection projectmodel
Relationship.
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.ts
file
An interface to get list data
Export default {getList: '/ getList ', // getList}Copy the code
4, modify,./src/config/requestConfig.ts
File 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.ts
Interface 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.ts
The 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.ts
In theeffects
function
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.tsx
Inside 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.scss
Style 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…