useReducer + Typescript的异步方式



我有一个问题与useReducer + Typescript + async。我就是做不到!当我从异步函数调用任何东西时,它返回一个破坏我代码的承诺。当我试图以其他方式获得它时,组件不会重新渲染!那简直要把我逼疯了。

我把这个问题写在我的个人项目上,它代表了我的问题!https://github.com/igormcsouza/full-stack-todo/issues/15

我怎么做才能让它工作?

我想从后端进行调用,用我从后端获得的信息填充列表。因此,每次对后端进行任何更改时,我的前端都需要重新渲染(当添加,更新或删除任何注册表时)。

reducers.tsx

import { delete_todo, fetch_todos, insert_todo, update_todo } from 
"../utils";
import { State, Actions, Todo } from "../TodoContext";
export const INITIAL_STATE: State = {
todos: [],
};
export const reducer = (state: State, action: Actions): State => {
let newState: State = {};
switch (action.type) {
case "POPULATE":
fetch_todos().then((value) => (newState = value));
return newState;
case "ADD_TODO":
if (state.todos) {
const newTodo: Todo = {
when: (+new Date()).toString(),
task: action.payload,
checked: false,
by: "Igor Souza",
};
insert_todo(newTodo);
}
fetch_todos().then((value) => (newState = value));
return newState;
case "CHECK_TODO":
action.payload.checked = !action.payload.checked;
update_todo(action.payload);
fetch_todos().then((value) => (newState = value));
return newState;
case "EDIT_TODO":
let todo = action.payload.task;
todo.task = action.payload.newTaskName;
update_todo(todo);
fetch_todos().then((value) => (newState = value));
return newState;
case "DELETE_TODO":
delete_todo(action.payload);
fetch_todos().then((value) => (newState = value));
return newState;
default:
return state;
}
};

跑龙套。TSX(带有axios调用)

import axios from "axios";
import { State, Todo } from "./TodoContext";
// const base = "http://backend:2500";
const base = "https://full-stack-todo-bknd.herokuapp.com";
export async function fetch_todos(): Promise<State> {
let todos: State = {};
await axios
.get<State>(base + "/api/todo")
.then((response) => {
const { data } = response;
todos = data;
})
.catch((e) => console.log(e));
console.log(typeof todos.todos);
return todos;
}
export async function insert_todo(todo: Todo) {
await axios.post(base + "/api/todo", todo).catch((e) => console.log(e));
}
export async function update_todo(todo: Todo) {
await axios.put(base + "/api/todo/" + todo.id).catch((e) => console.log(e));
}
export async function delete_todo(todo: Todo) {
await axios
.delete(base + "/api/todo/" + todo.id)
.catch((e) => console.log(e));
}

上下文。tsx (Context APi)

import React, { createContext, useReducer } from "react";
import { reducer, INITIAL_STATE } from "./reducers";
type ContextProps = {
state: State;
dispatch: (actions: Actions) => void;
};
export interface Todo {
id?: string;
task: string;
when: string;
checked: boolean;
by: string;
}
export interface State {
todos?: Array<Todo>;
}
export interface Actions {
type: string;
payload?: any;
}
export const TodoContext = createContext<Partial<ContextProps>>({});
const TodoContextProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
};
export default TodoContextProvider;

简单地说,你想做的事情是不可能的。不能有异步的减速器。这意味着您需要将异步逻辑移到reducer本身之外。

reducer只负责将来自action的数据应用到state。由于您在每个操作(不理想)之后都要重新获取整个列表,因此您只有一个真正的操作,即替换整个状态。您将执行aysnc抓取,然后刷新状态。

export const populate = (dispatch: Dispatch<Actions>) => {
fetch_todos().then((data) =>
dispatch({
type: "POPULATE",
payload: data
})
);
};
export const reducer = (state: State, action: Actions): State => {
switch (action.type) {
case "POPULATE":
return action.payload;
...
<button onClick={() => populate(dispatch)}>Populate</button>

dispatch函数传递给动作创建者称为"thunk"这是Redux的流行模式。我们没有任何中间件,所以我们直接调用populate(dispatch)而不是dispatch(populate())


寻找可以简化代码的方法。

我们可以利用我们所有的动作调用相同的fetch_todos()的事实,以简化事情(现在-最终你不想在每次更改后刷新整个列表)。

insert_todo,update_tododelete_todo都非常相似。主要的区别在于axios方法,它可以作为axios.request的参数传递。

虽然我看得越多,我越觉得他们应该不那么相似!您需要在put请求上传递todo数据。您希望Todo上的id属性是必需的,而add_todo则需要Omit<Todo, 'id'>


相反的方法是首先直接更改减速机状态。然后使用useEffect来检测更改并将其推送到后端。

最新更新