Крупномасштабная практика разработки мини-программ Taro (6): раннее внедрение облака мини-программ WeChat (часть 1)

Апплет WeChat
Крупномасштабная практика разработки мини-программ Taro (6): раннее внедрение облака мини-программ WeChat (часть 1)

Добро пожаловать, чтобы продолжить чтение серии «Крупномасштабное сражение по разработке мини-программы Таро», обзор предыдущей ситуации:

Если вы нажмете здесь, вы обнаружите, что контент, за которым мы будем следить, представляет собой чистую логику внешнего интерфейса (сторона мини-программы). Полное онлайн-приложение мини-программы также должно иметь серверную часть. В этой статье мы будем использовать WeChat. мини-программы.Облако программ в качестве нашего бэкенда, а затем мы введемredux-sagaЧтобы помочь Redux изящно обрабатывать асинхронные процессы, окончательный результат реализации этой статьи выглядит следующим образом:

Если вы не знакомы с Redux, мы рекомендуем прочитать нашу серию руководств «Redux Package Tutorial»:

Если вы хотите начать прямо с этого шага, выполните следующую команду:

git clone -b miniprogram-start https://github.com/tuture-dev/ultra-club.git
cd ultra-club

Исходный код, задействованный в этой статье, размещен вGithubНа, если вы считаете, что мы написали неплохо, я надеюсь, что вы можете поставить лайк этой статье ❤️ + звезда репозитория Github ❤️ о~

Чтобы постоянно хранить данные и эффективно выполнять запросы, нам необходимо хранить данные в базе данных.Чтобы реализовать удобный опыт разработки на стороне апплета, появилось большое количество бессерверных сервисов апплета, а также облако апплета WeChat. именно для апплета WeChat.Рожденный для быстрой разработки. В этой статье мы будем использовать облако апплетов WeChat в качестве нашей серверной части и объясним, как внедрить и реализовать асинхронный рабочий процесс Redux для реализации управления состоянием доступа на стороне апплета к облаку апплета.

Первые пользователи облака апплетов WeChat

В предыдущем коде мы завершаем персистентность данных, сохраняя данные в хранилище, что может решить проблему хранения небольших данных и запросов.Как только объем данных становится большим, запросы и хранилища должны полагаться на специальную базу данных. , Это решено. Как правило, мы можем решить эту проблему, создав собственный сервер и базу данных. Однако, когда небольшие программы становятся все более и более популярными, был предложен и постепенно стал популярным режим под названием «Бессерверный», который можно резюмировать в виде в народном смысле это «без бэкенда», то есть бэкенд передается поставщикам облачных услуг (Alibaba Cloud, Tencent Cloud, JD Cloud и т. д.), а разработчикам нужно сосредоточиться только на логике фронтенда и доставлять функции быстро.

Общий бессерверный сервис апплета включает в себя три функции:

  • База данных: обычно хранится в формате данных JSON, а данные могут храниться в облачной базе данных.
  • Хранилище: поддерживает хранение пользовательского контента, такого как текст и изображения, и может получать ссылки на ресурсы для использования.
  • Облачные функции: вы можете использовать Node.js для разработки, самостоятельно написать соответствующую внутреннюю логику и перенести написанный код в облако, а затем использовать API для вызова внешнего интерфейса апплета.

Подробное описание мини-программы Serverless можно найти в рекомендуемой статье Заинтересованные студенты могут узнать о ней больше:Что такое бессерверная мини-программа?

В этом разделе мы используем облако апплетов WeChat в качестве нашего «бэкенда». Облако апплетов WeChat и учетная запись апплета связаны друг с другом. Одна учетная запись апплета может открыть одно облачное пространство апплета. Далее мы рассмотрим подробности. Объясните, как активировать Mini Program Cloud.

Открытое мини-облако программ

  1. Сначала убедитесь, что вы зарегистрировали официальную учетную запись WeChat для мини-программы:Зарегестрированный адрес.
  2. После входа найдите его в строке меню «Разработка» > «Настройки разработки».AppID, он должен быть 18-битной строкой.
  3. использоватьИнструменты разработчика WeChatоткрыть нашultra-clubпапку проекта, а затем выберите «Настройки» > «Настройки проекта» в строке меню «Инструменты разработчика WeChat», чтобы открыть панель настроек:

4. Найдите основную информацию на панели настроек и измените ее на AppID выше в столбце AppID следующим образом:

5. Когда AppID установлен, кнопка «Облачная разработка» в наших инструментах разработчика должна стать кликабельной, найдите кнопку «Облачная разработка» в верхнем левом углу и нажмите ее, как на следующем рисунке:

4. После нажатия кнопки «Облачная разработка» появится окно подтверждения, нажмите «Согласен», чтобы войти в мини-консоль облачной разработки программы:

