Welcome to continue reading the Taro Small program Development large-scale Practical series, previously on:
- Familiar React, familiar Hooks: We implemented a very simple prototype for adding posts using React and Hooks
- Multi-page hops and the Taro UI Component Library: We implemented multi-page hops with the built-in Taro routing function, and upgraded the application interface with the Taro UI component library
And in this article, we will realize wechat and Alipay multi-terminal login. If you wish to start with this post directly, run the following command:
git clone -b third-part https://github.com/tuture-dev/ultra-club.git
cd ultra-club
Copy the code
The source code for this article is available on Github. If you think we did a good job, please give ❤️ a thumbs up +Github repository + star ❤️
Before we start, we hope you have the following knowledge:
- Learn about the React framework in this article
- React-hooks (
useState
,useEffect
React Hooks!!
In addition, you need to download and installAlipay developer toolsLogin and create your own applets ID.
Multi – terminal login, the mob dance
Compared with ordinary Web applications, applets can realize one-click login on the platform where they are located, which is very convenient. In this step, we will also implement multi-terminal login (mainly including wechat login and Alipay login). It is regrettable that we still need to step through many “holes” under the Taro framework to truly implement “multi-login”. The first visit to Taro is based on the following information:
The preparatory work
Component design planning
The code in this section is quite long, so before we get started let’s take a look at the layout of the component design so that you have a clear idea of what we’re going to do next.
You can see the “my” page split into Header and Footer:
Header
includingLoggedMine
(Personal information), or if not logged inLoginButton
(Common login button),WeappLoginButton
(wechat login button, which only appears in wechat mini program) andAlipayLoginButton
(Alipay login button only appears in alipay mini program)Footer
The text used to indicate whether you are logged in will be displayed in the case of logged inLogout
(Log out button)
Configure the Babel plug-in
From this step, we’ll start writing asynchronous code for the first time. This project will use the popular async/await to write asynchronous logic, so let’s configure the corresponding Babel plug-in:
npm install babel-plugin-transform-runtime --save-dev
# yarn add babel-plugin-transform-runtime -D
Copy the code
Then add the following configuration to config.babel.plugins in config/index.js:
const config = {
// ...
babel: {
// ...
plugins: [
// ...
[
'transform-runtime',
{
helpers: false.polyfill: false.regenerator: true.moduleName: 'babel-runtime',},],],},// ...
}
// ...
Copy the code
Implementation of components
Implement LoginButton
First, let’s implement the normal LoginButton LoginButton component. Create the SRC/components/LoginButton directory, create index. The js, HTML code is as follows:
import Taro from '@tarojs/taro'
import { AtButton } from 'taro-ui'
export default function LoginButton(props) {
return (
<AtButton type="primary" onClick={props.handleClick}>Normal login</AtButton>)}Copy the code
We used the Taro UI AtButton component and defined a handleClick event that will be passed in later.
Implement WeappLoginButton
Then we implement the wechat login button WeappLoginButton. Create SRC/components/WeappLoginButton directory, in which create index respectively. Js and index SCSS. The index.js code is as follows:
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}
>WeChat login</Button>)}Copy the code
As you can see, the wechat login button has a lot more things than the normal login button before:
- added
isLogin
Status, used to indicate whether the login is pending and to modify the statussetIsLogin
function - To achieve the
onGetUserInfo
Async functions that process the logic after the user clicks the login button and gets the information. Where, we pass in the user information we have obtainedprops
In thesetLoginInfo
To change the login status of the entire application - added
openType
(wechat open capability) attribute, here we input isgetUserInfo
To view all supported open-types, seeWechat open the corresponding part of the document - added
onGetUserInfo
This handler, which is used to write the processing logic after obtaining user information, is just implemented hereonGetUserInfo
WeappLoginButton style index.scSS code is as follows:
.login-button {
width: 100%;
margin-top: 40px;
margin-bottom: 40px;
}
Copy the code
Implement AlipayLoginButton
Let’s implement the Alipay login button component. Create SRC/components/AlipayLoginButton directory, in which create index respectively. Js and index SCSS. The index.js code is as follows:
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}
>Alipay login</Button>)}Copy the code
It can be seen that the content is basically similar to the previous wechat login button, but there are the following differences:
- implementation
onGetAuthorize
Callback function. Different from the wechat callback function before, here we will callTaro.getOpenUserInfo
Manually obtain user base information (in fact, the call is alipay open platformmy.getOpenUserInfo) Button
The component’sopenType
(Alipay open capacity) set togetAuthorize
(Small program authorization)- In setting open capability as
getAuthorize
“, you need to addscope
Properties foruserInfo
, so that users can authorize the mini program to obtain basic information of Alipay members (another valid value isphoneNumber
To get a mobile phone number) - The incoming
onGetAuthorize
The callback function
prompt
Details about the Alipay mini program login button can be found in the official documentation.
The code for the style file index. SCSS is as follows:
.login-button {
width: 100%;
margin-top: 40px;
}
Copy the code
Implement LoggedMine
Next we implement the LoggedMine component in the logged in state. Create the SRC/components/LoggedMine directory, in which create index respectively. The JSX and index SCSS. The index.jsx code is as follows:
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 ? NickName: 'booty '}</View>
<View className="mine-username">{userInfo.username}</View>
</View>
)
}
LoggedMine.propTypes = {
avatar: PropTypes.string,
nickName: PropTypes.string,
username: PropTypes.string,
}
Copy the code
Here we have added the function of clicking the avatar to preview, which can be achieved by Taro. PreviewImage function.
The LoggedMine component has the following style file:
.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;
}
Copy the code
Implementing the Header component
After all the “widgets” are implemented, we implement the Header section of the entire login screen. Create the SRC /components/Header directory and create index.js and index.scss, respectively. The index.js code is as follows:
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> ) }Copy the code
As you can see, we query the current platform (wechat, Alipay or others) based on Taro.ENV_TYPE and then decide whether to display the login button for the corresponding platform.
prompt
As you might notice, setLoginInfo still waits for the parent component to pass in. While Hooks simplify how state is defined and updated, they do not simplify the logic for changing state across components. In the next step, we will simplify with Redux.
The Header component has the following style code:
.user-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.login-button-box {
margin-top: 60px;
width: 100%;
}
Copy the code
Implement LoginForm
Next we implement the LoginForm component for general login. Since the goal of this series of tutorials is to explain Taro, the registration/login process has been simplified by allowing users to directly enter a user name and upload an avatar to register/log in without having to set up a password or other authentication procedures. Create the SRC/Components /LoginForm directory and create index.jsx and index.scss, respectively. The index.jsx code is as follows:
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 = "click enter a nickname" value = {props. FormNickName} the onInput = {props. HandleNickNameInput} / > < AtButton </AtButton> </View> </Form> </View>Copy the code
Here we use the ImagePicker ImagePicker component of Taro UI to allow users to select images to upload. The most important attribute of AtImagePicker is the onChange callback, which is handled by the handleFilesSelect function passed in by the parent component.
The style code for the LoginForm component is as follows:
.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;
}
Copy the code
To realize the Logout
After logging in, we also need a button to log out. Create a SRC/components/Logout/index. Js file, the code is as follows:
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}
>Log out</AtButton>)}Copy the code
Realize the Footer
After all the subcomponents are implemented, we’ll implement the Footer component. Create the SRC/Components /Footer directory and create index.jsx and index.scss, respectively. The index.jsx code is as follows:
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 Indicates the Login data
const [formNickName, setFormNickName] = useState(' ')
const [files, setFiles] = useState([])
async function handleSubmit(e) {
e.preventDefault()
// Authentication data
if(! formNickName || ! files.length) { Taro.atMessage({type: 'error'.message: 'You have something to fill in! ',})return
}
// A message indicating successful login is displayed
Taro.atMessage({
type: 'success'.message: 'Congratulations, login is successful! ',})// Cache in storage
const userInfo = { avatar: files[0].url, nickName: formNickName }
await props.handleSubmit(userInfo)
// Clear the form state
setFiles([])
setFormNickName(' ')}return( <View className="mine-footer"> {props.isLogged && ( <Logout loading={props.isLogout} handleLogout={props.handleLogout} /> )} <View className="tuture-motto"> {props.isLogged ? 'From the Tootlark community with Love ❤' : } </View> <AtFloatLayout isOpened={props. IsOpened} title=" Props "onClose={() => handleSetIsOpened(false)} > <LoginForm formNickName={formNickName} files={files} handleSubmit={e => handleSubmit(e)} handleNickNameInput={e => setFormNickName(e.target.value)} handleFilesSelect={files => setFiles(files)} /> </AtFloatLayout> </View> ) }Copy the code
The style file code for the Footer component is as follows:
.mine-footer {
font-size: 28px;
color: # 777;
margin-bottom: 20px;
}
.tuture-motto {
margin-top: 40px;
text-align: center;
}
Copy the code
After all the widgets are done, we just need to expose the Header and Footer in SRC/Components. 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 }
Copy the code
Update my page
It’s time to use the written Header and Footer components, but first let’s talk about useEffect Hooks that we need to use.
useEffect Hooks
UseEffect Hooks replace the React lifecycle hook function, which allows you to do “side effects” such as asynchronously retrieving back-end data, setting timers, or DOM operations:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount and componentDidUpdate:
useEffect((a)= > {
// Update the document title with the browser API
document.title = 'You clicked${count}Time `;
});
return (
<div>
<p>You hit {count} times</p>
<button onClick={()= >SetCount (count + 1)}> click me</button>
</div>
);
}
Copy the code
The above modification to the document header is a side effect. In the React application, we used to write:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = 'You clickedThe ${this.state.count}Time `;
}
componentDidUpdate() {
document.title = 'You clickedThe ${this.state.count}Time `;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={()= >This.setstate ({count: this.state.count + 1})}> tap me</button>
</div>); }}Copy the code
If you want to learn more about useEffect, check out the React documentation.
Well done! Now that you know the concept of useEffect Hooks, update the “my” page component SRC /pages/mine/mine.jsx as follows:
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)
// To construct a Boolean value corresponding to the string, which is used to indicate whether the user is logged in
constisLogged = !! nickName useEffect((a)= > {
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) {
// Cache in storage
await Taro.setStorage({ key: 'userInfo'.data: userInfo })
// Set local information
setAvatar(userInfo.avatar)
setNickName(userInfo.nickName)
// Close the pop-up layer
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: 'my ',}Copy the code
Here’s what we did:
- use
useState
Four states are created: User Information (nickName
和avatar
), whether the login pop-up layer is open (isOpened
), whether the login is successful (isLogged
), and the corresponding update function - through
useEffect
Hook attempts to retrieve user information from the local cache (Taro.getStorage) and used to updatenickName
和avatar
state - I realized what I had not seen for a long time
setLoginInfo
Function, where we not only updated itnickName
和avatar
Also stores user data in a local cache (Taro.getStorage) to ensure that you remain logged in the next time you open it - Achieved the same long-lost
handleLogout
Function, which not only updates the associated state, but also removes data from the local cache (Taro.removeStorage) - Implemented for handling normal logins
handleSubmit
Function, content is basically the same assetLoginInfo
consistent - Render when JSX code is returned
Header
和Footer
Component, passing in the corresponding state and callback functions
Adjust the style of the Mine component SRC /pages/ Mine /mine.scss code is as follows:
.mine {
margin: 30px;
height: 90vh;
padding: 40px 40px 0;
display: flex;
flex-direction: column;
justify-content: space-between;
}
Copy the code
Finally, introduce the corresponding Taro UI component styles in SRC/app.scSS:
@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';
Copy the code
See the effect
Finally came the sacred acceptance link. The first is normal login:
While wechat and Alipay login, after clicking will be directly logged in to the developer tool used by the account. The following is the interface display of my wechat and Alipay login:
After login, click the “log out” button below, and the current account will be logged out.
At this point, “Taro multi-terminal small program development large combat” third also ended. In the next installment, Part 4, we’ll step up to refactoring business data flows with Redux to make our now somewhat bloated state management clear and manageable.
Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.
The source code for this article is available on Github. If you think we did a good job, please give ❤️ a thumbs up +Github repository + star ❤️