我试图让我了解React,Redux,Saga和Typescript。我设法按照一个示例来连接一个简单的页面,该页面在表格中显示数据。我现在正在尝试创建一个登录表单。我遵循了各种不同的示例。我认为动作,reducer,saga和type部分都可以,但是我真的很难将实际页面连接起来工作。
以下是到目前为止的内容,我正在寻找有关如何使它工作的指南。
打字稿错误 我的index.ts页面上显示以下打字错误:
<form onSubmit={handleSubmit(this.submit)}> //[ts] Expected 5 arguments, but got 1
<button action="submit">LOGIN</button> //[ts] Property 'action' does not exist on type ....
const formed = reduxForm({form: 'signup'})(connected) //[ts] Argument of type 'ComponentClass<Pick<AllProps, "errors" | "handleSubmit ....
问题:
显然有很多,我一直在努力解决这些问题,但没有取得很大的成功,但是我认为这是我需要帮助的地方。 -如何连接表单的onSubmit来调用我的registerRequest操作。 -如何正确连接ReduxForm并在index.tsx末尾导出
页面/注册/index.tsx
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Dispatch } from 'redux';
import { reduxForm, Field } from 'redux-form'
import { ApplicationState, ConnectedReduxProps } from '../../store'
import { Message, Error } from '../../store/register/types'
import { registerRequest } from '../../store/register/actions'
// Separate state props + dispatch props to their own interfaces.
interface PropsFromState {
email: string,
password: string,
requesting: boolean,
successful: boolean,
message: Message[],
errors: Error[],
}
// We can use `typeof` here to map our dispatch types to the props, like so.
interface PropsFromDispatch {
registerRequest: typeof registerRequest,
handleSubmit: typeof PropTypes.func,
signupRequest: typeof PropTypes.func,
}
// Combine both state + dispatch props - as well as any props we want to pass - in a union type.
type AllProps = PropsFromState & PropsFromDispatch & ConnectedReduxProps
class RegisterIndexPage extends React.Component<AllProps> {
public componentDidMount() { }
// Redux Form will call this function with the values of our
// Form fields `email` and `password` when the form is submitted
// this will in turn call the action
submit = (email: string, password: string) => {
// we could just do signupRequest here with the static proptypes
// but ESLint doesn't like that very much...
this.props.registerRequest(email, password)
}
public render() {
// grab what we need from props. The handleSubmit from ReduxForm
// and the pieces of state from the global state.
const { handleSubmit } = this.props
return (
<div className="signup">
{/* Use the Submit handler with our own submit handler*/}
<form onSubmit={handleSubmit(this.submit)}>
<h1>Register</h1>
<label htmlFor="email">Email</label>
{
/*
Our Redux Form Field components that bind email and password
to our Redux state's form -> register piece of state.
*/}
<Field
name="email"
type="text"
id="email"
className="email"
label="Email"
component="input"
/>
<label htmlFor="password">Password</label>
<Field
name="password"
type="password"
id="password"
className="password"
label="Password"
component="input"
/>
<button action="submit">LOGIN</button>
</form>
</div>
)
}
}
// It's usually good practice to only include one context at a time in a connected component.
// Although if necessary, you can always include multiple contexts. Just make sure to
// separate them from each other to prevent prop conflicts.
const mapStateToProps = ({ register }: ApplicationState) => ({
email: register.email,
password: register.password,
requesting: register.requesting,
successful: register.successful,
message: register.message,
error: register.error,
})
// patchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = (dispatch: Dispatch, { register }: ApplicationState) => ({
registerRequest: () => dispatch(registerRequest(register.email, register.password))
})
// Now let's connect our component!
// With redux v4's improved typings, we can finally omit generics here.
const connected = connect(
mapStateToProps,
mapDispatchToProps
)(RegisterIndexPage)
// Connect our connected component to Redux Form. It will namespace
// the form we use in this component as `signup`.
const formed = reduxForm({
form: 'signup'
})(connected)
// Export our well formed component!
export default formed
register / types.ts
export interface RegisterUser {
email: string
password: string
}
export interface Message {
body: string
time: Date
}
export interface Error {
body: string
time: Date
}
export const enum RegisterActionTypes {
SIGNUP_REQUESTING = '@@register/SIGNUP_REQUESTING',
SIGNUP_SUCCESS = '@@register/SIGNUP_SUCCESS',
SIGNUP_ERROR = '@@register/SIGNUP_ERROR',
}
export interface RegisterState {
readonly email: string
readonly password: string
readonly requesting: boolean,
readonly successful: boolean,
readonly message: Message[],
readonly error: Error[],
}
注册/sagas.ts
import { call, put, takeLatest } from 'redux-saga/effects'
import { RegisterActionTypes } from './types'
import { handleApiErrors } from '../../utils/handleAPIErrors'
const signupUrl = `${process.env.TEST_AUTH_API}/api/Clients`
function signupApi(email: string, password: string) {
return fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
})
.then(handleApiErrors)
.then(response => response.json())
.then(json => json)
.catch((error) => { throw error })
}
function* signupFlow(action: any) {
try {
const { email, password } = action
const response = yield call(signupApi, email, password)
yield put({ type: RegisterActionTypes.SIGNUP_SUCCESS, response })
} catch (error) {
yield put({ type: RegisterActionTypes.SIGNUP_ERROR, error })
}
}
function* signupWatcher() {
yield takeLatest(RegisterActionTypes.SIGNUP_REQUESTING, signupFlow)
}
export default signupWatcher
register / reducer.ts
import { Reducer } from 'redux'
import { RegisterState, RegisterActionTypes } from './types'
const initialState: RegisterState = {
email: "",
password: "",
requesting: false,
successful: false,
message: [],
error: [],
}
const reducer: Reducer<RegisterState> = (state = initialState, action) => {
switch (action.type) {
case RegisterActionTypes.SIGNUP_REQUESTING: {
return {
email: action.response.email,
password: action.response.password,
requesting: true,
successful: false,
message: [{ body: 'Signing up...', time: new Date() }],
error: [],
}
}
case RegisterActionTypes.SIGNUP_SUCCESS: {
return {
email: action.response.email,
password: action.response.password,
error: [],
message: [{
body: `Successfully created account for ${action.response.email}`,
time: new Date(),
}],
requesting: false,
successful: true,
}
}
case RegisterActionTypes.SIGNUP_ERROR: {
return {
email: action.response.email,
password: action.response.password,
error: state.error.concat([{
body: action.error.toString(),
time: new Date(),
}]),
message: [],
requesting: false,
successful: false,
}
}
default: {
return state
}
}
}
export { reducer as registerReducer }
register / actions.ts
import { action } from 'typesafe-actions'
import { RegisterActionTypes } from './types'
export const registerRequest = (email: string, password: string) => action(RegisterActionTypes.SIGNUP_REQUESTING, { email, password })
export default registerRequest