1、背景
这几天在wx随机搜了关键词“前端简历”,发现了很多个人的在线简历小程序,脑子里突然也想搞一个属于自己的简历小程序。也可以当做自己的个人项目,大家也可以试一下。
2、选框架
开发小程序现在有很多方式可以选择,可以给我们开发带来很多便利,同时也让项目具有一定的扩展性。目前市面上流行的小程序开发方式主要有:原生,腾讯wepy,美团mpvue,O2实验室Taro,DCloud公司Uni-app 等5种.
下图是部分框架简单的对比介绍:
由于目前公司的业务主要使用的技术栈是 React + Ts,因此使用taro开发小程序再好不过了,上手快,社区活跃度也蛮高的,而且这个框架的目标很伟大,就是用 React 的开发方式编写一次代码,生成能运行在微信小程序、H5、React Native 等的应用,其拓展性可谓是相当好的,因此选定 Taro。
3、开始搭建
yarn global add @tarojs/cli taro init my-intro-App 按照自己需要安装插件:typescript\sass cd my-intro-app yarn dev:weapp 以下是项目结构:
├── dist 编译结果目录
|
├── config 项目编译配置目录
| ├── index.js 默认配置
| ├── dev.js 开发环境配置
| └── prod.js 生产环境配置
|
├── src 源码目录
| ├── pages 页面文件目录
| | └── index index 页面目录
| | ├── index.js index 页面逻辑
| | ├── index.css index 页面样式
| | └── index.config.js index 页面配置
| |
| ├── app.js 项目入口文件
| ├── app.css 项目总通用样式
| └── app.config.js 项目入口配置
|
├── project.config.json 微信小程序项目配置 project.config.json
├── project.tt.json 字节跳动小程序项目配置 project.config.json
├── project.swan.json 百度小程序项目配置 project.swan.json
├── project.qq.json QQ 小程序项目配置 project.config.json
|
├── babel.config.js Babel 配置
├── tsconfig.json TypeScript 配置
├── .eslintrc ESLint 配置
|
└── package.json
src 目录下创建几个文件夹:api、assets、components、store、styles
- 将index.scss文件重新命名为style.module.scss,将config/index.js文件中cssModules.enable置为true,即可支持模块化引入样式。
- 将index.tsx内容改写,因为我习惯使用函数式编程,所以将class改写。
import React,{useEffect} from 'react'
import { View, Text } from '@tarojs/components'
import styles from './style.module.scss';
const Index:React.FC = props=> {
useEffect(()=>{
})
return (
<View className={styles.index}>
<Text>Hello world!</Text>
</View>
)
}
export default Index
- 在src目录下新建几个目录:api、assets、components、store
- 打开config目录下的index.js文件,设置别名路径如下:
const path = require('path') // eslint-disable-line 别名设置
const config = {
projectName: 'my-intro-app',
date: '2021-8-26',
designWidth: 750,
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
},
sourceRoot: 'src',
outputRoot: 'dist',
plugins: [],
defineConstants: {
},
copy: {
patterns: [
],
options: {
}
},
framework: 'react',
mini: {
postcss: {
pxtransform: {
enable: true,
config: {
}
},
url: {
enable: true,
config: {
limit: 1024 // 设定转换尺寸上限
}
},
cssModules: {
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
postcss: {
autoprefixer: {
enable: true,
config: {
}
},
cssModules: {
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
},
alias: {
'@api': path.resolve(__dirname, '..', 'src/api'),
'@components': path.resolve(__dirname, '..', 'src/components'),
'@assets': path.resolve(__dirname, '..', 'src/assets'),
'@store': path.resolve(__dirname, '..', 'src/store')
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}
避免别名引入报错,找到tsconfig.json文件加入以下代码:
"paths": {
"@src/*": ["src/*"],
"@config/*": ["src/config/*"],
"@store/*": ["src/store/*"],
"@views/*": ["src/views/*"],
"@components/*": ["src/components/*"],
"@services/*": ["src/services/*"],
"@styles/*": ["src/styles/*"]
},
• 使用 redux 状态管理 src目录下创建store/index.js文件,写入如下内容:
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '@redux/reducers'
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose
const middlewares = [
thunkMiddleware
]
/**
* redux-logger 中间件,用于在发起 Action 时,在控制台打印 Action 及其前后 Store 中的保存的状态信息
*/
if (process.env.NODE_ENV === 'development' && process.env.TARO_ENV !== 'quickapp') {
middlewares.push(require('redux-logger').createLogger())
}
const enhancer = composeEnhancers(
applyMiddleware(...middlewares),
)
export default function configStore () {
const store = createStore(rootReducer, enhancer)
return store
}
src目录下创建 redux 目录,在其中分别创建:action目录/login.ts、reducers目录/user.ts、constants/index.ts. • reducers/user.ts代码如下:
import { SET_IS_LOGIN,SET_LOGIN_INFO } from '../constants'
// 初始状态
const INITIAL_STATE = {
avatar: '', // 头像
nickName: '', // 微信昵称
isLogin: false, // 初始登录状态
}
export default function user (state = INITIAL_STATE, action) {
switch (action.type) {
case SET_IS_LOGIN:
// 登录状态
const isLogin = action.payload
return {
...state,
isLogin
};
case SET_LOGIN_INFO:
// 头像和昵称
const { avatar, nickName } = action.payload
return {
...state,
avatar,
nickName
}
default:
return state
}
}
• action/login.ts代码如下:
import { SET_IS_LOGIN,SET_LOGIN_INFO } from '../constants'
// 设置登录状态
export const setLogin = () => {
return {
type: SET_IS_LOGIN
}
}
// 设置登录信息
export const setUserInfo = ()=>{
return {
type: SET_LOGIN_INFO
}
}
如何使用? 登录弹窗中使用:
/**
* 全局登录弹窗
*/
import React, { useEffect } from 'react';
import { Button, View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import styles from './style.module.scss';
import { useSelector,useDispatch } from 'react-redux'
import { SET_IS_LOGIN,SET_LOGIN_INFO } from '@redux/constants'
import {setStorageSync} from '@utils/storage'
interface Iprops {
setLoginHide:()=>void // 关闭弹窗
}
const LoginModal: React.FC<Iprops> = props => {
const {setLoginHide} = props
// 使用 useDispatch
const dispatch = useDispatch()
// 检查用户微信是否授权登录
const getUserfile = () => {
Taro.getUserProfile({
// 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
desc: '用于个人资料',
success: (res) => {
const {avatarUrl,nickName} = res.userInfo
// 将用户信息存储在缓存中
setStorageSync('userInfo',{ avatar: avatarUrl, nickName:nickName })
setStorageSync('isLogin',true)
// 调用action设置登录状态
dispatch({ type: SET_IS_LOGIN,payload:{isLogin:true} })
// 设置redux存储头像和昵称
dispatch({ type: SET_LOGIN_INFO,payload:{
avatar: avatarUrl,
nickName: nickName
} })
// 隐藏登录弹窗
setLoginHide()
console.log('获取用户信息',res.userInfo);
},
fail: (res) => {
setStorageSync('isLogin',false)
Taro.showToast({title:'获取失败',icon:'none'})
console.log(res);
},
complete: (res) => {
console.log(res);
}
})
}
return (
<View className={styles.lgmodalbox}>
<View className={styles.box}>
<View className={styles.txt}>请点击登录</View>
<Button type="primary" onClick={getUserfile} className={styles.btn}>登录</Button>
</View>
</View>
)
}
export default LoginModal