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,useEffectReact 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:

  • HeaderincludingLoggedMine(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)
  • FooterThe 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:

  • addedisLoginStatus, used to indicate whether the login is pending and to modify the statussetIsLoginfunction
  • To achieve theonGetUserInfoAsync 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 obtainedpropsIn thesetLoginInfoTo change the login status of the entire application
  • addedopenType(wechat open capability) attribute, here we input isgetUserInfoTo view all supported open-types, seeWechat open the corresponding part of the document
  • addedonGetUserInfoThis 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:

  • implementationonGetAuthorizeCallback function. Different from the wechat callback function before, here we will callTaro.getOpenUserInfoManually obtain user base information (in fact, the call is alipay open platformmy.getOpenUserInfo)
  • ButtonThe component’sopenType(Alipay open capacity) set togetAuthorize(Small program authorization)
  • In setting open capability asgetAuthorize“, you need to addscopeProperties foruserInfo, so that users can authorize the mini program to obtain basic information of Alipay members (another valid value isphoneNumberTo get a mobile phone number)
  • The incomingonGetAuthorizeThe 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:

  • useuseStateFour states are created: User Information (nickNameavatar), whether the login pop-up layer is open (isOpened), whether the login is successful (isLogged), and the corresponding update function
  • throughuseEffectHook attempts to retrieve user information from the local cache (Taro.getStorage) and used to updatenickNameavatarstate
  • I realized what I had not seen for a long timesetLoginInfoFunction, where we not only updated itnickNameavatarAlso 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-losthandleLogoutFunction, which not only updates the associated state, but also removes data from the local cache (Taro.removeStorage)
  • Implemented for handling normal loginshandleSubmitFunction, content is basically the same assetLoginInfoconsistent
  • Render when JSX code is returnedHeaderFooterComponent, 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 ❤️