如何解决类型错误:无法解构“Object(...)(...)”的属性“toDos”,因为它未定义
我是第一次学习 ts 的初学者。预先感谢您分享您的知识。 我正在制作待办事项清单。我曾经做出反应来完成它。但是现在我用 react 和 typescript 一起来完成代码。
在我看来,'reducer' 工作不正常。我该如何操作?
import random
menu1 = input("1) Addition")
menu2 = input("2) Subtraction")
choose = int(input("Enter 1 or 2"))
def random_generator():
r_1 = random.randint(4,19)
r_2 = random.randint(4,19)
print("First number: " + str(r_1))
print("Second number: " + str(r_2))
user_adds = int(input("Add two random numbers"))
r_ans = r_1 + r_2
return r_ans,user_adds
def random_generator3():
r_1 = random.randint(24,49)
r_2 = random.randint(2,24)
print("First number: " + str(rando_1))
print("Second number: " + str(rando_2))
r_ans = rando_1 - rando_2
user_adds = int(input("Work out random number 1- random number 2: "))
return r_ans,user_adds
def verify(r_ans,user_adds):
if r_ans == user_adds:
print("Correct")
else:
print("Incorrect. The answer is: " + str(r_ans))
def main():
if choose == 1:
random_generator()
verify(r_ans,user_adds)
elif choose == 2:
random_generator3()
verify(r_ans,user_adds)
else:
print("Invalid input")
main()
,todos
两者都有错误。我的电脑根本不带这些东西。
如果你让我知道,我将不胜感激。这是带有表面错误的“App.tsx”代码。
completed
import React from "react";
import Add from "./Add";
import List from "./List";
import Todo from "./Todo";
import Title from "./Title";
import Progress from "./Progress";
import styled from "styled-components";
import { usetodosstate } from '../context';
function App() {
const { todos,completed } = usetodosstate();
return (
<Title>
<Add />
<Progress />
<Lists>
<List title={todos.length !== 0 ? "To Dos" : ""}>
{todos.map((todo: any) => (
<Todo key={todo.id} id={todo.id} text={todo.text} isCompleted={false} />
))}
</List>
<List title={completed.length !== 0 ? "Completed" : ""}>
{completed.map((todo: any) => (
<Todo key={todo.id} id={todo.id} text
{...todo.text} isCompleted />
))}
</List>
</Lists>
</Title>
);
}
export default App;
import { v4 as uuidv4 } from "uuid";
import { ADD,DEL,COMPLETE,UNCOMPLETE,EDIT } from "./actions";
export const initialState = {
todos: [],completed: [],};
interface IReducer {
state: any;
action: any;
}
const Reducer = ({ state,action }: IReducer) => {
switch (action) {
case ADD:
return {
...state,todos: [...state.todos,{ text: action.payload,id: uuidv4() }],};
case DEL:
return {
...state,todos: state.todos.filter((todo: { id: number; }) => todo.id !== action.payload),};
case COMPLETE:
const target = state.todos.find((todo: { id: number; }) => todo.id === action.payload);
return {
...state,completed: [...state.completed,{ ...target }],};
case UNCOMPLETE:
const aTarget = state.completed.find(
(todo: { id: number; }) => todo.id === action.payload
);
return {
...state,{ ...aTarget }],completed: state.completed.filter(
(complete: { id: number; }) => complete.id !== action.payload
),};
case EDIT:
const bTarget = state.todos.find((todo: { id: number; }) => todo.id === action.id);
const rest = state.todos.filter((todo: { id: number; }) => todo.id !== action.id);
return {
...state,todos: rest.concat({ ...bTarget,text: action.payload }),};
default:
return;
}
};
export default Reducer;
解决方法
输入状态
interface IReducer {
state: any;
action: any;
}
这种类型不是特别有用,因为您的 state
可以是任何东西!
这导致您必须在代码中进一步做出断言,例如在调用 { id: number; }
时必须添加 state.toDos.filter()
,如果您的 state
正确,则不需要打字。
这也会导致您忽略错误,例如在您的 return;
情况下使用 default
而不是 return state;
。 typescript 编译器应该会处理这些类型的事情,但在这种情况下它不会显示错误,因为 undefined
仍然可以分配给您的 any
状态类型。
看起来您的状态是一个具有 toDos
和 completed
属性的对象,其中两个属性都是 array
对象的 Todo
。似乎您实际上并未在 done
类型上使用 Todo
属性,而是使用单独的数组来查看哪些已完成。我不确定您是否希望在我们从 state 中选择 toDos 时添加 done
属性,或者它是否只是旧代码的遗物而不需要。
interface Todo {
id: string;
text: string;
}
interface State {
toDos: Todo[];
completed: Todo[];
}
打字操作
就您的操作而言,您可以通过将操作类型定义为特定操作的所有类型的联合来获得最大的类型安全性。这就是它开始感觉 “这太头疼了,只需使用 Redux Toolkit” 的地方,因为该工具包确实带走了很多样板。
对于大多数操作,toDo 的数字 id
看起来就是您的 action.payload
。但是对于您的编辑操作,id 是 action.id
,文本是有效负载。我不喜欢这种不一致的地方,但我只是要输入您在此处拥有的内容,而不是更改减速器。
type Action = {
type: typeof ADD | typeof DEL | typeof COMPLETE | typeof UNCOMPLETE;
payload: string;
} | {
type: typeof EDIT;
payload: string;
id: string;
}
打字减速器
当我开始为减速器添加类型时,一个我以前没有注意到的重大错误就被突出显示了!这就是为什么正确的类型如此重要。您的 switch
语句在应该在 action
上时根据 action.type
进行切换。
现在你的 reducer 接受一个参数,它是一个具有 state
和 action
属性的对象。但这不是 reducer 的内容,如果您不接受正确的参数,它将无法与 useReducer
(或 redux)一起使用。 reducer 函数看起来像 (state,action) => newState
。
const reducer = (state: State,action: Action): State
当我解决这个问题时,我开始看到更多错误被突出显示。事实证明,您通过调用 id
创建的 uuidv4()
是 string
而不是 number
。因此,您在任何地方将待办事项 ID 输入为 number
都是错误的。但是在回调中有 (toDo: { id: number; })
的任何地方,您都可以更改为 toDo
,因为该类型是从数组中已知的。
将 target
添加到数组时,由于可能未找到匹配项且 target
为 undefined
,因此在完整、未完成和编辑情况下会出现错误。我们可以将此作为条件。
completed: target ? [...state.completed,{ ...target }] : state.completed,
我们必须在这么多地方做同样的事情,这并不好。这类事情是您可能开始考虑用于更改数组 toDos 的辅助实用程序函数的地方。或者,使用 Redux Toolkit 一切都会变得更轻松。
输入上下文
在您的原始类型中,您说上下文值是具有属性 done
的单个 toDo 数组。我不确定您是否打算从状态映射到单个数组,或者这是否是一个错误。我会假设这是一个错误。
但如果你想要那种格式,那就是:
const withDone = (state: State): Array<Todo & {done: boolean}> => {
return [
...state.toDos.map(todo => ({...todo,done: false})),...state.completed.map(todo => ({...todo,done: true})),]
}
我们不需要在 useReducer
上指定任何类型,因为这些都可以从我们的强类型 reducer
函数中推断出来。哇!但是我们确实需要指定上下文值的类型。
interface ContextValue {
state: State;
dispatch: React.Dispatch<Action>;
}
const ToDosContext = createContext<ContextValue>(null);
除非您在 strictNullChecks
中关闭了 tsconfig
,否则您可能会在将 null
指定为上下文的初始值时遇到错误,因为 null
无法指定给ContextValue
。所以我们必须给它一个初始值,这个值是在没有 Provider
的情况下访问上下文时将使用的值来给出实际值。
const ToDosContext = createContext<ContextValue>({
state: initialState,dispatch: () => { console.error("called dispatch outside of a ToDosContext Provider")}
});
输入上下文后,useTodosDispatch
和 useTodosState
会自动推断正确的返回类型。虽然我更喜欢直截了当。
export const useTodosDispatch = (): React.Dispatch<Action> => { ... };
export const useTodosState = (): State => { ... };
现在一起
终于没有错误了!当我向事物添加类型时,我发现了许多以前被所有 any
隐藏的错误。完整代码如下:
import React,{ createContext,useContext,useReducer } from "react";
import { v4 as uuidv4 } from "uuid";
import { ADD,DEL,COMPLETE,UNCOMPLETE,EDIT } from "./actions";
interface Todo {
id: string;
text: string;
}
interface State {
toDos: Todo[];
completed: Todo[];
}
type Action = {
type: typeof ADD | typeof DEL | typeof COMPLETE | typeof UNCOMPLETE;
payload: string;
} | {
type: typeof EDIT;
payload: string;
id: string;
}
const reducer = (state: State,action: Action): State => {
switch (action.type) {
case ADD:
return {
...state,toDos: [...state.toDos,{ text: action.payload,id: uuidv4() }],};
case DEL:
return {
...state,toDos: state.toDos.filter((toDo) => toDo.id !== action.payload),};
case COMPLETE:
const target = state.toDos.find((toDo) => toDo.id === action.payload);
return {
...state,completed: target ? [...state.completed,};
case UNCOMPLETE:
const aTarget = state.completed.find(
(toDo) => toDo.id === action.payload
);
return {
...state,toDos: aTarget ? [...state.toDos,{ ...aTarget }] : state.toDos,completed: state.completed.filter(
(complete) => complete.id !== action.payload
),};
case EDIT:
const bTarget = state.toDos.find((toDo) => toDo.id === action.id);
const rest = state.toDos.filter((toDo) => toDo.id !== action.id);
return {
...state,toDos: bTarget ? rest.concat({ ...bTarget,text: action.payload }) : rest,};
default:
return state;
}
};
interface ContextValue {
state: State;
dispatch: React.Dispatch<Action>;
}
export const initialState = {
toDos: [],completed: [],};
const ToDosContext = createContext<ContextValue>({
state: initialState,dispatch: () => { console.error("called dispatch outside of a ToDosContext Provider") }
});
const ToDosProvider = ({ children }: { children: React.ReactNode }) => {
const [state,dispatch] = useReducer(reducer,initialState);
return (
<ToDosContext.Provider value={{ state,dispatch }}>
{children}
</ToDosContext.Provider>
);
};
export const useTodosDispatch = (): React.Dispatch<Action> => {
const { dispatch } = useContext(ToDosContext);
return dispatch;
};
export const useTodosState = (): State => {
const { state } = useContext(ToDosContext);
return state;
};
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。