Персональный блог-Добро пожаловать в гости
Предварительный просмотр эффекта:
Разработка мини-программ WeChat в настоящее время является очень горячей областью.Существует множество режимов разработки.Найдя свой собственный метод, разработка пойдет гладко.
Эта архитектура предназначена для использования Taro + dva + typescript для разработки интерфейса.
- Платформа React Taro от JD.com Labs очень зрелая, и это крупная фабрика, которая постоянно поддерживает и обновляет ее. Не беспокойтесь о том, что никто не поддерживает ее. У нее есть собственный пользовательский интерфейс и сообщество материалов, которое намного удобнее, чем собственный апплет, код в нескольких местах, запуск в нескольких местах, апплет WeChat, апплет H5, апплет Baidu, апплет Alipay, апплет ByteDance, легкое приложение QQ, быстрое приложение, ReactNative;
- Управление данными — это фреймворк dva, интегрированный с Redux, схема потока данных, основанная на redux и redux-saga, и для упрощения процесса разработки dva также имеет дополнительный встроенный react-router и fetch, так что его тоже можно понять как легкая рамка приложения;
- TypeScript — это то, что называют надмножеством JavaScript. Это не замена JavaScript и не добавление каких-либо новых функций в код JavaScript. Напротив, TypeScript позволяет программистам использовать в своем коде объектно-ориентированные конструкции, которые затем переводятся в JavaScript. Он также включает удобные функции, такие как безопасность типов и проверка типов во время компиляции.
материал
Адрес официального сайта Таро: https://taro.aotu.io/
адрес официального сайта dva: https://dvajs.com/guide/
Начинать
Предварительная подготовка к работе
установка инструмента кли:
# 使用 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
установить файл конфигурации
установить два
cnpm install --save dva-core dva-loading
-
dva-core
: Плагин, который инкапсулирует redux и redux-saga. -
dva-loading
: управлять статусом загрузки страницы
установить @tarojs/редукс
cnpm install --save redux @tarojs/redux @tarojs/redux-h5 redux-thunk redux-logger
файл проекта конфигурации
Удалите ненужные файлы, добавьте некоторые файлы на фактическую потребность, удалить./ssrc/page
Индексная папка в командной строке используется для создания папки с полной структурой позже.
В каталоге ``/src` настройте следующее в соответствии с вашими потребностями:
-
assets
: некоторые статические ресурсы, такие как: изображение, шрифт значка -
config
: файл конфигурации проекта -
components
: Некоторые общие компоненты, написанные проектом -
types
: Объявления типа машинописного текста, общие для проекта. -
models
: ссылка на модельную функцию плагина dva проекта или некоторые общие файлы 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
Создайте два.ц под, настройте два
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
Создайте подсказки.ts ниже, интегрируйте и упакуйте родное всплывающее окно WeChat.
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 ниже, инкапсулируйте функцию журнала
/**
* 封装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;
// 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 // 触发时间
}
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
Создайте ниже 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 {
// 登陆时的promise
static loginReadyPromise: Promise<any> = Promise.resolve()
// 正在登陆
static isLoading: boolean = false
// 导出的API对象
static apiLists: { [key: string]: () => any;} = {}
// token
static token: string = ''
// 开始处理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 请求
*
* */
static async request(opts: Options) {
// Taro.request 请求
const res = await Taro.request(opts);
// 是否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) || '网络错误 ~'}
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”
. , поскольку объявление не добавляется, его необходимо создать в каталоге ./srcapp-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, упаковывающие апплет таро
import Taro,{ Component } from '@tarojs/taro'
import { SHAREINFO } from '../config/index'
/**
* 封装taro小程序的一些方法
* - 方法改写
* - utils 挂载
*/
// navigateTo 超过8次后,强行进行redirectTo,避免页面卡顿
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. Создайте папку 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 $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": {
...
"tep": "node scripts/template",
"com": "node scripts/component"
}
2. Автоматически создавать папку скриптов
cnpm run tep 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.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
}
}
// 获取设备信息
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.ts
внутриeffects
функция
Создайте интерфейс, который страница должна запрашивать здесь, ссылка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.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
Стиль домашней страницы
Надпись здесь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
, Откройте разработчик апплета WeChat в соответствии с ним, импортируйте только что созданный локальныйdist
файл, проект был успешно запущен.
Предварительный просмотр эффекта:
Если у вас есть какие-либо вопросы, пожалуйста, обсудите и изучите вместе.
Пример проекта Адрес Github:GitHub.com/D U An Ruilong…