Taro 小程序开发大型实战(三):实现微信和支付宝多端登录
2020/1/24 17:53:22
本文主要是介绍Taro 小程序开发大型实战(三):实现微信和支付宝多端登录,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
欢迎继续阅读《Taro 小程序开发大型实战》系列,前情回顾:
- 熟悉的 React,熟悉的 Hooks:我们用 React 和 Hooks 实现了一个非常简单的添加帖子的原型
- 多页面跳转和 Taro UI 组件库:我们用 Taro 自带的路由功能实现了多页面跳转,并用 Taro UI 组件库升级了应用界面
而在这一篇中,我们将实现微信和支付宝多端登录。如果你希望直接从这一篇开始,请运行以下命令:
git clone -b third-part https://github.com/tuture-dev/ultra-club.git cd ultra-club
本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个推荐+Github仓库加星❤️哦~
在正式开始之前,我们希望你已经具备以下知识:
- 基本的 React 框架知识,可参考这篇文章进行学习
- 对常用的 React Hooks (
useState
、useEffect
)有所了解,后面图雀社区将推出 “一杯茶的时间,上手 React Hooks”,敬请期待!
除此之外,你还需要下载并安装支付宝开发者工具,登录后创建自己的小程序 ID。
多端登录,群魔乱舞
与普通的 Web 应用相比,小程序能够在所在的平台实现一键登录,非常方便。这一步,我们也将实现多端登录(主要包括微信登录和支付宝登录)。之所以标题取为“群魔乱舞”,不仅受了“震惊”小编们的启发,也是因为当今各平台处理登录和鉴权的方式差异很大,很遗憾的是在 Taro 框架下我们依然需要踩很多“坑”才能真正实现“多端登录”。
准备工作
组件设计规划
这一节的代码很长,在正式开始之前我们先查看一下组件设计的规划,便于你对接下来我们要做的工作有清晰的了解。
可以看到“我的”页面整体拆分成了 Header
和 Footer
:
Header
包括LoggedMine
(个人信息),如果在未登录状态下则还有LoginButton
(普通登录按钮)、WeappLoginButton
(微信登录按钮,仅在微信小程序中出现)以及AlipayLoginButton
(支付宝登录按钮,仅在支付宝小程序中出现)Footer
则用来显示是否已登录的文字,在已登录的情况下会显示Logout
(退出登录按钮)
配置 Babel 插件
从这一步开始,我们将首次开始写异步代码。本项目将采用流行的 async/await 来编写异步逻辑,因此我们配置一下相应的 Babel 插件:
npm install babel-plugin-transform-runtime --save-dev # yarn add babel-plugin-transform-runtime -D
然后在 config/index.js
中为 config.babel.plugins
添加相应的配置如下:
const config = { // ... babel: { // ... plugins: [ // ... [ 'transform-runtime', { helpers: false, polyfill: false, regenerator: true, moduleName: 'babel-runtime', }, ], ], }, // ... } // ...
各组件的实现
实现 LoginButton
首先,我们来实现普通登录按钮 LoginButton
组件。创建 src/components/LoginButton
目录,在其中创建 index.js
,代码如下:
import Taro from '@tarojs/taro' import { AtButton } from 'taro-ui' export default function LoginButton(props) { return ( <AtButton type="primary" onClick={props.handleClick}> 普通登录 </AtButton> ) }
我们使用了 Taro UI 的 AtButton
组件,并定义了一个 handleClick
事件,后面在使用时会传入。
实现 WeappLoginButton
接着我们实现微信登录按钮 WeappLoginButton
。创建 src/components/WeappLoginButton
目录,在其中分别创建 index.js
和 index.scss
。index.js
代码如下:
import Taro, { useState } from '@tarojs/taro' import { Button } from '@tarojs/components' import './index.scss' export default function LoginButton(props) { const [isLogin, setIsLogin] = useState(false) async function onGetUserInfo(e) { setIsLogin(true) const { avatarUrl, nickName } = e.detail.userInfo await props.setLoginInfo(avatarUrl, nickName) setIsLogin(false) } return ( <Button openType="getUserInfo" onGetUserInfo={onGetUserInfo} type="primary" className="login-button" loading={isLogin} > 微信登录 </Button> ) }
可以看到,微信登录按钮和之前的普通登录按钮多了很多东西:
- 添加了
isLogin
状态,用于表示是否在等待登录中,以及修改状态的setIsLogin
函数 - 实现了
onGetUserInfo
async 函数,用于处理在用户点击登录按钮、获取到信息之后的逻辑。其中,我们将获取到的用户信息传入props
中的setLoginInfo
,从而修改整个应用的登录状态 - 添加了
openType
(微信开放能力)属性,这里我们输入的是getUserInfo
(获取用户信息),欲查看所有支持的 open-type,请查看微信开放文档对应部分 - 添加了
onGetUserInfo
这个 handler,用于编写在获取到用户信息后的处理逻辑,这里就是传入刚刚实现的onGetUserInfo
WeappLoginButton
的样式 index.scss
代码如下:
.login-button { width: 100%; margin-top: 40px; margin-bottom: 40px; }
实现 AlipayLoginButton
让我们来实现支付宝登录按钮组件。创建 src/components/AlipayLoginButton
目录,在其中分别创建 index.js
和 index.scss
。index.js
代码如下:
import Taro, { useState } from '@tarojs/taro' import { Button } from '@tarojs/components' import './index.scss' export default function LoginButton(props) { const [isLogin, setIsLogin] = useState(false) async function onGetAuthorize(res) { setIsLogin(true) try { let userInfo = await Taro.getOpenUserInfo() userInfo = JSON.parse(userInfo.response).response const { avatar, nickName } = userInfo await props.setLoginInfo(avatar, nickName) } catch (err) { console.log('onGetAuthorize ERR: ', err) } setIsLogin(false) } return ( <Button openType="getAuthorize" scope="userInfo" onGetAuthorize={onGetAuthorize} type="primary" className="login-button" loading={isLogin} > 支付宝登录 </Button> ) }
可以看到,内容与之前的微信登录按钮基本相似,但是有以下差别:
- 实现
onGetAuthorize
回调函数。与之前微信的回调函数不同,这里我们要调用Taro.getOpenUserInfo
手动获取用户基础信息(实际上调用的是支付宝开放平台 my.getOpenUserInfo) Button
组件的openType
(支付宝开放能力)设置成getAuthorize
(小程序授权)- 在设定开放能力为
getAuthorize
时,需要添加scope
属性为userInfo
,让用户可以授权小程序获取支付宝会员的基础信息(另一个有效值是phoneNumber
,用于获取手机号码) - 传入
onGetAuthorize
回调函数
提示
关于支付宝小程序登录按钮的细节,可以查看官方文档。
样式文件 index.scss
的代码如下:
.login-button { width: 100%; margin-top: 40px; }
实现 LoggedMine
接着我们实现已经登录状态下的 LoggedMine
组件。创建 src/components/LoggedMine
目录,在其中分别创建 index.jsx
和 index.scss
。index.jsx
代码如下:
import Taro from '@tarojs/taro' import { View, Image } from '@tarojs/components' import PropTypes from 'prop-types' import './index.scss' import avatar from '../../images/avatar.png' export default function LoggedMine(props) { const { userInfo = {} } = props function onImageClick() { Taro.previewImage({ urls: [userInfo.avatar], }) } return ( <View className="logged-mine"> <Image src={userInfo.avatar ? userInfo.avatar : avatar} className="mine-avatar" onClick={onImageClick} /> <View className="mine-nickName"> {userInfo.nickName ? userInfo.nickName : '图雀酱'} </View> <View className="mine-username">{userInfo.username}</View> </View> ) } LoggedMine.propTypes = { avatar: PropTypes.string, nickName: PropTypes.string, username: PropTypes.string, }
这里我们添加了点击头像可以预览的功能,可以通过 Taro.previewImage
函数实现。
LoggedMine
组件的样式文件如下:
.logged-mine { display: flex; flex-direction: column; align-items: center; } .mine-avatar { width: 200px; height: 200px; border-radius: 50%; } .mine-nickName { font-size: 40; margin-top: 20px; } .mine-username { font-size: 32px; margin-top: 16px; color: #777; }
实现 Header 组件
在所有的“小零件”全部实现后,我们就实现整个登录界面的 Header
部分。创建 src/components/Header
目录,在其中分别创建 index.js
和 index.scss
。index.js
代码如下:
import Taro from '@tarojs/taro' import { View } from '@tarojs/components' import { AtMessage } from 'taro-ui' import LoggedMine from '../LoggedMine' import LoginButton from '../LoginButton' import WeappLoginButton from '../WeappLoginButton' import AlipayLoginButton from '../AlipayLoginButton' import './index.scss' export default function Header(props) { const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY return ( <View className="user-box"> <AtMessage /> <LoggedMine userInfo={props.userInfo} /> {!props.isLogged && ( <View className="login-button-box"> <LoginButton handleClick={props.handleClick} /> {isWeapp && <WeappLoginButton setLoginInfo={props.setLoginInfo} />} {isAlipay && <AlipayLoginButton setLoginInfo={props.setLoginInfo} />} </View> )} </View> ) }
可以看到,我们根据 Taro.ENV_TYPE
查询当前所在的平台(微信、支付宝或其他),然后确定是否显示相应平台的登录按钮。
提示
你也许发现了,
setLoginInfo
还是要等待父组件的传入。虽然 Hooks 简化了状态的定义和更新方式,但是却没有简化跨组件修改状态的逻辑。在接下来的一步,我们将用 Redux 进行简化。
Header
组件的样式代码如下:
.user-box { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; } .login-button-box { margin-top: 60px; width: 100%; }
实现 LoginForm
接着我们实现用于普通登录的 LoginForm
组件。由于本系列教程的目标是讲解 Taro,因此这里简化了注册/登录的流程,用户可以直接输入用户名并上传头像进行注册/登录,无需设置密码和其他验证过程。创建 src/components/LoginForm
目录,在其中分别创建 index.jsx
和 index.scss
。index.jsx
代码如下:
import Taro, { useState } from '@tarojs/taro' import { View, Form } from '@tarojs/components' import { AtButton, AtImagePicker } from 'taro-ui' import './index.scss' export default function LoginForm(props) { const [showAddBtn, setShowAddBtn] = useState(true) function onChange(files) { if (files.length > 0) { setShowAddBtn(false) } props.handleFilesSelect(files) } function onImageClick() { Taro.previewImage({ urls: [props.files[0].url], }) } return ( <View className="post-form"> <Form onSubmit={props.handleSubmit}> <View className="login-box"> <View className="avatar-selector"> <AtImagePicker length={1} mode="scaleToFill" count={1} files={props.files} showAddBtn={showAddBtn} onImageClick={onImageClick} onChange={onChange} /> </View> <Input className="input-nickName" type="text" placeholder="点击输入昵称" value={props.formNickName} onInput={props.handleNickNameInput} /> <AtButton formType="submit" type="primary"> 登录 </AtButton> </View> </Form> </View> ) }
这里我们使用 Taro UI 的 ImagePicker 图片选择器组件,让用户能够选择图片进行上传。AtImagePicker
最重要的属性就是 onChange
回调函数,这里我们通过父组件传进来的 handleFilesSelect
函数来搞定。
LoginForm
组件的样式代码如下:
.post-form { margin: 0 30px; padding: 30px; } .input-nickName { border: 1px solid #eee; padding: 10px; font-size: medium; width: 100%; margin-top: 40px; margin-bottom: 40px; } .avatar-selector { width: 200px; margin: 0 auto; }
实现 Logout
在登录之后,我们还需要退出登录的按钮。创建 src/components/Logout/index.js
文件,代码如下:
import Taro from '@tarojs/taro' import { AtButton } from 'taro-ui' export default function LoginButton(props) { return ( <AtButton type="secondary" full loading={props.loading} onClick={props.handleLogout} > 退出登录 </AtButton> ) }
实现 Footer
所有的子组件全部实现之后,我们就来实现 Footer
组件。创建 src/components/Footer
目录,在其中分别创建 index.jsx
和 index.scss
。index.jsx
代码如下:
import Taro, { useState } from '@tarojs/taro' import { View } from '@tarojs/components' import { AtFloatLayout } from 'taro-ui' import Logout from '../Logout' import LoginForm from '../LoginForm' import './index.scss' export default function Footer(props) { // Login Form 登录数据 const [formNickName, setFormNickName] = useState('') const [files, setFiles] = useState([]) async function handleSubmit(e) { e.preventDefault() // 鉴权数据 if (!formNickName || !files.length) { Taro.atMessage({ type: 'error', message: '您还有内容没有填写!', }) return } // 提示登录成功 Taro.atMessage({ type: 'success', message: '恭喜您,登录成功!', }) // 缓存在 storage 里面 const userInfo = { avatar: files[0].url, nickName: formNickName } await props.handleSubmit(userInfo) // 清空表单状态 setFiles([]) setFormNickName('') } return ( <View className="mine-footer"> {props.isLogged && ( <Logout loading={props.isLogout} handleLogout={props.handleLogout} /> )} <View className="tuture-motto"> {props.isLogged ? 'From 图雀社区 with Love ' : '您还未登录'} </View> <AtFloatLayout isOpened={props.isOpened} title="登录" onClose={() => props.handleSetIsOpened(false)} > <LoginForm formNickName={formNickName} files={files} handleSubmit={e => handleSubmit(e)} handleNickNameInput={e => setFormNickName(e.target.value)} handleFilesSelect={files => setFiles(files)} /> </AtFloatLayout> </View> ) }
Footer
组件的样式文件代码如下:
.mine-footer { font-size: 28px; color: #777; margin-bottom: 20px; } .tuture-motto { margin-top: 40px; text-align: center; }
所有小组件都搞定之后,我们在 src/components
中只需暴露出 Header
和 Footer
。修改 src/components/index.jsx
,代码如下:
import PostCard from './PostCard' import PostForm from './PostForm' import Footer from './Footer' import Header from './Header' export { PostCard, PostForm, Footer, Header }
更新“我的”页面
是时候用上写好的 Header
和 Footer
组件了,但在此之前,我们先来讲一下我们需要用到的 useEffect
Hooks。
useEffect Hooks
useEffect
Hooks 是用来替代原 React 的生命周期钩子函数的,我们可以在里面发起一些 “副作用” 操作,比如异步获取后端数据、设置定时器或是进行 DOM 操作等:
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 和 componentDidMount 以及 componentDidUpdate 类似: useEffect(() => { // 使用浏览器 API 更新 document 的标题 document.title = `你点击了 ${count} 次`; }); return ( <div> <p>你点击了 {count} 次</p> <button onClick={() => setCount(count + 1)}> 点我 </button> </div> ); }
上面的对 document
标题的修改是具有副作用的操作,在之前的 React 应用中,我们通常会这么写:
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `你点击了 ${this.state.count} 次`; } componentDidUpdate() { document.title = `你点击了 ${this.state.count} 次`; } render() { return ( <div> <p>你点击了 {this.state.count} 次</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> 点我 </button> </div> ); } }
如果你想了解 useEffect
具体的详情,可以去查看 React 的官方文档。
做的好!了解了 useEffect
Hooks 的概念之后,我们马上来更新“我的”页面组件 src/pages/mine/mine.jsx
,代码如下:
import Taro, { useState, useEffect } from '@tarojs/taro' import { View } from '@tarojs/components' import { Header, Footer } from '../../components' import './mine.scss' export default function Mine() { const [nickName, setNickName] = useState('') const [avatar, setAvatar] = useState('') const [isOpened, setIsOpened] = useState(false) const [isLogout, setIsLogout] = useState(false) // 双取反来构造字符串对应的布尔值,用于标志此时是否用户已经登录 const isLogged = !!nickName useEffect(() => { async function getStorage() { try { const { data } = await Taro.getStorage({ key: 'userInfo' }) const { nickName, avatar } = data setAvatar(avatar) setNickName(nickName) } catch (err) { console.log('getStorage ERR: ', err) } } getStorage() }) async function setLoginInfo(avatar, nickName) { setAvatar(avatar) setNickName(nickName) try { await Taro.setStorage({ key: 'userInfo', data: { avatar, nickName }, }) } catch (err) { console.log('setStorage ERR: ', err) } } async function handleLogout() { setIsLogout(true) try { await Taro.removeStorage({ key: 'userInfo' }) setAvatar('') setNickName('') } catch (err) { console.log('removeStorage ERR: ', err) } setIsLogout(false) } function handleSetIsOpened(isOpened) { setIsOpened(isOpened) } function handleClick() { handleSetIsOpened(true) } async function handleSubmit(userInfo) { // 缓存在 storage 里面 await Taro.setStorage({ key: 'userInfo', data: userInfo }) // 设置本地信息 setAvatar(userInfo.avatar) setNickName(userInfo.nickName) // 关闭弹出层 setIsOpened(false) } return ( <View className="mine"> <Header isLogged={isLogged} userInfo={{ avatar, nickName }} handleClick={handleClick} setLoginInfo={setLoginInfo} /> <Footer isLogged={isLogged} isOpened={isOpened} isLogout={isLogout} handleLogout={handleLogout} handleSetIsOpened={handleSetIsOpened} handleSubmit={handleSubmit} /> </View> ) } Mine.config = { navigationBarTitleText: '我的', }
可以看到,我们做了这么些工作:
- 使用
useState
创建了四个状态:用户有关信息(nickName
和avatar
),登录弹出层是否打开(isOpened
),是否登录成功(isLogged
),以及相应的更新函数 - 通过
useEffect
Hook 尝试从本地缓存中获取用户信息(Taro.getStorage),并用来更新nickName
和avatar
状态 - 实现了久违的
setLoginInfo
函数,其中我们不仅更新了nickName
和avatar
的状态,还把用户数据存入本地缓存(Taro.getStorage),确保下次打开时保持登录状态 - 实现了同样久违的
handleLogout
函数,其中不仅更新了相关状态,还去掉了本地缓存中的数据(Taro.removeStorage) - 实现了用于处理普通登录的
handleSubmit
函数,内容基本上与setLoginInfo
一致 - 在返回 JSX 代码时渲染
Header
和Footer
组件,传入相应的状态和回调函数
调整 Mine
组件的样式 src/pages/mine/mine.scss
代码如下:
.mine { margin: 30px; height: 90vh; padding: 40px 40px 0; display: flex; flex-direction: column; justify-content: space-between; }
最后在 src/app.scss
中引入相应的 Taro UI 组件的样式:
@import './custom-theme.scss'; @import '~taro-ui/dist/style/components/button.scss'; @import '~taro-ui/dist/style/components/fab.scss'; @import '~taro-ui/dist/style/components/icon.scss'; @import '~taro-ui/dist/style/components/float-layout.scss'; @import '~taro-ui/dist/style/components/textarea.scss'; @import '~taro-ui/dist/style/components/message.scss'; @import '~taro-ui/dist/style/components/avatar.scss'; @import '~taro-ui/dist/style/components/image-picker.scss'; @import '~taro-ui/dist/style/components/icon.scss';
查看效果
终于到了神圣的验收环节。首先是普通登录:
而微信和支付宝登录,点击之后就会直接以登录开发者工具所用的帐号登录了。下面贴出我微信和支付宝登录后的界面展示:
登录后点击下方的“退出登录”按钮,就会将当前登录帐户注销哦。
至此,《Taro 多端小程序开发大型实战》第三篇也就结束啦。在接下来的第四篇中,我们将逐步用 Redux 来重构业务数据流,让我们现在略显臃肿的状态管理变得清晰可控。
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。
本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~
这篇关于Taro 小程序开发大型实战(三):实现微信和支付宝多端登录的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-07-03微信小程序分享跳转-icode9专业技术文章分享
- 2024-07-03小程序微信支付提示缺少total_fee-icode9专业技术文章分享
- 2024-07-03微信小程序携带参数分享 前后端交互流程-icode9专业技术文章分享
- 2024-07-02微信小程序中禁止左右滑动切换页面-icode9专业技术文章分享
- 2024-06-30实现小程序内部扫码签到和微信扫一扫签到-icode9专业技术文章分享
- 2024-06-30微信内调用扫码扫描小程序码如何解析链接-icode9专业技术文章分享
- 2024-06-30小程序码链接加密了如何解-icode9专业技术文章分享
- 2024-05-302024年最新版云开发cms开通步骤,开始开发微信小程序前的准备工作,认真看完奥!
- 2024-03-30微信小程序的网络设置,及网络请求:wx.request(OBJECT)
- 2024-01-22基于taro搭建小程序多项目框架