typescript - TypeError:无法解构“对象(...)(...)”的属性“toDos”,因为它未定义
问题描述
我是第一次学习ts的初学者。提前感谢您分享您的知识。我正在做一个待办事项清单。我曾经做出反应来完成它。但现在我正在使用 react 和 typescript 一起完成代码。
在我看来,“减速器”工作不正常。我该如何操作?
toDos
,completed
两者都有错误。我的电脑根本没有带这些东西。如果你让我知道,我将不胜感激。这是带有表面错误的“App.tsx”代码。
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;
这段代码是我认为有问题的“reducer.tsx”代码。
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,
toDos: state.toDos.filter((toDo: { id: number; }) => toDo.id !== action.payload),
completed: [...state.completed, { ...target }],
};
case UNCOMPLETE:
const aTarget = state.completed.find(
(toDo: { id: number; }) => toDo.id === action.payload
);
return {
...state,
toDos: [...state.toDos, { ...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;
此代码是“context.tsx”代码。
import React, { createContext, useReducer, useContext } from 'react';
import Reducer, { initialState } from "./reducer";
export type Todo = {
id: number;
text: string;
done: boolean;
};
export type TodosState = Todo[];
const ToDosContext = createContext<Array<Todo> | any>(null);
const ToDosProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<ToDosContext.Provider value={{ state, dispatch }}>
{children}
</ToDosContext.Provider>
);
};
export const useTodosDispatch = () => {
const { dispatch } = useContext(ToDosContext);
return dispatch;
};
export const useTodosState = () => {
const { state } = useContext(ToDosContext);
return state;
};
export default ToDosProvider;
解决方案
打字状态
interface IReducer {
state: any;
action: any;
}
这种类型并不是特别有用,因为你state
可以是任何东西!
这导致您必须在代码中进一步做出断言,例如必须{ id: number; }
在调用时添加state.toDos.filter()
,如果您state
的输入正确,则没有必要这样做。
它还导致您忽略错误,例如return;
在您的default
情况下,而不是return state;
. typescript 编译器应该选择这些类型的东西,但在这种情况下它不会显示错误,因为undefined
它仍然可以分配给您的any
状态类型。
看起来您的状态是一个具有属性的对象,toDos
并且completed
两个属性都是对象的array
s Todo
。似乎您实际上并没有使用done
该类型的属性Todo
,而是使用单独的数组来查看哪些已完成。我不确定您是否希望done
在我们从状态中选择 toDos 时添加该属性,或者它是否只是旧代码的遗物并且不需要。
interface Todo {
id: string;
text: string;
}
interface State {
toDos: Todo[];
completed: Todo[];
}
打字动作
就您的操作而言,您可以通过将操作类型定义为特定操作的所有类型的联合来获得最大的类型安全性。这就是它开始感觉“这太令人头疼了,只需使用Redux Toolkit ”的地方,因为该工具包真的带走了这么多样板文件。
对于大多数操作,看起来id
toDo 的数字是您的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()
是 astring
而不是 a number
。因此,无论您在哪里输入待办事项 idnumber
都是错误的。但是(toDo: { id: number; })
在回调中的任何地方,您都可以更改为,toDo
因为类型是从数组中已知的。
target
将 a 添加到数组时,在完成、未完成和编辑案例中存在错误,因为可能找不到匹配项并且target
is undefined
。我们可以把它作为条件。
completed: target ? [...state.completed, { ...target }] : state.completed,
我们必须在这么多地方做同样的事情,这并不好。这类事情是您可能开始考虑用于更改数组 toDos 的辅助实用程序函数的地方。或者,使用Redux Toolkit一切都变得更简单。
键入上下文
在您的原始类型中,您说上下文值是一个具有属性的 toDos 数组done
。我不确定您是否打算从状态映射到单个数组,或者这是一个错误。我会假设这是一个错误。
但如果你想要这种格式,它是:
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
_ 所以我们必须给它一个初始值,这是在没有 a 的情况下访问上下文时将使用的值,以给出一个真正的值。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,
toDos: state.toDos.filter((toDo) => toDo.id !== action.payload),
completed: target ? [...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;
};
推荐阅读
- typescript - nestjs 单元测试 createTestingModule 依赖注入
- javascript - 从检查元素是否作为另一个子元素存在于 Javascript 中删除错误
- excel - 如何将文本转换为列并忽略一些分隔符
- django - Django Ckeditor 将richtextfield中的不完整数据保存到cookies或服务器
- python - scrapy CrawlSpider 不遵循带有restrict_xpaths 的链接
- r - R中的抽象文本摘要
- javascript - MVC 交叉点观察者结构
- javascript - 具有内部异步代码和递归 EventListener 创建的 EventListener 的生命周期
- java - C# 和 Java 之间的 Diffie-Hellman 算法
- google-apps-script - 来自 Google Apps 脚本的 LDAP 搜索(最好在表格中)?