После входа первое, что мы видим, это интерфейс «Анализ операций» консоли облачной разработки, который представляет собой интерфейс, используемый для визуализации использования различных ресурсов облачной разработки.Мы не будем объяснять этот аспект в этом руководстве. В основном речь идет о части, отмеченной красным на рисунке:

  • Серийный номер 1 — это наша облачная база данных, представляющая собой базу данных JSON, в которой хранятся данные, необходимые нам во время разработки.
  • Серийный номер 2 — это хранилище, то есть мы можем загрузить какой-то текст, картинки, аудио/видео, а потом вернуть нам ссылку для доступа к этим ресурсам.
  • № 3 — это облачная функция, то есть мы можем управлять частью внутренней логики Node.js, которую мы написали в ней, она работает в облаке, и мы можем вызывать их через API на стороне апплета.
  • На этот раз серийный номер 4 — это идентификатор, представляющий нашу облачную среду, который можно использовать для обозначения облачной среды, вызываемой в данный момент, при вызове ресурсов облачной разработки с помощью API на стороне апплета.

В этом руководстве мы будем использовать базу данных и облачные функции, упомянутые выше.

Создать таблицу базы данных

После знакомства с интерфейсом облака апплетов давайте сразу потренируемся в создании нужных нам таблиц базы данных, потому что логика нашего фронтенда в основном делится наuserа такжеpostДва типа логики, поэтому мы создаем две таблицы в базе данных:

Здесь мы специально объясняем значение этого интерфейса работы с базой данных:

  • Как видите, нажмите вторую кнопку в левом верхнем углу консоли облачной разработки, а затем нажмите кнопку «+», отмеченную красной цифрой 1 на рисунке, чтобы создать два набора.userа такжеpost, поэтому мы создали нашу таблицу базы данных.
  • Серийный номер — 2, что означает, что мы можем выбрать коллекцию и щелкнуть правой кнопкой мыши, чтобы удалить ее.
  • Серийный номер — 3, что означает, что мы можем добавлять записи в коллекцию.Поскольку это база данных JSON, каждая запись в коллекции может быть разной.
  • № 4 означает, что мы можем выбрать запись и щелкнуть правой кнопкой мыши, чтобы удалить ее.
  • Порядковый номер 5 означает, что мы можем добавлять поля в одну запись.
  • № 6 означает, что мы можем выбрать одну запись для удаления/изменения
  • Порядковый номер 7 означает, что мы можем запросить запись в этом наборе

СоздайтеpostЗаписывать

Здесь мы добавляем значение по умолчаниюpostзапись, что означает данные по умолчанию на нашем предыдущем апплете, эти данные записываютсяpostсвязанная информация:

  • _id: уникальный идентификатор этого фрагмента данных.
  • title: название статьи
  • content: содержание статьи
  • user: пользователь, опубликовавший эту статью, здесь мы напрямую сохраняем полную информацию о пользователе для удобства._idсвойства, а затем в запросеpost, получить_idсвойства, а затем проверьтеuserПолучите полную информацию о пользователе.
  • updatedAt: время последнего обновления этой записи
  • createdAt: время создания этой записи

СоздайтеuserЗаписывать

Выше мы упоминали, что мы сохранили информацию об авторе сообщения в этой записи статьи, поэтому, конечно же, нашаuserНовая порция информации об этом авторе будет создана в сборнике следующим образом:

Можно видеть, что мы добавляем запись пользователя, его поля следующие:

  • _id: Этот пользователь находится вuserУникальный идентификатор в коллекции
  • avatar: адрес аватара этого пользователя
  • nickName: псевдоним этого пользователя, который мы будем использовать для входа
  • createdAt: время создания этой записи
  • updatedAt: последний раз, когда эта запись была обновлена

Инициализировать облачную среду апплета на стороне апплета

После открытия облака апплета нам также необходимо инициализировать среду облака апплета во внешнем коде апплета, чтобы API апплета можно было вызывать во внешнем интерфейсе апплета.

Открытьsrc/index/index.jsxфайл, добавьте в него следующий код:

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

// ... 其余代码一致

