我有一个问题与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_todo
和delete_todo
都非常相似。主要的区别在于axios
方法,它可以作为axios.request
的参数传递。
虽然我看得越多,我越觉得他们应该不那么相似!您需要在put
请求上传递todo
数据。您希望Todo
上的id
属性是必需的,而add_todo
则需要Omit<Todo, 'id'>
。
相反的方法是首先直接更改减速机状态。然后使用useEffect
来检测更改并将其推送到后端。