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
  • Realize wechat and Alipay multi-terminal login: realize wechat, Alipay and ordinary login and logout login
  • Redux: Use the Hooks version of Redux to refactor your application’s state management
  • Large Application State Management using Redux from Hooks (Part 1)Redux is implemented using the Hooks versionuserState management refactoring of logic
  • Large application State Management with Redux from Hooks (Part 2)Redux is implemented using the Hooks versionpostState management refactoring of logic

After if you come to here, you will find our content is pure front end (small end) logic, a complete online small applications should also have the back-end, in this article, we will use the WeChat applet cloud as our background, then we will introduce the story – saga to help redux elegant processing asynchronous processes, The final results of this paper are as follows:

If you’re not familiar with Redux, we recommend reading our Redux Tutorial series:

  • Redux Redux Redux Redux Redux Redux Redux Redux Redux Redux
  • Redux: Strike while the iron is hot and completely refactor
  • Redux Education Package Association (3) : To perform their respective duties and regain the original purpose

If you want to start directly from this step, run the following command:

git clone -b miniprogram-start 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 ❤️

In order to make persistent storage of data and efficient query, we need to store the data in the database. In order to realize the convenient development experience of the small program side, a large number of small program Serverless services rise, and wechat small program cloud is born for the rapid development of wechat small program. In this article, we will use wechat applet cloud as our back end and explain how to introduce and implement Redux asynchronous workflow to achieve state management of applet side access to the applet cloud.

Wechat small program cloud at first taste

In the previous code, we use the data stored in the Storage to complete data persistence inside, so that we can solve the problem of small data Storage and query, once the data quantity big, the query and store to need to rely on specialized database to solve, we can generally addressed by way of the back-end and database construction, However, when small programs are becoming more and more popular at the same time, a model called Serverless is proposed and gradually popular. In a popular sense, it is summarized as “no back end”, that is, the back end is handed over to cloud service manufacturers (Ali Cloud, Tencent Cloud, Jingdong Cloud, etc.). Developers only need to focus on front-end logic and fast delivery of functions.

The common applets Serverless service contains three main functions:

  • Database: Data is usually stored in JSON data format and can be stored in the cloud database.
  • Storage: Supports storage of user-generated content such as texts and images. You can obtain resource links for use.
  • Cloud functions: you can use Node.js for development, write the corresponding back-end logic, and upload the written code to the cloud, and then use API in front of the small program to call.

About the detailed description of the small program Serverless, here is a recommended article, interested students can understand in detail: What is the small program Serverless?

In this section, we use wechat small program cloud as our “back end”, wechat small program cloud and small program account are bound together, a small program account can open a small program cloud space, next, we will explain in detail how to open the small program cloud.

Open the small program cloud

  1. First of all, make sure you register the wechat public platform account: registered address.
  2. After login, go to the menu bar Development > Development SettingsAppIDIt should be an 18-bit string.
  3. useWechat developer toolsOpen up ourultra-clubProject folder, and then choose Settings > Project Settings from the menu bar of wechat Developer Tools to open the Settings bar:

4. Find the basic information of the setting bar and change it to the AppID as follows:

5. When the AppID is set, the “Cloud Development” button in our developer tools should become clickable. Find the “Cloud Development” button in the upper left corner and click it, similar to the picture below:

4. Click “Cloud development” button and the confirmation box will pop up. Click “Agree” and you will enter the small program cloud development console:

The first thing we’ll see is the Cloud Development Console’s “Operations Analytics” interface, which is used to visualize the use of various cloud development resources, something we won’t cover in this tutorial. Let’s focus on the part highlighted in red:

  • The number 1 is our cloud database, which is a JSON database, which stores the data we need during development.
  • Number 2 is storage, which means we can upload some text, pictures, audio/video, and then return us links to access those resources.
  • Number 3 is the cloud function, which is where we can manage some back-end Node.js logic we wrote, which runs in the cloud, and which we can call from the applets through the API.
  • No. 4 is the identifier that represents our cloud environment this time, which can be used to mark the cloud environment that is called at this time when the API of the small program side calls cloud development resources.

In this tutorial, we will use the database and cloud functions mentioned above.

Create a database table

Introduce the interface of the small program cloud, we immediately start to practice, to create the database table we need, because our front-end logic is mainly divided into user and POST two types of logic, so we create two tables in the database:

Here we specifically to explain the meaning of the database operation interface:

  • As you can see, click the second button in the upper left corner of the Cloud Development Console, and then click the “+” button marked red 1 to create two collectionsuserpost, so we have created our database table.
  • A number of 2 means we can select a collection and right-click to delete it.
  • The number 3 indicates that we can add records to a collection, and since it’s a JSON database, each record in the collection can be different.
  • The serial number 4 indicates that we can select a record and right click to delete it
  • The ordinal number 5 indicates that we can add fields to individual records
  • The number 6 indicates that a single record can be selected for deletion/modification
  • The serial number 7 indicates that we can query a particular record in the collection

createpostrecord

Here we add a default POST record, which represents the default data of our previous applet. This data record contains information about post:

  • _id: Unique identifier of the data
  • titleArticle title
  • content: Content of the article
  • user: The user who published this article, we save the full information of the user directly for convenience, general best practice is to save this user_idProperties, and then in the querypostFetch the user’s_idProperty, and look it upuserGet complete information about the user.
  • updatedAt: Indicates the last update time of this record
  • createdAt: Indicates the creation time of the record

createuserrecord

As mentioned above, we have saved the author’s information in this post record, so of course we need to create a new user set with the following information:

As you can see, we have added a user record with the following fields:

  • _id: The user is inuserA unique identifier in a collection
  • avatar: Profile picture address of the user
  • nickName: the nickname for this user, which we will use to log in
  • createdAt: The time when the record was created
  • updatedAt: Time when this record was last updated

Initialize the applet cloud environment on the applet side

After opening the small program cloud, we also need to initialize the small program cloud environment in the small program front-end code, so as to call the API of the small program in the small program front.

Open the SRC /index/index.jsx file and add the following code:

import Taro, { useEffect } from '@tarojs/taro'

/ /... The rest of the code is consistent

