[React 实战系列] 注册功能的实现

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[React 实战系列] 注册功能的实现相关的知识,希望对你有一定的参考价值。

[React 实战系列] 注册功能的实现

之前已经实现的内容:

注册功能的实现

注册的页面 UI 既然已经实现了,剩下的就是向里面填充功能。注册的功能会使用 Redux 去进行规范化,所以,首先需要时间 Redux 的 Action 和 Reducer。

注册的 Redux

回顾一下 Redux 的核心逻辑就是这样的一个轮回:

发送
触发
更新
提供信息
store
action
reducer
UI

因此,当前的实现是一旦 UI 上的点击事件被触发,就在 action 中异步调用 AJAX,随后通过 reducer 去将用户信息更新到 store/local storage 中。因为涉及到了异步调用,所以这里会使用 Redux Saga 去进行异步操作。

注册页面的 Action

新建注册页面所需的文件,其结构为 store > actions > auth.action.ts,此时的 Redux 结构为:

|- store
|  |- actions
|  |  |- auth.action.ts
|  |- reducers
|  |  |- ... # 其余文件省略

接下来的实现中,首先会使用 const 去定义每一个 action 的 type,随后会使用 TypeScript 对 Redux 进行规范化。这里会使用 interface 去定义每一个 action 会接受的 payload,再使用 interface 去规定每一个 action 所要接收的参数。

注册这个业务场景存在三种 actions 可能会发生:

  1. 注册本身
  2. 注册成功
  3. 注册失败

这里也会将内容拆分成针对每一个业务的功能区实现。

在开始正式编写 actions 之前,先复习一下 action 的基本返回类型:

action = {
  type: 'action type in string',
  payload: {
    payloadObj: payloadVal,
  },
};

action 的返回值必须包含的值是 type,reducer 可以通过 action type 去执行正确的操作。payload 则是传给 reducer 的值,大多数情况下 reducer 从传进来的 payload 中获取需要更新的值。

注册业务

首先是 action type 的实现:

export const SIGNUP = 'SIGNUP';

随后就需要使用 TypeScript 对 action 以及 payload 进行定义。

首先,是 action 自己本身接受的 参数(payload) 的定义。根据页面的表单,注册需要的是邮箱地址、姓名和密码三个条件。随后,再定义 action 自己自身的定义。

具体实现如下:

export interface SignupPayload {
  email: string;
  name: string;
  password: string;
}

export interface SignupAction {
  type: typeof SIGNUP;
  payload: SignupPayload;
}

有了定义,就可以对 action 进行实现了:

// signup 这个 action 会接受一个参数,这个参数的类型就是 SignupPayload
export const signup = (payload: SignupPayload) => ({
  type: SIGNUP,
  payload,
});
注册成功

注册成功的 action 不需要 payload 这个值,只需要 type 类型以及成功的 action 即可。具体实现如下:

export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS';

// 需要定义成功的action的数据类型
export interface SignupSuccessAction {
  type: typeof SIGNUP_SUCCESS;
}

// 这里就是注册成功时,传到reducer中的action类型
export const signupSuccess = (): SignupSuccessAction => ({
  type: SIGNUP_SUCCESS,
});
注册失败

注册失败的流程和注册成功的流程类似,只不过在 action 的定义中需要添加一条错误信息。以及在最后需要导出一个认证的 action 类型,这个类型就是所有注册 actions 的联合类型。

export const SIGNUP_FAIL = 'SIGNUP_FAIL';

export interface SignupFailAction {
  type: typeof SIGNUP_FAIL;
  message: string;
}

// 导出联合类型以供 reducer 使用,这样导出的 AuthUnionType 是上面实现的三种 action 之一
export type AuthUnionType =
  | SignupAction
  | SignupSuccessAction
  | SignupFailAction;

至此,与 action 相关的内容就全部定义好了,也可以开始对 reducer 的实现了。

注册页面的 Reducer

新建注册页面所需的文件,其结构与 action 相对应,为 store > reducers > auth.reducer.ts,此时的 Redux 结构为:

|- store
|  |- actions
|  |  |- auth.action.ts
|  |- reducers
|  |  |- auth.reducer.ts
|  |  |- ... # 其余文件省略

Reducer 代码部分逻辑相对简单一点,主要由两个部分组成:

  1. state

    最开始的申明代表的是初始化状态,鉴于是使用 TypeScript 去实现的,也自然会用 interface 去对状态进行规范化

  2. reducer

    这里通过 发送(dispatch) 的 action 来判断应该进行什么样的逻辑处理

接下来就根据业务逻辑去实现 authReducer:

// Auth状态中管理注册的逻辑
// loaded代表API是否调用成功
// success代表是否注册成功
// message代表的是错误信息
export interface AuthState {
  signup: {
    loaded: boolean;
    success: boolean;
    message: string;
  };
}

// 根据interface去初始化状态
const initialState: AuthState = {
  signup: {
    loaded: false,
    success: false,
    message: '',
  },
};

export default function authReducer(
  state = initialState,
  action: AuthUnionType
) {
  switch (action.type) {
    case SIGNUP:
      return {
        ...state,
        signup: {
          loaded: false,
          success: false,
          message: '',
        },
      };
    case SIGNUP_SUCCESS:
      return {
        ...state,
        signup: {
          loaded: true,
          success: true,
          message: '',
        },
      };
    case SIGNUP_FAIL:
      return {
        ...state,
        signup: {
          loaded: true,
          success: false,
          message: action.message,
        },
      };
    // 如果是默认则返回初始状态
    default:
      return state;
  }
}