export default function Index() {
  // ... 其余代码一致
  useEffect(() => {
    const WeappEnv = Taro.getEnv() === Taro.ENV_TYPE.WEAPP

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

  // ...其余代码一致
  return (
    <View className="index">
      ...
    </View>
  )
}

Видно, что мы добавили получение и оценку среды апплета WeChat.Когда текущая среда является средой апплета WeChat, нам нужно вызватьTaro.cloud.init()Инициализация облачной среды апплета

резюме

До сих пор мы объясняли, как активировать облако апплета, а затем объяснили интерфейс консоли облака апплета.В то же время мы объяснили интерфейс функции базы данных, который будет использоваться, и создали две таблицы (коллекцию), необходимые для нашего приложения. . ):postа такжеuser, и каждый инициализирует запись.

Что ж, облако апплетов готово, мы готовы получить к нему доступ в приложении, но перед этим, поскольку мы хотим получить доступ к облаку апплетов, оно должно инициировать асинхронный запрос, который требует понимания потока асинхронной обработки Redux, в следующий раздел, мы будем использоватьredux-sagaПромежуточное ПО для упрощения процесса обработки асинхронности Redux.

Анализ асинхронного рабочего процесса Redux

Давайте посмотрим на диаграмму потока данных Redux:

Серая фигура над этим путем — это карта потока данных Redux, которую мы использовали ранее, то есть синхронная диаграмма потока данных Redux:

  • viewсерединаdispatch(syncAction)синхронное действие для обновленияstoreданные в
  • reducerреагировать на действия, обновлятьstoreусловие
  • connectПередайте обновленное состояниеview
  • viewПолучать новые данные и перенаправить

Уведомление

Студенты, не знакомые с Redux, могут узнать о сообществе Tuque.Серия руководств по пакету ReduxОй.

Теперь мы собираемся сделать запрос к облаку апплета, этот запрос является асинхронным запросом, он не получит ответ сразу, поэтому нам нужно промежуточное состояние (здесь мы используемSaga) назад и вперед, чтобы обработать этот асинхронный запрос и получить данные, а затем выполнить путь, аналогичный предыдущему синхронному запросу, то есть зеленую часть + оставшуюся серую часть на нашем рисунке выше, поэтому асинхронный рабочий процесс становится таким:

  • viewсерединаdispatch(asyncAction)Асинхронное действие для получения данных бэкенда (здесь облако апплета)
  • sagaОбработайте асинхронное действие и дождитесь ответа данных
  • sagaполучить данные ответа,dispatch(syncAction)Синхронное действие для обновления состояния хранилища
  • reducerреагировать на действия, обновлятьstoreусловие
  • connectПередайте обновленное состояниеview
  • viewПолучение новых данных и повторный рендеринг

Уведомление

Сообщество Tuque в будущем опубликует учебник для объяснения асинхронного рабочего процесса Redux.Я не буду здесь подробно изучать принцип всего асинхронного процесса, а только объясню, как интегрировать этот асинхронный рабочий процесс. Пожалуйста, ждите этого ✌️~

Реальный асинхронный рабочий процесс Redux

Установить

Мы используемredux-sagaЭто промежуточное программное обеспечение берет на себя часть обработки асинхронных запросов асинхронного рабочего процесса Redux, сначала установленного в корневом каталоге проекта.redux-sagaМешок:

$ npm install redux-saga

После установки нашpackage.jsonЭто становится таким:

{
  "dependencies": {
    ...
    "redux-saga": "^1.1.3",
    "taro-ui": "^2.2.4"
  },
}

redux-sagaдаreduxПромежуточное ПО, которое обрабатывает асинхронные процессы, так что же такое Saga? Определение Saga — «долгоживущая транзакция» (Long Lived Transaction, далее LLT). Это концепция, предложенная профессором ГЕКТОРОМ ГАРСИА-МОЛИНОЙ из Принстонского университета в статье 1987 года о распределенных базах данных.

Официально сага уподобляется отдельному потоку в приложении, который отвечает за независимую обработку побочных эффектов.В JavaScript побочные эффекты относятся к внешним операциям, таким как асинхронные сетевые запросы и локальное чтение localStorage/Cookie.

настроитьredux-sagaпромежуточное ПО

После установки нам нужно настроитьredux-sagaиспользовать его, открытьsrc/store/index.jsфайл и внесите соответствующие изменения в его содержимое следующим образом:

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() {
  const store = createStore(rootReducer, applyMiddleware(...middlewares))

  sagaMiddleware.run(rootSaga)

  return store
}

Как видите, в наш файл выше были внесены следующие четыре изменения:

  • Сначала мы экспортировалиcreateSagaMiddleware
  • Затем мы начинаем сsrc/store/sagasэкспортировано из папкиrootSaga, который объединяет всеsagaфайл, который похож на комбинациюreducerизcombineReducers, мы напишем их в последующих шагахsagas.
  • Затем мы звонимcreateSagaMiddlewareгенерироватьsagaMiddlewareпромежуточное ПО и поместите его вmiddlewareмассив, поэтому Redux зарегистрирует это промежуточное ПО, и при ответе на асинхронные действияsagaMiddlewareвмешается и передаст его нашему определенномуsagaфункция для обработки.
  • Наконец вcreateStoreвнутри функции, при созданииstoreПосле этого звонимsagaMiddleware.run(rootSaga)положить всеsagasЗапустите и начните слушать и реагировать на асинхронные действия.

Инициировать асинхронный запрос в представлении

Настроить с помощьюredux-sagaпромежуточное ПО и будетsagasПосле запуска мы можем начать отправку асинхронных действий в React.

Давайте следовать предыдущей последовательности рефакторинга, сначала чтобы получить асинхронную обработку потока данных входа в систему, открытьsrc/components/LoginForm/index.jsxфайл и внесите соответствующие изменения в его содержимое следующим образом:

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) {
  // 其他逻辑不变 ...

  async function handleSubmit(e) {
    // 其他逻辑不变 ...

    // 缓存在 storage 里面
    const userInfo = { avatar: files[0].url, nickName: formNickName }

    // 清空表单状态
    setFiles([])
    setFormNickName('')

    // 向后端发起登录请求
    dispatch({ type: LOGIN, payload: { userInfo: userInfo } })
  }

  return (
    // 返回的组件...
  )
}