export default function Index() {
  / /... The rest of the code is consistent
  useEffect((a)= > {
    const WeappEnv = Taro.getEnv() === Taro.ENV_TYPE.WEAPP

    if (WeappEnv) {
      Taro.cloud.init()
    }

  / /... The rest of the code is consistent
  return (
    <View className="index">.</View>)}Copy the code

As can be seen, we have added the acquisition and judgment of the Micro-program environment. When the current environment is the micro-program environment, we need to call taron.cloud.init () to initialize the micro-program cloud environment

summary

So far, we have explained how to open the applet cloud, and then the applet cloud console interface. At the same time, we have explained the database function interface that we will use, where we have created two tables (sets) for our application: POST and User, and initialized a record each.

Ok, now that we have the applets cloud ready, we are ready to plug it into our application, but before that, since we are going to plug into the applets cloud, we are going to have to make asynchronous requests, which requires us to understand the asynchronous processing flow of Redux. In the next section, We will use the Redux-Saga middleware to simplify the process of redux handling asynchrony.

Redux asynchronous workflow resolution

Let’s take a look at Redux’s data flow diagram:

The grey path in the figure above is the Redux data flow graph that we have been using. It is the Redux synchronous data flow graph:

  • viewdispatch(syncAction)A synchronous action to updatestoreThe data in the
  • reducerUpdate in response to actionstorestate
  • connectPass the updated status toview
  • viewReceive new data for re-rendering

Pay attention to

For those of you who are not familiar with Redux, take a look at the Tuq Redux Tutorial series.

Now we are going to go small program cloud initiate the request, the request is an asynchronous request, it is not immediately get a response, so we need an intermediate state (in this case we use the Saga) deal with the asynchronous request and receive data back and forth, and then execute and synchronous request before a similar path, the green part of the above for us + remaining gray part, So the asynchronous workflow looks like this:

  • viewdispatch(asyncAction)An asynchronous action to get the data from the back end (in this case, the applets cloud)
  • sagaProcess the asynchronous action and wait for the data response
  • sagaGet the data for the response,dispatch(syncAction)A synchronized action to update the store state
  • reducerUpdate in response to actionstorestate
  • connectPass the updated status toview
  • viewReceive new data for re-rendering

Pay attention to

The Tuq community will publish a tutorial on the Redux asynchronous workflow in the future. This tutorial will not go into the mechanics of the entire asynchronous workflow, but how to integrate the asynchronous workflow. Stay tuned ✌️~

Actual Redux asynchronous workflow

The installation

We used the redux-Saga middleware to take over the processing of the asynchronous request part of the Redux asynchronous workflow by first installing the Redux-Saga package in the project root directory:

$ npm install redux-saga
Copy the code

After installation, our package.json should look like this:

{ "dependencies": { ... Redux-saga: "^1.1.3", "taro-ui": "^2.2.4"},}Copy the code

Redux-saga is redux’s middleware that handles asynchronous processes. What is Saga? Saga is defined as a “Long Lived Transaction” (LLT). He was the brainchild of HECTOR Garcia-Molina, a Professor at Princeton University, in a 1987 paper on distributed databases.

A saga is officially likened to a single thread in an application that handles its own side effects, which in JavaScript are asynchronous network requests, local reads of localStorage/ cookies, and other external operations.

configurationredux-sagaThe middleware

After installing redux-saga, we need to configure redux-saga to use it. Open the SRC /store/index.js file and make the following changes:

import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import createSagaMiddleware from 'redux-saga'

import rootReducer from '.. /reducers'
import rootSaga from '.. /sagas'

const sagaMiddleware = createSagaMiddleware()
const middlewares = [sagaMiddleware, createLogger()]

export default function configStore() {
  conststore = createStore(rootReducer, applyMiddleware(... middlewares)) sagaMiddleware.run(rootSaga)return store
}
Copy the code

As you can see, our file above has made the following four changes:

  • So first we derived itcreateSagaMiddleware
  • And then we go fromsrc/store/sagasOne is exported under the folderrootSagaIt combines all of themsagaFiles, this is kind of a combinationreducercombineReducers, we will write these in subsequent stepssagas.
  • And then we callcreateSagaMiddlewaregeneratesagaMiddlewareMiddleware and place it inmiddlewareArray, so that Redux registers the middleware, and when responding to an asynchronous action,sagaMiddlewareWill come in and hand it over to ussagaDelta function.
  • Finally, increateStoreFunction, when creatingstoreAfter that, we callsagaMiddleware.run(rootSaga)To take all thesagasRun and start listening for and responding to asynchronous actions.

View initiates an asynchronous request

With redux-Saga middleware configured and Sagas running, we can dispatch asynchronous actions in React.

Let’s follow the order of refactoring, first to get the login of the asynchronous data stream processing, open the SRC/components/LoginForm/index. The JSX files, make corresponding modifications to the content as follows:

import Taro, { useState } from '@tarojs/taro'
import { View, Form } from '@tarojs/components'
import { AtButton, AtImagePicker } from 'taro-ui'
import { useDispatch } from '@tarojs/redux'

import { LOGIN } from '.. /.. /constants'
import './index.scss'

export default function LoginForm(props) {
  // Other logic unchanged...

  async function handleSubmit(e) {
    // Other logic unchanged...

    // Cache in storage
    const userInfo = { avatar: files[0].url, nickName: formNickName }

    // Clear the form state
    setFiles([])
    setFormNickName(' ')

    // The backend initiates a login request
    dispatch({ type: LOGIN, payload: { userInfo: userInfo } })
  }

  return (
    // The returned component...)}Copy the code

As you can see, we made the following three changes to the above code:

  • We will set the user login information beforeSET_LOGIN_INFOAnd set the login box pop-up layerSET_IS_OPENEDReplaced with aLOGINConstant, which means that we need to initiate a login request to the small program cloud first, and then get the login data and then set the login information and close the pop-up layer of the login box (in fact, here can also directly close the pop-up layer, a little error… .
  • We then delete the previous actions of setting login information and turning off the pop-up layer of the login box.
  • And finally we are going todispatchaaction.typeLOGINAction with the information we need to log inuserInfo.

Add Action constant

We used the LOGIN constant in the previous step, open SRC /constants/user.js and add the LOGIN constant to it:

export const SET_IS_OPENED = 'MODIFY_IS_OPENED'
export const SET_LOGIN_INFO = 'SET_LOGIN_INFO'
export const LOGIN = 'LOGIN'
Copy the code

Saga handles asynchronous requests

Saga handles asynchronous requests in a number of different ways, depending on the project, but we have used the official best practices:

  • WatcherSaga listens for asynchronous actions
  • HandlerSaga handles asynchronous actions
    • dispatchFor synchronous actions, update the starting status of asynchronous actions
    • dispatchUpdate the success/failure status of synchronous actions

Using recent practice, the previous Redux data flow diagram looks like this:

Now that we’ve covered the best practices of Redux-Saga for handling asynchronous actions, we’ll apply the best practices to writing saga files for handling asynchronous actions.

Multiple asynchronous requests may be involved in our application, so Redux-Saga recommends that the best practice is to create a separate SAGAS folder to hold all the SAGAS files that handle asynchronous requests, as well as any ancillary files that may be needed.

In the previous step, we made the LOGIN asynchronous LOGIN request from the View. Now we need to write the saga file that handles this LOGIN request, create the sagas folder in the SRC folder, and create user.js in it, and write the following:

import Taro from '@tarojs/taro'
import { call, put, take, fork } from 'redux-saga/effects'

import { userApi } from '.. /api'
import {
  SET_LOGIN_INFO,
  LOGIN_SUCCESS,
  LOGIN,
  LOGIN_ERROR,
  SET_IS_OPENED,
} from '.. /constants'

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the login logic start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

function* login(userInfo) {
  try {
    const user = yield call(userApi.login, userInfo)

    // Cache user information locally
    yield Taro.setStorage({ key: 'userInfo'.data: user })

    // The following three steps can be combined into one step, but they are broken up into separate units for clarity

    // Initiate a successful login action
    yield put({ type: LOGIN_SUCCESS })

    // Close the login pop-up layer
    yield put({ type: SET_IS_OPENED, payload: { isOpened: false}})// Update Redux store data
    const { nickName, avatar, _id } = user
    yield put({
      type: SET_LOGIN_INFO,
      payload: { nickName, avatar, userId: _id },
    })

    // A message indicating successful login is displayed
    Taro.atMessage({ type: 'success'.message: 'Congratulations! Login successful! '})}catch (err) {
    console.log('login ERR: ', err)

    // Failed to log in. Failed to initiate action
    yield put({ type: LOGIN_ERROR })

    // Login failed
    Taro.atMessage({ type: 'error'.message: 'I'm sorry! Login failed! '}}})function* watchLogin() {
  while (true) {
    const { payload } = yield take(LOGIN)

    console.log('payload', payload)

    yield fork(login, payload.userInfo)
  }
}

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the login logic end * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

export { watchLogin }
Copy the code

As you can see, the above changes mainly create watcherSaga and handlerSaga.

createwatcherSaga

  • We created the logged inwatcherSaga:watchLoginIt is used to listen inaction.typeLOGINAction, and when listeningLOGINAfter the action, get the necessary ones from the actionuserInfoArray, and then activatehandlerSaga:loginTo handle the corresponding logon logic.
  • Here,watcherSaga:watchLoginIs a generator function that contains awhileInfinite loop, which means continuous listening insideLOGINThe action.
  • Inside the loop, we useredux-sagaTo provide theeffects helperFunction:takeIt is used to listenLOGINAction to get the data carried in the action.
  • And then we used another oneeffects helperFunction:fork, which represents non-blocking executionhandlerSaga:loginAnd willpayload.userInfoPass as a parameterlogin.

createhandlerSaga

  • We created the logged inhandlerSaga:login, which handles login logic.
  • loginIs also a generator function, and inside it is atry/catchStatement to handle possible error cases in login requests.
  • intryStatement, the first is usedredux-sagaProvided to useffects helperFunction:callTo call the login API:userApi.loginAnd put theuserInfoPass it to the API as a parameter.
    • And then if we login successfully, we will login successfullyuserThe cache tostorageThe inside.
    • Next, we useredux-sagaTo provide theeffects helpersFunction:put.putSimilar to beforeviewIn thedispatchOperation, comedispatchThere are three actions:LOGIN_SUCCESS.SET_IS_OPENED.SET_LOGIN_INFOTo update the login status, close the login box, and set the login information to the Redux Store.
    • Finally, we used the message box provided by Taro UI to display asuccessThe message.
  • If the login fails, we useputTo launch aLOGIN_ERRORAction to update the failed login information to the Redux Store, and then use the message box provided by Taro UI to display oneerrorThe message.

Pay attention to

For those unfamiliar with generator functions, take a look at this document: Iterators and Generators.

Some extra work

To create watcherSaga and handlerSaga, we also imported the userApi, which we will create later.

We also import the action constants we need to use:

  • SET_LOGIN_INFO: Sets the login information
  • LOGIN_SUCCESS: Updates the login success information
  • LOGIN: Listens for login actions
  • LOGIN_ERROR: Updates the login failure information
  • SET_IS_OPENED: Enables or disables the login dialog box

We also imported the necessary functions from the Redux-Saga/Effects package:

  • callIn:sagaFunction to call other asynchronous/synchronous functions to get the result
  • putSimilar to:dispatchforsagaInitiate action in the function
  • takeIn:sagaFunction to listen on the action and get the data carried by the corresponding action
  • forkIn:sagaA non-blocking call to a functionhandlerSagaThat is, after the call, no subsequent execution logic is blocked.

Finally, we exported watchLogin.

createsagaCentral dispatch file

In the previous step, we exported watchLogin, which is similar to a single reducer function in reducers. We also need to combine all watcherSaga as combineReducers combined reducer.

Create an index.js file in the SRC /sagas folder and write the following in it:


import { fork, all } from 'redux-saga/effects'
 
import { watchLogin } from './user'
 
export default function* rootSaga() {
  yield all([
    fork(watchLogin)
  ])
}
Copy the code

As you can see, there are three major changes to the file above:

  • We learn fromredux-saga/effectsDerived byeffects helperfunctionforkall.
  • And then we go fromuser.jsSaga importedwatchLogin
  • And finally we derived onerootSaga, which is the center for scheduling all sagAS functions through theallThe function passes in an array, andforkNon-blocking executionwatchLoginTo start listening and distributing asynchronous actions, once listeningLOGINAction is activatedwatchLoginThe processing logic inside.

Pay attention to

The only array received by all is fork(watchLogin). When the asynchronous logic of POST is added later, multiple forks (watcherSaga) will be added to the array.

Add action constant

Since we used some undefined constants in the User saga file from the previous step, let’s define them immediately, open SRC /constants/user.js and add the corresponding constants as follows:

export const SET_IS_OPENED = 'MODIFY_IS_OPENED'
export const SET_LOGIN_INFO = 'SET_LOGIN_INFO'

export const LOGIN = 'LOGIN'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGIN_ERROR = 'LOGIN_ERROR'
export const LOGIN_NORMAL = 'LOGIN_NORMAL'
Copy the code

As you can see, in addition to the constant we used in “Saga handling asynchronous requests”, there is a LOGIN_NORMAL constant, which is mainly used to set the default state of the login state.

Implement the request Login API

In the previous User Saga file, we used the userApi, which encapsulates the logic for making requests to the back end (in this case, the applet cloud). Let’s implement it now.

We put all the API files in the API folder, which is convenient for us to maintain the code in the future. We created the API folder in the SRC folder, added the user.js file to it, and wrote the following contents in the file:

import Taro from '@tarojs/taro'

async function login(userInfo) {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY

  // Use the miniprogram cloud function for wechat miniprograms, and the rest use the miniprogram RESTful API
  try {
    if (isWeapp) {
      const { result } = await Taro.cloud.callFunction({
        name: 'login'.data: {
          userInfo,
        },
      })

      return result.user
    }
  } catch (err) {
    console.error('login ERR: ', err)
  }
}

const userApi = {
  login,
}

export default userApi
Copy the code

In the above code, we define the login function, which is an async function used to process asynchronous logic. In the login function, we evaluate the current environment and perform login operations only under the conditions of isappellate apps. We’ll address this in the next section using LeanCloud Serverless.

The logon logic is a try/catch statement that catches possible request errors. In the try code block, We use the cloud function API taro.cloud. callFunction of the wechat applet cloud provided by Taro to conveniently initiate cloud function call requests to the applet cloud. Its call body is an object similar to the following structure:

{
  name: ' '.// The name of the cloud function to call
  data: {} // The data to be passed to the cloud function
}
Copy the code

Here we call a login cloud function, which we will implement in the next section, and pass userInfo as a parameter to the cloud function to use the user information in the cloud function to register the user and save it to the database.

prompt

To learn more about the wechat applets cloud function, you can refer to the wechat applets cloud function documentation: Document address

If the call is successful, we can receive the return value, which is used to return data from the back end. Here we use the deconstruction method, taking the Result object from the return body, and then retrieving the User object as the return value of the Login API function.

If the call fails, an error is printed.

Finally, we define a userApi object to hold all functions associated with the user logic, add the Login API attribute and export it. This allows you to import the userApi in the User Saga function and then call the login API to handle the login logic as userapi.login.

Create the default API export file

We created SRC/API /user.js file, we need to create a unified default file for exporting all API files, convenient for unified distribution of all API files, create index.js file in SRC/API folder, and write the following contents in it:


import userApi from './user'
export { userApi }
Copy the code

As you can see, we exported the userApi from user.js by default and added it as a property of the object exported by Export.

Configure the cloud function development environment

We called the login cloud function in the previous section using the cloud function API provided by Taro. Now we will implement the cloud function.

In the wechat applet document, we are required to create a folder to store cloud functions under the root directory of the project, and then specify the value of cloudfunctionRoot field in project.config.json as this directory, so that the applet developer tool can identify this directory as the directory to store cloud functions. And do special mark processing.

We created a functions folder at the root of the project, which is the same as the SRC folder:

. ├ ─ ─ LICENSE ├ ─ ─ the README. Md ├ ─ ─ the config ├ ─ ─ dist ├ ─ ─functions├ ─ ─ node_modules ├ ─ ─ package. Json ├ ─ ─ project. Config. The json ├ ─ ─ the SRC ├ ─ ─ tuture - assets ├ ─ ─ tuture - build ├ ─ ─ tuture. Yml └ ─ ─ yarn.lockCopy the code

Json in the root directory and set it to ‘functions/’ as follows:

{
  "miniprogramRoot": "dist/"."projectname": "ultra-club"."description": ""."appid": ""."cloudfunctionRoot": "functions/"."setting": {
    "urlCheck": true."es6": false."postcss": false."minified": false
  },
  "compileType": "miniprogram"."simulatorType": "wechat"."simulatorPluginLibVersion": {},
  "cloudfunctionTemplateRoot": "cloudfunctionTemplate"."condition": {}}Copy the code

As you can see, when we create the folder above and set project.config.json, our applet developer tool will look something like this:

We create the folder functions provides one more additional cloud icon, and a folder named transformed from functions provides functions provides | ultra – club, vertical bar on the right is a small program that our current environment.

And when we right-click on the Functions folder in the Applets developer tools, a menu pops up that allows us to perform cloud function-related actions:

We can see that there are many operations. Here we mainly use the following operations:

  • Create a new Node.js cloud function
  • Enable local cloud function debugging

Pay attention to

Other operations will be encountered when you need to write more complex business logic after you complete the development process of the small program cloud. For details, please refer to the document of the small program cloud: Document address.

Pay attention to

You must first open the applets cloud development environment to use cloud functions. Specific steps can refer to our explanation in the section of “Opening the small program cloud”.

Create the Login cloud function

We explained the configuration of wechat applet cloud functions, and finally arrived at the stage of creating cloud functions. We right-click functions folder in the applet developer tool, and then select New Node.js cloud function, enter login, and press Enter to create. We’ll see that the applets developer tool automatically creates the following code file for us:

As you can see, a cloud function is a separate Node.js module that handles a class of logic.

Let’s look at the package.json file as follows:

{
  "name": "login"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": ""."license": "ISC"."dependencies": {
    "wx-server-sdk": "latest"}}Copy the code

As you can see, when adding the cloud function, the applet developer tool adds a wX-Server-SDK dependency by default, and we need to use its built-in API in the cloud function to operate the applet cloud.

To make the Node.js cloud function/project run, we need to install the dependencies, go to the functions/login directory and run NPM install to install the dependencies.

Learn about the default generated cloud functions

Once the cloud functions have been created and the dependencies installed, we can immediately unmask the cloud functions by opening functions/login/index.js and see the following code:

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()

// Cloud function entry function
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
}
Copy the code

As you can see, the default generated code does the following:

  • The importwx-server-sdkPackage and name itcloudAll the methods we need to manipulate the applet cloud are bound tocloudOn the object.
  • Then callcloud.init()To initialize the cloud development environment for cloud functions, which we will implement laterloginLogical time to set the environment.
  • Finally, there is the entry function of the cloud function, which defaults tomainThe function, as the derived function, is aasyncFunction, we can handle asynchronous logic synchronously inside the function. As you can see, this function takes two arguments:eventcontext.eventRefers to the event that triggers the cloud function. When the applet side calls the cloud function, the event is the parameter passed in when the applet side calls the cloud function, plus the parameter of the applet user automatically injected by the back endopenidAnd small programappid.contextObject contains the call information and the running state of the call here, which you can use to see how the service is running. The default generated function internal code is mainly to obtain the wechat context information at this time, and then witheventObjects return together, so that when we’re on the applet sideTaro.cloud.callFunctionThe return value from calling this function is containing wechat context information andeventThe object.

Write the Login cloud function

Now that we know the specific logic of the cloud function, we immediately implement our specific login logic in the cloud function, open functions/login/index.js, and make the corresponding modifications to the code as follows:

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})

const db = cloud.database()

// Cloud function entry function
exports.main = async (event, context) => {
  const { userInfo } = event

  console.log('event', event)

  try {
    const { data } = await db
      .collection('user')
      .where({
        nickName: userInfo.nickName,
      })
      .get()

    if (data.length > 0) {
      return {
        user: data[0].}}else {
      const { _id } = await db.collection('user').add({
        data: {
          ...userInfo,
          createdAt: db.serverDate(),
          updatedAt: db.serverDate(),
        },
      })

      const user = await db.collection('user').doc(_id)

      return {
        user,
      }
    }
  } catch (err) {
    console.error(`login ERR: ${err}`)}}Copy the code

As you can see, the code changes above are mainly as follows:

  • First we givecloud.init()Passed in the environment parameters, we use the built-incloud.DYNAMIC_CURRENT_ENV, means automatically set to the current cloud environment, that is, right click in the applets developer toolsfunctionsFolder to select the environment.
  • And then we go throughcloud.database()A data instance is generateddbFor easy operation of the cloud database in the function body.
  • Then there ismainThe body of the function, we start with theeventObject is taken from the call in the appletTaro.cloud.callFunctionTo get theuserInfoThe data.
  • And then, there’s a guy who takes the datatry/catchStatement block, used to catch errors, intryStatement block, we usedbQuery operation:db.collection('user').where().get(), indicating querywhereConditions of theuserTable data, which checks out should be an array if there is no satisfywhereConditional, then is an empty array if there is one that satisfieswhereConditional, then return oneuserThe array.
  • Next, we determine whether the queried user array is empty. If it is empty, a new user is created. If it is not empty, the first element is returned.
  • This is what we use heredb.collection('user').add()To add auserData, and then inaddMethod passed indataField to set the initial value for this user, which we use in addition heredb.serverDate()It is used to record the time of creating the user and updating the user, which is convenient for conditional query. Because adding a record to the database only returns that record_idSo we need an extra operationdb.collection('user').doc()To get this record, thisdocRetrieves the specified record reference and returns the data instead of an array.

Pay attention to

Here about the related operations of the cloud database, you can refer to the wechat small program cloud document, which provides a detailed example: database document.

Reducer reducer for asynchronous actions

When we were dealing with LOGIN earlier, we dispatched the LOGIN action inside the component. In the saga function that handles asynchronous actions, we used PUT to initiate a series of actions that update the LOGIN status in the store. Now let’s implement the reducers in response to these actions. Open SRC /reducers/user.js and modify the corresponding code as follows:

import {
  SET_LOGIN_INFO,
  SET_IS_OPENED,
  LOGIN_SUCCESS,
  LOGIN,
  LOGIN_ERROR,
  LOGIN_NORMAL,
} from '.. /constants/'

const INITIAL_STATE = {
  userId: ' '.avatar: ' '.nickName: ' '.isOpened: false.isLogin: false.loginStatus: LOGIN_NORMAL,
}

export default function user(state = INITIAL_STATE, action) {
  switch (action.type) {
    case SET_IS_OPENED: {
      const { isOpened } = action.payload

      return { ...state, isOpened }
    }

    case SET_LOGIN_INFO: {
      const { avatar, nickName, userId } = action.payload

      return { ...state, nickName, avatar, userId }
    }

    case LOGIN: {
      return { ...state, loginStatus: LOGIN, isLogin: true}}case LOGIN_SUCCESS: {
      return { ...state, loginStatus: LOGIN_SUCCESS, isLogin: false}}case LOGIN_ERROR: {
      return { ...state, loginStatus: LOGIN_ERROR, isLogin: false}}default:
      return state
  }
}
Copy the code

Take a look at the code above and see that there are three major changes:

  • First we import the necessary action constants
  • And then we giveINITIAL_STATESeveral fields have been added:
    • userId: used to obtain user data later and mark the user’s login status
    • isLogin: indicates whether login logic is being executed during login.trueIndicates that login is in progress,falseIndicates that the login logic is complete
    • loginStatus: Used to indicate the status of the login process: Start login (LOGIN), login successful (LOGIN_SUCCESS), login failed (LOGIN_ERROR)
  • The last isswitchThe statement updates the corresponding state in response to the action.

End the rest of the User’s asynchronous logic

WeChat login

In the previous section, “Implementing Redux Asynchronous Logic”, we focused on implementing the asynchronous logic of ordinary login buttons. Now let’s wrap up the logic of using wechat to log in. Open the SRC/components/WeappLoginButton/index. The js file, make corresponding modifications to the content as follows:

import Taro, { useState } from '@tarojs/taro'
import { Button } from '@tarojs/components'
import { useDispatch } from '@tarojs/redux'

import './index.scss'
import { LOGIN } from '.. /.. /constants'

export default function WeappLoginButton(props) {
  const [isLogin, setIsLogin] = useState(false)

  const dispatch = useDispatch()

  async function onGetUserInfo(e) {
    setIsLogin(true)

    const { avatarUrl, nickName } = e.detail.userInfo
    const userInfo = { avatar: avatarUrl, nickName }

    dispatch({
      type: LOGIN,
      payload: {
        userInfo: userInfo,
      },
    })

    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 above code has three major changes:

  • We deleted the one that set login information directlySET_LOGIN_INFOConstant, instead of constantLOGINConstants.
  • Then we deleted the direct SettingsstorageCache code logic
  • Finally, we will launch beforeSET_LOGIN_INFOAction logic changed to initiateLOGINAsynchronous action to handle login, and assembleduserInfoObject as apayloadObject properties.

Since we have already processed the entire asynchronous data flow logic of LOGIN in the previous section “Implementing Redux Asynchronous Logic”, only the LOGIN action corresponding to Dispatch is needed to process the asynchronous logic of wechat LOGIN.

To optimize theuserLogical top-level component

JSX: SRC /pages/mine/mine.jsx: SRC /pages/mine/mine.jsx

import Taro, { useEffect } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { useDispatch, useSelector } from '@tarojs/redux'

import { Header, Footer } from '.. /.. /components'
import './mine.scss'
import { SET_LOGIN_INFO } from '.. /.. /constants'

export default function Mine() {
  const dispatch = useDispatch()
  const nickName = useSelector(state= > state.user.nickName)

  constisLogged = !! nickName useEffect((a)= > {
    async function getStorage() {
      try {
        const { data } = await Taro.getStorage({ key: 'userInfo' })

        const { nickName, avatar, _id } = data

        // Update Redux Store data
        dispatch({
          type: SET_LOGIN_INFO,
          payload: { nickName, avatar, userId: _id },
        })
      } catch (err) {
        console.log('getStorage ERR: ', err)
      }
    }

    if(! isLogged) { getStorage() } })return (
    <View className="mine">
      <Header />
      <Footer />
    </View>
  )
}

Mine.config = {
  navigationBarTitleText: 'I',}Copy the code

As you can see, we made three changes to the above code as follows:

  • So first we derived ituseSelectorHooks, obtained from Redux StorenickName.
  • Then, because we saved it in the section “Implementing Redux asynchronous logic.userIdThe story of the StoreuserLogic, so here we go fromstorageAccess to the_idAnd then give it to the previous oneSET_LOGIN_INFOpayloadTake theuserIdProperties.
  • Finally, let’s judgegetStorageOnly when there is no data in the Redux Store, we will get the data in the storage to update the Redux Store.

Expand Logout to clear the data range

Because the user property in the Redux Store has an extra userId, we can dispatch the Logout action to clear the userId as follows:

import Taro, { useState } from '@tarojs/taro'
import { AtButton } from 'taro-ui'
import { useDispatch } from '@tarojs/redux'

import { SET_LOGIN_INFO } from '.. /.. /constants'

export default function LoginButton(props) {
  const [isLogout, setIsLogout] = useState(false)
  const dispatch = useDispatch()

  async function handleLogout() {
    setIsLogout(true)

    try {
      await Taro.removeStorage({ key: 'userInfo' })

      dispatch({
        type: SET_LOGIN_INFO,
        payload: {
          avatar: ' '.nickName: ' '.userId: ' ',}})}catch (err) {
      console.log('removeStorage ERR: ', err)
    }

    setIsLogout(false)}return (
    <AtButton type="secondary" full loading={isLogout} onClick={handleLogout}>Log out</AtButton>)}Copy the code

summary

And you’re done! Here we put the user logic access to the small program cloud, and can successfully achieve the small program cloud login wechat small program end, let us immediately try to preview the effect of local debugging preview:

As can be seen, the steps for us to debug the cloud function locally and access the cloud function on the applet side are as follows:

  • So let’s right click firstfunctionsFolder, turned on “cloud function local debugging”.
  • And then pick oursloginCloud function, then click On Enable Local debugging, so we can debug the cloud function locally.
  • Then we click wechat login in the small program side, and then we will see the small program developer tools console and cloud function debugging console will allow the operation of the cloud function at this time.
  • Finally, we login successfully, successfully in the applet side display login nickname and avatar, and check the cloud development > Database > user table, it does add a correspondinguserRecord, indicating that we successfully connected the applets and applets cloud.

In commonly after the local debugging, we can upload function of cloud to cloud, in this way, we can not open the local debugging function to use the cloud, that there is little published online program is a must, specific cloud upload function can be in small developers tool right-click the functions provides folder corresponding to the function of cloud, Then select “Upload and deploy: Cloud Install so rely” :

In this tutorial, we implemented the asynchronous flow of User logic. In the next tutorial, we will implement the asynchronous flow of Post logic. Stay tuned!

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 it is well written, please give ❤️ a thumbs up +Github repository plus a star ❤️