类型错误:无法解构“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

这段代码是我认为有问题的'reducer.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;

代码是“context.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,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 状态类型。

看起来您的状态是一个具有 toDoscompleted 属性的对象,其中两个属性都是 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 接受一个参数,它是一个具有 stateaction 属性的对象。但这不是 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 添加到数组时,由于可能未找到匹配项且 targetundefined,因此在完整、未完成和编辑情况下会出现错误。我们可以将此作为条件。

  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")}
});

输入上下文后,useTodosDispatchuseTodosState 会自动推断正确的返回类型。虽然我更喜欢直截了当。

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;
};

Typescript Playground Link

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...