Как видите, мы вносим следующие три изменения в приведенный выше код:

  • Мы установим информацию для входа пользователя ранееSET_LOGIN_INFOи установите всплывающий слой окна входа в системуSET_IS_OPENEDзаменяетсяLOGINКонстанта означает, что нам нужно сначала инициировать запрос на вход в облако апплета, затем получить данные для входа, затем установить информацию для входа и закрыть всплывающий слой окна входа в систему (на самом деле, всплывающий слой также может быть закрыт прямо здесь, что немного ошибочно (⊙o⊙)…).
  • Затем мы удаляем предыдущие операции установки информации для входа и закрытия всплывающего слоя окна входа.
  • Наконец мы будемdispatchОдинaction.typeдляLOGINдействие, с информацией, необходимой для входа в системуuserInfo.

Добавить константу действия

мы использовали на предыдущем шагеLOGINпостоянный, открытыйsrc/constants/user.js, в котором увеличениеLOGINпостоянный:

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

Saga обрабатывает асинхронные запросы

В Saga есть много способов обработки асинхронных запросов.В зависимости от проекта могут использоваться разные способы.Здесь мы выбираем лучшие практики, рекомендованные официальным лицом:

  • watcherSaga прослушивает асинхронные действия
  • handlerSaga обрабатывает асинхронные действия
    • dispatchСинхронное действие, обновить состояние, в котором началось асинхронное действие.
    • dispatchСинхронные действия, обновить состояние успеха / неудачи асинхронных действий

После применения последней практики предыдущая диаграмма потока данных Redux становится следующей:

Хорошо, объяснилredux-sagaТеперь, когда мы рассмотрели рекомендации по обработке асинхронных действий, давайте применим рекомендации по написанию файлов Saga, обрабатывающих асинхронные действия.

В нашем приложении может быть несколько асинхронных запросов, поэтомуredux-sagaРекомендуется создать отдельныйsagasПапка для хранения всех асинхронных запросовsagasфайл и вспомогательные файлы, которые могут быть использованы.

На предыдущем шаге мы излучали из представленияLOGINАсинхронный запрос на вход, далее мы должны написать соответствующую обработкуLOGINпросилsagaфайл, вsrcсоздать папкуsagasпапку и создать в нейuser.js, в котором напишите следующее:

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'

/***************************** 登录逻辑开始 ************************************/

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

    // 将用户信息缓存在本地
    yield Taro.setStorage({ key: 'userInfo', data: user })

    // 其实以下三步可以合成一步,但是这里为了讲解清晰,将它们拆分成独立的单元

    // 发起登录成功的 action
    yield put({ type: LOGIN_SUCCESS })

    // 关闭登录框弹出层
    yield put({ type: SET_IS_OPENED, payload: { isOpened: false } })

    // 更新 Redux store 数据
    const { nickName, avatar, _id } = user
    yield put({
      type: SET_LOGIN_INFO,
      payload: { nickName, avatar, userId: _id },
    })

    // 提示登录成功
    Taro.atMessage({ type: 'success', message: '恭喜您!登录成功!' })
  } catch (err) {
    console.log('login ERR: ', err)

    // 登录失败,发起失败的 action
    yield put({ type: LOGIN_ERROR })

    // 提示登录失败
    Taro.atMessage({ type: 'error', message: '很遗憾!登录失败!' })
  }
}

function* watchLogin() {
  while (true) {
    const { payload } = yield take(LOGIN)

    console.log('payload', payload)

    yield fork(login, payload.userInfo)
  }
}

/***************************** 登录逻辑结束 ************************************/

export { watchLogin }

Как видите, вышеперечисленные изменения в основном предназначены для созданияwatcherSagaа такжеhandlerSaga.

СоздайтеwatcherSaga

  • Мы создали логинwatcherSaga:watchLogin, который используется для наблюденияaction.typeдляLOGINдействия, а при прослушиванииLOGINПосле действия получить необходимое от этого действияuserInfoмассив, затем активируйтеhandlerSaga:loginДля обработки соответствующей логики входа.
  • здесьwatcherSaga:watchLoginявляется генераторной функцией, которая внутренне являетсяwhileБесконечный цикл, что означает непрерывное внутреннее прослушиваниеLOGINдействие.
  • Внутри цикла мы использовалиredux-sagaкоторый предоставилeffects helperфункция:take, который используется для наблюденияLOGINДействие, получите данные, переданные в действии.
  • Тогда мы используем другойeffects helperфункция:fork, что означает неблокирующее выполнениеhandlerSaga:login, и воляpayload.userInfoпередается как параметр вlogin.