总体来说,目前注册的 reducer 逻辑还是很简单的,主要的处理逻辑就是:

  • 注册刚刚触发时,触发接口、注册成功都设置为 false,报错信息则是空的字符串;

  • 注册成功时注册成功都设置为 true,报错信息依旧是空的字符串;

  • 注册失败时则接口调用设置为 true,成功设置为 false,并且将 action 中传递来的信息传给 message。

    回顾失败的 action 定义是这样的:

    export interface SignupFailAction {
      type: typeof SIGNUP_FAIL;
      message: string;
    }
    

    除了 type 之外,action 还直接会传给 reducer 一个 message,表示报错信息。

将注册页面的 reducer 添加到 rooterReducer

这里只需要更新一下 rootReducer 即可,也就是 store > reducers > index.ts 这个文件,内容如下:

const createRootReducer = (history: History) =>
  combineReducers({
    router: connectRouter(history),
    auth: authReducer,
  });

注册页面的 Redux Saga

Redux Saga 是负责异步调用的部分,只有 Redux Saga 实现了之后才能够真正的确认程序是否能够正常运行。

Redux-Saga 的首页其实已经将 Redux Saga 的应用步骤写得非常明白了:

  1. 发送一个 action

    这个会在登录的组件内被触发

  2. 初始化一个副作用

    这里指的就是 API 调用

  3. 将 Redux Saga 挂载到中间件上

现在就按照 1 -> 2 -> 3 的步骤去实现 Redux Saga。

修改注册页面发送 Action

这一步其实就是触发 Redux 的 Action,配置成功的 Saga 可以监听到 Redux 的状态变化,再根据相应的函数去触发副作用,实现的代码如下:

// ...
import { useDispatch } from 'react-redux';
import { signup, SignupPayload } from '../../store/actions/auth.actions';

const Signup = () => {
  const dispatch = useDispatch();

  const onFinish = (value: SignupPayload) => {
    console.log(value);
    dispatch(signup(value));
  };

  return (
    <Layout title="注册" subTitle={''}>
      <Form onFinish={onFinish}>{/* 省略其他 */}</Form>
    </Layout>
  );
};

export default Signup;

onFinish 是提交表单时触发的回调函数,是最合适发送 action 的地方。

配置 Saga 并使用 Axios 发送异步请求

这一步需要在 src/store 下面创建一个新的文件夹,并且集中对需要触发 Saga 的状态变化进行处理。

其目录结构如下:

|- src
|  |- ...
|  |- stroe
|  |  |- sagas
|  |  | |- auth.saga.ts # 这里实现验证功能
|  |  | |- index.ts # Saga 的集中管理

Saga 的功能需要使用 generators,这也是可以算是 generators 处理异步函数的案例了。

  • auth.saga.ts 的实现

    import axios from 'axios';
    import { takeEvery, put } from 'redux-saga/effects';
    import { API } from '../../config';
    import {
      SIGNUP,
      SignupAction,
      signupFail,
      signupSuccess,
    } from '../actions/auth.actions';
    
    function* handleSignup(action: SignupAction) {
      try {
        yield axios.post(`${API}/signup`, action.payload);
        yield put(signupSuccess());
      } catch (error: any) {
        yield put(signupFail(error.response.data.error));
      }
    }
    
    export default function* authSaga() {
      yield takeEvery(SIGNUP, handleSignup);
    }
    

    可以看到,auth.saga 导出的是一个 generator,而 auth.saga 中执行异步的函数也是一个 generator。

    Generator 的优势有:

    • 可迭代
    • 可中断
    • 可继续
    • 可暂停
    • 可用于循环调用并等待异步操作
    • 中断与继续的操作使用 yield 实现

    了解了 Generator 的作用,那么代码的作用也比较直接明了了:

    每一次 SIGNUP action 被触发之后,都会调用 handleSignup Generator 进行注册的处理。handleSignup 之中会进行异步函数的调用。根据返回的结果继续触发下一个的 Action。

  • index.ts

    import { all } from 'redux-saga/effects';
    import authSaga from './auth.saga';
    
    export default function* rootSaga() {
      yield all([authSaga()]);
    }
    

    这一段代码的作用类似于 rootReducer,将所有的 Saga 内容进行其中管理。

挂载 Saga

这部分会将 Saga 挂载到 Redux 中的 Middleware 上,完成最后的配置,操作的文件为 src/store/index.ts,内容如下:

// ...
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';

// ...
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  createRootReaducer(history),
  applyMiddleware(routerMiddleware(history), logger, sagaMiddleware)
);

// execute the saga middleware
sagaMiddleware.run(rootSaga);

这时候再去提交表单,就会调用 Axios 执行异步操作,并且处理相应的副作用:

为了控制大小截图不是很成功,但是基本上还是显示了一旦注册之后,/signup 的 API 就被调用了。

至此,注册功能就实现了。下一步会优化注册功能:

  • 显示注册成功信息
  • 显示注册失败信息
  • 重置状态

以及实现登陆功能。

以上是关于[React 实战系列] 注册功能的实现的主要内容,如果未能解决你的问题,请参考以下文章

[React 实战系列] 布局登录注册的页面实现及 Route 的封装

基于 React hooks + Typescript + Cesium 的 三维 webgis 实战系列教程

React系列实战篇:留言功能

React系列实战篇:留言功能

Flask博客实战 - 实现登录注册功能

React实战_实现待办事项TodoList(Hook版)