next.js 如何让useReducer钩子在按下Typescript ReactJS中的按钮后增加特定对象属性值?

ugmeyewa  于 2023-01-20  发布在  TypeScript
关注(0)|答案(1)|浏览(99)

我正在尝试增加Attribute的一个对象属性的值(STR,AGI,INT,和CHA)每当“加”按钮,每一个都被点击。我已经弄清楚了如何使每个按钮和属性显示在我的网站上,但我试图找到一种更直观的方法来使用useReducer操作增加每个属性的值。我以前的解决方案是为每个属性创建一个单独的useReducer操作,该操作将使该操作所绑定的属性递增一。这导致了大量多余的代码,我正在尝试找到一个更好的解决方案。我想创建一个单一的“inc_Attributes”一个允许每个属性的值单独增加的动作,而不需要一个单独的动作来增加每个Attribute属性。我怎样做一个useReducer动作来做到这一点?
在index.tsx中,它表示“({[key]:value + 1}))”是未知类型,并显示为错误,尽管我在attributes.tsx中使用value几乎相同的方式,它工作得非常好。另一件值得注意的事情是,每当我按下其中一个与Attribute属性绑定的按钮时,它会给我错误:“错误:对象作为React子级无效(找到:对象的属性名{/Attributes object property name here/})。如果你想呈现一个子对象的集合,请使用数组。
index.tsx:

import { createContext, Dispatch, useReducer } from 'react'
import Background from '@/components/background';
import Attributes from '@/components/attributes';
import Proficiencies from '@/components/proficiencies';
import Skills from '@/components/skills';

export const context = createContext<{ contextState: any, contextDispatch: Dispatch<string> } | null>(null);

export const initialState = {
  Level: 1,
  Background: {
    Gender: "",
    Father: "",
    Early_Life: "",
    Adulthood: "",
    Adventuring_Reason: "",
  },
  Attribute_Points: 4,
  Attributes: {
    STR: 5,
    AGI: 5,
    INT: 4,
    CHA: 5
  },
  Skill_Points: 0,
  Skills: {
    Ironflesh: 1,
    Power_Strike: 1,
    Power_Throw: 1,
    Power_Draw: 1,
    Weapon_Master: 1,
    Shield: 1,
    Athletics: 1,
    Riding: 2,
    Horse_Archery: 1,
    Looting: 1,
    Trainer: 1,
    Tracking: 1,
    Tactics: 1,
    Path_Finding: 1,
    Spotting: 1,
    Inventory_Management: 1,
    Wound_Treatment: 1,
    Surgery: 1,
    First_Aid: 1,
    Engineer: 1,
    Persuasion: 1,
    Prisoner_Management: 1,
    Leadership: 2,
    Trade: 1
  },
  Proficiency_Points: 0,
  Proficiencies: {
    One_Handed_Weapons: 23,
    Two_Handed_Weapons: 15,
    Polearms: 20,
    Archery: 15,
    Crossbows: 15,
    Throwing: 19
  }
}

export default function Home() {
  const reducer = (state: any, action: string) => {
    switch(action) {
      case "inc_Attributes":
        return {
          ...state, 
          Attributes: Object.entries(state.Attributes).map(([key, value]) => ({[key]: value + 1}))
        }
    }
  }
  
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <context.Provider value={{contextState: state, contextDispatch: dispatch}}>
      <Attributes />
    </context.Provider>
  )
}

attributes.tsx:

import { useContext } from 'react';
import { context } from '../pages/index';

export default function Attributes() {
    const stateContext = useContext(context);
    return (
        <div>
            {Object.entries(stateContext?.contextState.Attributes).map(([key, value]) => (
                <ul>
                    <button onClick={() => stateContext?.contextDispatch('inc_Attributes')}>+</button> 
                    <p>{key} {value}</p>
                </ul>
            ))}
        </div>
    )
}

我试着查看React给我的错误,但没有一个解决方案对我有帮助。我还试着用“[keys:string]:number”,但我找不到一种方法将它插入到我的操作中,而不会给予我一个错误消息。

qvtsj1bj

qvtsj1bj1#

由于contextState看起来有一个固定的结构,我认为为它的许多(如果不是全部的话)属性明确地定义类型是有帮助的,这可能会防止像reducer中当前错误那样的潜在错误。
在上演示以下基本示例:stackblitz
定义contextState的类型,指定Attributes(可能还有更多)属性。在这里,action是用字符串type和对象payload构造的,字符串type用于标记reducer中的操作类型,对象payload用于携带值,例如在这里增加的目标属性,但这是可选方法。

interface state {
  Attributes?: {
    [key: string]: number;
  };
  [key: string]: any;
}

interface action {
  type: string;
  payload: { [key: string]: string | number };
}

export const context = createContext<{
  contextState: state;
  contextDispatch: Dispatch<action>;
}>({ contextState: {}, contextDispatch: () => {} });

reducer中,考虑使用reduce()而不是map()来迭代Attributes[key, value],以便将结果保存为state中要更新的对象。
在这里,我认为添加!以省略对Attributesundefined检查是安全的(因为它总是被给定初始值),但是如果不希望这样,可以在运行时检查undefined的值。

const reducer = (state: state, action: action) => {
  const { type, payload } = action;
  switch (type) {
    case 'inc_Attributes': {
      const { Attributes } = state;
      return {
        ...state,
        Attributes: Object.entries(Attributes!).reduce(
          (acc, [key, value]) =>
            payload[key]
              ? { ...acc, [key]: value + Number(payload[key]) }
              : { ...acc, [key]: value },
          {}
        ),
      };
    }
    default: {
      return { ...state };
    }
  }
};

在使用contextDispatch的地方,传递字符串type和前面定义的结构中的对象payload,以便可以通过此操作更新一个或多个属性。
虽然action类型仍然是"inc_Attributes",但考虑到它在reducer中的连接方式,我认为用减号减小值也应该可以。

<ul>
  {Object.keys(Attributes!).map((key) => (
    <li key={key}>
      <button
        onClick={() =>
          stateContext?.contextDispatch({
            type: "inc_Attributes",
            payload: { [key]: 1 },
          })
        }
      >
        {`${key} + 1`}
      </button>
    </li>
  ))}
</ul>

相关问题