СоздайтеhandlerSaga

  • Мы создали логинhandlerSaga:login, который обрабатывает логику входа.
  • loginтакже является генераторной функцией, а внутри нее находитсяtry/catchЗаявление для обработки возможных условий ошибки для запросов на вход.
  • существуетtryоператор, первый должен использоватьredux-sagaпредоставил намeffects helperфункция:callдля вызова API входа:userApi.login, и положиuserInfoПередано в качестве параметра этому API.
    • Затем, если вход в систему прошел успешно, мы войдем успешноuserкешировать вstorageв.
    • Далее мы используемredux-sagaкоторый предоставилeffects helpersфункция:put,putпохож на предыдущийviewсерединаdispatchдействуй, приходиdispatchтри действия:LOGIN_SUCCESS,SET_IS_OPENED,SET_LOGIN_INFO, который представляет статус успешного входа в систему, закройте окно входа и установите информацию для входа в Redux Store.
    • Наконец, мы использовали окно сообщения, предоставленное нам пользовательским интерфейсом Taro, для отображенияsuccessИнформация.
  • Если авторизация не удалась, мы используемputинициироватьLOGIN_ERRORдействие, чтобы обновить сообщение об ошибке входа в Redux Store, а затем использовать окно сообщения, предоставленное нам пользовательским интерфейсом Taro, для отображенияerrorИнформация.

Уведомление

Учащиеся, не понимающие функции генератора, могут ознакомиться с этим документом:итераторы и генераторы.

дополнительная работа

чтобы создатьwatcherSagaа такжеhandlerSaga, мы также импортировалиuserApi, мы создадим этот API позже.

В дополнение к этому мы также импортируем константы действия, которые нам нужно использовать:

  • SET_LOGIN_INFO: установить данные для входа
  • LOGIN_SUCCESS: обновить информацию об успешном входе в систему.
  • LOGIN: прослушивание действий входа
  • LOGIN_ERROR: обновить информацию об ошибке входа в систему.
  • SET_IS_OPENED: включение/выключение информации в окне входа в систему.

мы тоже изredux-saga/effectsВ пакете импортированы необходимые функции:

  • call:существуетsagaВызовите другие асинхронные/синхронные функции в функции, чтобы получить результат
  • put:аналогичныйdispatchДляsagaИнициировать действие в функции
  • take:существуетsagaСлушайте действие в функции и получайте данные, переносимые соответствующим действием.
  • fork:существуетsagaНеблокирующие вызовы в функцияхhandlerSaga, то есть после вызова последующая логика выполнения не будет заблокирована.

Наконец, мы получаемwatchLogin.

Создайтеsagaцентральный файл расписания

Мы экспортировали на предыдущем шагеwatchLogin, что похоже наreducersодин внутриreducerфункция, нам также нужно иметь что-то вродеcombineReducersкомбинацияreducerобъединить одно и то жеwatcherSaga.

существуетsrc/sagasсоздать папкуindex.jsфайл и пишем в нем следующее:


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

Как видите, в приведенном выше файле есть три основных изменения:

  • мы начинаем сredux-saga/effectsэкспортируетсяeffects helperфункцияforkа такжеall.
  • Затем мы начинаем сuser.jsимпортировано в сагеwatchLogin.
  • Наконец, мы экспортировали одинrootSaga, Это центр всех функций сага, черезallМассив передается в функцию, иforkнеблокирующее выполнениеwatchLogin, а затем начните слушать и отправлять асинхронные действия,LOGINдействие, активироватьwatchLoginЛогика обработки внутри.

Уведомление

В настоящее времяallМассив, полученный функцией,fork(watchLogin), дождитесь последующих соединенийpostКогда используется асинхронная логика, она также добавит несколькоfork(watcherSaga).

добавить константу действия

потому что на предыдущем шагеuserВ файле саги мы используем некоторые константы, которые еще не определены, поэтому давайте сразу их определим, открываемsrc/constants/user.jsи добавьте соответствующие константы следующим образом:

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'

Как видите, помимо констант, которые мы использовали в «саге об обработке асинхронных запросов», есть еще однаLOGIN_NORMALКонстанты, которые в основном используются для установки состояния входа по умолчанию.

Реализовать API запроса входа в систему

в предыдущемuserВ файле саги мы используемuserApi, который инкапсулирует логику инициирования запросов к бэкенду (здесь мы облако апплета), давайте сразу его реализуем.

Мы храним все файлы API в одном местеapiВ папке это удобно для нашей будущей работы по обслуживанию кода, вsrcсоздать папкуapiпапка, добавитьuser.jsфайл и пропишите в файле следующее:

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

  // 针对微信小程序使用小程序云函数,其他使用小程序 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

В приведенном выше коде мы определяемloginфункция, этоasyncфункция, используемая для обработки асинхронной логики, вloginВ функции мы судим о текущей среде, и только в апплете WeChat, т.е.isWeappВыполните операцию входа в систему в условиях апплета Alipay и H5, мы будем использовать бессерверную версию LeanCloud для ее решения в следующем разделе.

Логика входа в системуtry/catchоператор, используемый для обнаружения возможных ошибок запроса, вtryВ блоке кода мы использовалиTaroПредоставляет нам API облачных функций WeChat Mini Program Cloud.Taro.cloud.callFunctionЧтобы было удобно запускать запрос на вызов облачной функции, его тело вызова представляет собой объект, подобный следующей структуре:

{
  name: '', // 需要调用的云函数名
  data: {} // 需要传递给云函数的数据
}

Здесь мы называемloginоблачная функция, и будетuserInfoОн передается в качестве параметра облачной функции, чтобы использовать информацию о пользователе в облачной функции для регистрации пользователя и сохранения ее в базе данных.Мы реализуем эту облачную функцию в следующем разделе.

намекать

Чтобы узнать больше об облачных функциях апплета WeChat, вы можете обратиться к документации по облачным функциям апплета WeChat:адрес документа

Если вызов успешно, мы можем получить возвращаемое значение для возврата данных с бэкэнда. Здесь мы используем метод разрушимости, чтобы получить его от возврата телаresultобъект, а затем извлекитеuserобъект и какloginВозвращаемое значение функции API.

Если вызов не удался, вывести ошибку.

Наконец, мы определяемuserApiОбъект, используемый для хранения всех функций, связанных с пользовательской логикой, и добавленияloginНедвижимость API, а затем экспортировать это так, чтобы вuserВы можете импортировать его в функцию сагиuserApiзатем пройтиuserApi.loginспособ позвонитьloginAPI обрабатывает логику входа.

Создать файл экспорта API по умолчанию

мы создалиsrc/api/user.jsфайл, нам нужно создать единый файл по умолчанию для экспорта всех файлов API, чтобы облегчить единое распространение всех API.src/apiсоздать папкуindex.jsфайл и пишем в нем следующее:


import userApi from './user'
export { userApi }

Видно, что мы начинаем сuser.jsОн экспортируется по умолчаниюuserApi, и добавьте его вexportСвойства экспортируемого объекта.

Настройка среды разработки облачных функций

Мы использовали API облачных функций, предоставленный Taro в предыдущем разделе, для вызоваloginОблачная функция, теперь мы будем реализовывать эту облачную функцию немедленно.

Документация апплета WeChat требует, чтобы мы создали папку для хранения облачных функций в корневом каталоге проекта, а затемproject.config.jsonизcloudfunctionRootЗначение поля указывается как этот каталог, чтобы инструмент разработчика апплета мог идентифицировать этот каталог как каталог для хранения облачных функций и выполнять специальную обработку флагов.

Мы создалиfunctionsпапка, аналогичнаяsrcПапки являются братьями и сестрами:

.
├── LICENSE
├── README.md
├── config
├── dist
├── functions
├── node_modules
├── package.json
├── project.config.json
├── src
├── tuture-assets
├── tuture-build
├── tuture.yml
└── yarn.lock

Затем мы находимся в корневом каталогеproject.config.jsonфайл добавленcloudfunctionRootполе и установить его на'functions/'следующим образом:

{
  "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": {}
}

Как видите, когда мы создали указанную выше папку и установилиproject.config.jsonПосле этого наш инструмент разработчика апплета будет выглядеть так:

тот, который мы создалиfunctionsПапки имеют дополнительный значок облака и называются отfunctionsсталfunctions | ultra-club, правая сторона вертикальной полосы — это наша текущая среда апплета.

И когда мы щелкаем правой кнопкой мыши в инструментах разработчика апплетаfunctionsКогда папка открыта, появится всплывающее окно меню, позволяющее нам выполнять операции, связанные с облачными функциями:

Мы видим, что операций много, здесь мы в основном используем следующие операции:

  • Новая облачная функция Node.js
  • Включить локальную отладку облачных функций

Уведомление

Другие операции будут встречаться, когда вам нужно написать более сложную бизнес-логику после того, как вы прошли весь процесс разработки Mini Program Cloud.Подробности см. в документации Mini Program Cloud:адрес документа.

Уведомление

Прежде чем использовать облачные функции, необходимо активировать облачную среду разработки Mini Program. Для конкретных шагов, пожалуйста, обратитесь к нашему объяснению в разделе «Активация Mini Program Cloud».

Создайте облачную функцию входа в систему

Объяснили настройку облачных функций апплета WeChat и, наконец, дошли до этапа создания облачных функций.Щелкаем правой кнопкой мыши в инструментах разработчика апплетаfunctionsпапку, затем выберите «Новая облачная функция Node.js», введитеlogin, а затем нажмите Enter для создания, вы увидите, что инструмент разработчика апплета автоматически создал для нас следующий файл кода:

Как видите, облачная функция — это автономный модуль Node.js, который обрабатывает класс логики.

Давайте сначала посмотримpackage.jsonФайлы следующие:

{
  "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"
  }
}

Видно, что при добавлении облачных функций инструмент разработчика апплета добавил нам пункт по умолчанию.wx-server-sdkЗависимость, нам нужно использовать его встроенные связанные API в облачной функции для работы с облаком апплетов.

Чтобы запустить эту облачную функцию/проект Node.js, нам нужно установить зависимости, введитеfunctions/loginкаталог, запустить в каталогеnpm installкоманда для установки зависимостей.

Общие сведения об облачных функциях, созданных по умолчанию

Когда облачная функция будет создана и зависимости установлены, мы сразу разгадаем тайну облачной функции и откроемfunctions/login/index.js, вы можете увидеть следующий код:

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()

// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

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

Видно, что созданный по умолчанию код в основном сделал следующую работу:

  • импортwx-server-sdkпакет и названный какcloud, все методы, необходимые для работы с облаком апплетов, связаны вcloudна объекте.
  • Тогда позвониcloud.init()Для инициализации облачной среды разработки облачных функций мы реализуем ее позжеloginУстановите среду, когда это логично.
  • Наконец, функция входа в облачную функцию, которая по умолчаниюmainфункция как экспортируемая функция, являетсяasyncфункции, мы можем обрабатывать асинхронную логику синхронным образом внутри функции.Как видите, эта функция получает два параметра:eventа такжеcontext,eventОтносится к событию, которое запускает облачную функцию. Когда сторона апплета вызывает облачную функцию, событие представляет собой параметр, передаваемый, когда сторона апплета вызывает облачную функцию, плюс серверная часть, автоматически внедряемая пользователем апплета.openidИ апплетыappid.contextОбъект содержит информацию о вызове и статус выполнения вызова, который можно использовать для понимания текущего статуса службы. Внутренний код функции, сгенерированный по умолчанию, в это время в основном получает контекстную информацию WeChat, а затем связывается сeventОбъекты возвращаются вместе, так что когда мы используем сторону апплета сTaro.cloud.callFunctionВозвращаемое значение, полученное при вызове этой функции, содержит контекстную информацию WeChat иeventОбъект.

Напишите облачную функцию входа в систему

Поняв конкретную логику облачной функции, мы немедленно реализуем нашу конкретную логику входа в облачную функцию, открываемfunctions/login/index.js, и внесите соответствующие изменения в код следующим образом:

// 云函数入口文件
const cloud = require('wx-server-sdk')

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

const db = cloud.database()

// 云函数入口函数
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}`)
  }
}

Видно, что приведенные выше изменения кода в основном включают следующие шесть:

  • Сначала мы даемcloud.init()Передаются параметры окружения, мы используем встроенныйcloud.DYNAMIC_CURRENT_ENV, указывающий, что он автоматически устанавливается в текущую облачную среду, то есть щелчок правой кнопкой мыши в инструменте разработчика апплетаfunctionsСреда, выбранная при использовании папки.
  • Далее проходимcloud.database()сгенерированный экземпляр данныхdb, который позже используется для удобной работы с облачной базой данных в теле функции.
  • Тогда естьmainТело функции, мы сначала начнем сeventВызов в апплете получается из объектаTaro.cloud.callFunctionПрошло болееuserInfoданные.
  • Затем следует выборка данныхtry/catchЗаявление Блок, используемый для ловли ошибок, вtryВ блоке операторов мы используемdbоперация запроса:db.collection('user').where().get(), представляющий запросwhereусловныйuserТабличные данные, это должен быть массив, когда он узнает, если он не существует, чтобы удовлетворитьwhereусловный, то пустой массив, если существуетwhereусловно, затем вернутьuserмножество.
  • Далее мы оцениваем, пуст ли запрошенный массив пользователей, если он пуст, значит, пользователь еще не зарегистрирован, и создаем нового пользователя, если он не пуст, то возвращаем первый запрошенный элемент.
  • Здесь мы используемdb.collection('user').add(), чтобы добавитьuserданные, а затемaddпрошел в методеdataПоле, которое означает установить начальное значение этого пользователя, здесь мы дополнительно используемdb.serverDate()Используется для записи времени создания пользователя и времени обновления пользователя, что удобно для последующего условного запроса, так как после добавления записи в БД будут возвращены записи только этой записи._id, поэтому нам нужна дополнительная операцияdb.collection('user').doc()чтобы получить эту запись, этоdocИспользуется для получения указанной ссылки на запись, эти данные возвращаются вместо массива.

Уведомление

Для связанных операций с облачной базой данных здесь вы можете обратиться к облачной документации апплета WeChat, которая содержит подробные примеры:документация по базе данных.

Редьюсер для асинхронных действий

Когда мы ранее обрабатывали вход в систему, внутри компонентаdispatchохватыватьLOGINдействие, в функции саги, которая обрабатывает асинхронные действия, используйтеputИнициируется ряд действий для обновления статуса входа в магазин, и теперь мы реализуем действия, которые реагируют на эти действия.reducers,Открытьsrc/reducers/user.js, и внесите соответствующие изменения в код следующим образом:

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
  }
}

Взгляните и убедитесь, что приведенный выше код имеет три основных изменения:

  • Сначала мы импортируем необходимые константы действия
  • Затем мы даемINITIAL_STATEДобавлено несколько полей:
    • userId: используется для последующего получения пользовательских данных и для обозначения статуса входа пользователя в систему.
    • isLogin: используется для обозначения того, выполняется ли логика входа в систему во время процесса входа в систему,trueУказывает, что выполняется вход в систему,falseУказывает, что логика входа завершена
    • loginStatus: используется для отметки состояния во время входа в систему: начать вход (LOGIN),Авторизация успешна(LOGIN_SUCCESS),Авторизация не удалась(LOGIN_ERROR)
  • Ну наконец тоswitchОператор отвечает на действие и обновляет соответствующее состояние.

Завершите остальную часть асинхронной логики пользователя.

Вход в WeChat

В предыдущем разделе «Реализация асинхронной логики Redux» мы сосредоточились на реализации асинхронной логики обычной кнопки входа, теперь давайте завершим логику использования входа в WeChat. Открытьsrc/components/WeappLoginButton/index.jsфайл и внесите соответствующие изменения в его содержимое следующим образом:

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}
    >
      微信登录
    </Button>
  )
}

Как видите, приведенный выше код имеет три основных изменения:

  • Мы удалили ранее установленную информацию для входа напрямуюSET_LOGIN_INFOпостоянная, вместоLOGINпостоянный.
  • Затем мы удалили прямую настройкуstorageЛогика кэшированного кода
  • Наконец, мы начнемSET_LOGIN_INFOЛогика действия изменена на инициациюLOGINАсинхронное действие для обработки входа и сборкиuserInfoобъект какpayloadсвойства объекта.

Потому что мы уже разобрались с этим в предыдущем разделе «Реализация асинхронной логики Redux».LOGINВся логика асинхронного потока данныхdispatchсоответствующийLOGINaction может обрабатывать асинхронную логику входа в WeChat.

оптимизацияuserлогический компонент верхнего уровня

Наконец, давайте завершимuserкомпонент верхнего уровня логики,mineстраница, открыть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)

  const isLogged = !!nickName

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

        const { nickName, avatar, _id } = data

        // 更新 Redux Store 数据
        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: '我的',
}

Как видите, мы внесли три изменения в приведенный выше код следующим образом:

  • Сначала мы экспортировалиuseSelectorКрючки, полученные из Redux StorenickName.
  • Далее, поскольку мы были в разделе «Реализация асинхронной логики Redux», мы сохранилиuserIdв магазин Редуксuserлогическая часть, так что здесь мы начинаем сstorageпонятно_id, затем дайте предыдущийSET_LOGIN_INFOизpayloadпринесuserIdАтрибуты.
  • Наконец, мы судимgetStorageЛогика, только когда данных в Redux Store в это время нет, идем получать данные в хранилище для обновления Redux Store.

Расширьте диапазон чистых данных Logout

потому что в магазине Reduxuserеще один атрибутuserIdсвойства, поэтому мыLogoutкомпонентdispatchдействие, очиститьuserIdследующим образом:

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}>
      退出登录
    </AtButton>
  )
}

резюме

Готово! здесь мы ставимuserЛогика подключена к облаку апплета и может успешно реализовать вход в облако апплета на апплете WeChat Давайте попробуем предварительно просмотреть изображение предварительного просмотра эффекта локальной отладки:

Как видите, мы отлаживаем облачную функцию локально, и шаги для доступа к облачной функции из апплета следующие:

  • Мы сначала щелкаем правой кнопкой мышиfunctionsпапку и включите «Локальная отладка облачных функций».
  • Тогда выберите нашloginОблачная функция, а затем нажмите, чтобы начать локальную отладку, чтобы мы могли отлаживать облачную функцию локально.
  • Затем мы нажимаем «Войти в WeChat» на стороне апплета, и тогда мы видим, что и консоль инструментов разработчика апплета, и консоль отладки облачных функций разрешают работу облачной функции в это время.
  • Наконец, мы успешно вошли в систему, успешно отобразили логин и аватар в апплете и проверили облачную разработку> базу данных> таблицу пользователей, она добавила соответствующийuserзапись, указывающая, что мы успешно соединили терминал апплета и облако апплета.

Как правило, после локальной отладки мы можем загрузить облачную функцию в облако, чтобы мы могли использовать облачную функцию без включения локальной отладки, что необходимо для выпуска небольшой программы в Интернете.Щелкните правой кнопкой мыши в инструментах разработчика.functionsСоответствующую облачную функцию в папке, а затем выберите «Загрузить и развернуть: облачная установка так зависит»:

В этом уроке мы реализовали асинхронный процесс пользовательской логики, а в следующем уроке мы реализуем асинхронный процесс пост-логики, так что следите за обновлениями!

Хотите узнать больше интересных практических технических руководств? ПриходитьСообщество ТукеМагазин вокруг.

Исходный код, задействованный в этой статье, размещен вGithubНа, если вы считаете, что мы написали неплохо, я надеюсь, что вы можете поставить лайк этой статье ❤️ + звезда репозитория Github ❤️ о